forum.module 38.6 KB
Newer Older
Dries's avatar
   
Dries committed
1
2
3
<?php
// $Id$

Dries's avatar
   
Dries committed
4
5
/**
 * @file
6
 * Provides discussion forums.
Dries's avatar
   
Dries committed
7
8
 */

9
/**
10
 * Implement hook_help().
11
 */
12
13
function forum_help($path, $arg) {
  switch ($path) {
14
    case 'admin/help#forum':
15
16
17
18
19
20
21
22
23
24
25
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<p>' . t('The Forum module lets you create threaded discussion forums with functionality similar to other message board systems. Forums are useful because they allow community members to discuss topics with one another while ensuring those conversations are archived for later reference. Forums behave by posting topics and threads in nested hierarchies, which allow many discussions to be categorized in various ways. The <a href="@create-topic">forum topic</a> link on the <a href="@content-add">Add new content</a> page creates the first post of a new threaded discussion, or thread. For more information, see the online handbook entry for <a href="@forum">Forum module</a>.', array('@create-topic' => url('node/add/forum'), '@content-add' => url('node/add'), '@forum' => 'http://drupal.org/handbook/modules/forum/')) . '</p>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
      $output .= '<dt>' . t('Navigation') . '</dt>';
      $output .= '<dd>' . t('Enabling the Forum module provides a default <em>Forums</em> menu item in the navigation menu that links to the <a href="@forums">main forums page</a>.', array('@forums' => url('forum'))) . '</dd>';
      $output .= '<dt>' . t('Moving forum topics') . '</dt>';
      $output .= '<dd>' . t('A forum topic (and all of its comments) may be moved between forums by selecting a different forum while editing a forum topic. When moving a forum topic between forums, the <em>Leave shadow copy</em> option creates a link in the original forum pointing to the new location.') . '</dd>';
      $output .= '<dt>' . t('Locking and disabling comments') . '</dt>';
      $output .= '<dd>' . t('Selecting <em>Closed</em> under <em>Comment settings</em> while editing a forum topic will lock (prevent new comments on) the thread. Selecting <em>Disabled</em> under <em>Comment settings</em> while editing a forum topic will hide all existing comments on the thread, and prevent new ones.') . '</dd>';
      $output .= '</dl>';
26
      return $output;
27
    case 'admin/structure/forum':
28
      return '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
29
    case 'admin/structure/forum/add/container':
30
      return '<p>' . t('Use containers to group related forums.') . '</p>';
31
    case 'admin/structure/forum/add/forum':
32
      return '<p>' . t('A forum holds related forum topics.') . '</p>';
33
    case 'admin/structure/forum/settings':
34
      return '<p>' . t('Adjust the display of your forum topics. Organize the forums on the <a href="@forum-structure">forum structure page</a>.', array('@forum-structure' => url('admin/structure/forum'))) . '</p>';
Dries's avatar
   
Dries committed
35
36
37
  }
}

38
/**
39
 * Implement hook_theme().
40
41
42
 */
function forum_theme() {
  return array(
43
    'forums' => array(
44
      'template' => 'forums',
45
      'variables' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
46
47
    ),
    'forum_list' => array(
48
      'template' => 'forum-list',
49
      'variables' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
50
51
    ),
    'forum_topic_list' => array(
52
      'template' => 'forum-topic-list',
53
      'variables' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
54
55
    ),
    'forum_icon' => array(
56
      'template' => 'forum-icon',
57
      'variables' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0),
58
    ),
59
    'forum_submitted' => array(
60
      'template' => 'forum-submitted',
61
      'variables' => array('topic' => NULL),
62
    ),
63
    'forum_form' => array(
64
      'render element' => 'form',
65
66
      'file' => 'forum.admin.inc',
    ),
67
68
69
  );
}

70
/**
71
 * Implement hook_menu().
72
 */
73
74
function forum_menu() {
  $items['forum'] = array(
75
    'title' => 'Forums',
76
77
    'page callback' => 'forum_page',
    'access arguments' => array('access content'),
78
    'file' => 'forum.pages.inc',
79
  );
80
  $items['admin/structure/forum'] = array(
81
    'title' => 'Forums',
82
    'description' => 'Control forum hierarchy settings.',
83
84
    'page callback' => 'drupal_get_form',
    'page arguments' => array('forum_overview'),
85
    'access arguments' => array('administer forums'),
86
    'file' => 'forum.admin.inc',
87
  );
88
  $items['admin/structure/forum/list'] = array(
89
    'title' => 'List',
90
91
92
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
93
  $items['admin/structure/forum/add/container'] = array(
94
    'title' => 'Add container',
95
96
    'page callback' => 'forum_form_main',
    'page arguments' => array('container'),
97
    'access arguments' => array('administer forums'),
98
    'type' => MENU_LOCAL_ACTION,
99
    'parent' => 'admin/structure/forum',
100
    'file' => 'forum.admin.inc',
101
  );
102
  $items['admin/structure/forum/add/forum'] = array(
103
    'title' => 'Add forum',
104
105
    'page callback' => 'forum_form_main',
    'page arguments' => array('forum'),
106
    'access arguments' => array('administer forums'),
107
    'type' => MENU_LOCAL_ACTION,
108
    'parent' => 'admin/structure/forum',
109
    'file' => 'forum.admin.inc',
110
  );
111
  $items['admin/structure/forum/settings'] = array(
112
    'title' => 'Settings',
113
114
    'page callback' => 'drupal_get_form',
    'page arguments' => array('forum_admin_settings'),
115
    'access arguments' => array('administer forums'),
116
117
    'weight' => 5,
    'type' => MENU_LOCAL_TASK,
118
    'parent' => 'admin/structure/forum',
119
    'file' => 'forum.admin.inc',
120
  );
121
  $items['admin/structure/forum/edit/container/%taxonomy_term'] = array(
122
    'title' => 'Edit container',
123
    'page callback' => 'forum_form_main',
124
    'page arguments' => array('container', 5),
125
    'access arguments' => array('administer forums'),
126
    'type' => MENU_CALLBACK,
127
    'file' => 'forum.admin.inc',
128
  );
129
  $items['admin/structure/forum/edit/forum/%taxonomy_term'] = array(
130
    'title' => 'Edit forum',
131
132
    'page callback' => 'forum_form_main',
    'page arguments' => array('forum', 5),
133
    'access arguments' => array('administer forums'),
134
    'type' => MENU_CALLBACK,
135
    'file' => 'forum.admin.inc',
136
137
138
  );
  return $items;
}
139

140
141

/**
142
 * Implement hook_init().
143
 */
144
function forum_init() {
145
  drupal_add_css(drupal_get_path('module', 'forum') . '/forum.css');
146
}
147

148
/**
149
150
151
152
 * Check whether a content type can be used in a forum.
 *
 * @param $node
 *   A node object.
153
 *
154
155
 * @return
 *   Boolean indicating if the node can be assigned to a forum.
156
 */
157
function _forum_node_check_node_type($node) {
158
  // Fetch information about the forum field.
159
  $field = field_info_instance('node', 'taxonomy_forums', $node->type);
160

161
  return is_array($field);
162
}
163

164
/**
165
 * Implement hook_node_view().
166
 */
167
function forum_node_view($node, $build_mode) {
168
  $vid = variable_get('forum_nav_vocabulary', 0);
169
  $vocabulary = taxonomy_vocabulary_load($vid);
170
171
  if (_forum_node_check_node_type($node)) {
    if ((bool)menu_get_object()) {
172
173
174
      // Breadcrumb navigation
      $breadcrumb[] = l(t('Home'), NULL);
      $breadcrumb[] = l($vocabulary->name, 'forum');
175
      if ($parents = taxonomy_get_parents_all($node->forum_tid)) {
176
        $parents = array_reverse($parents);
177
178
        foreach ($parents as $parent) {
          $breadcrumb[] = l($parent->name, 'forum/' . $parent->tid);
179
180
181
        }
      }
      drupal_set_breadcrumb($breadcrumb);
182

183
184
185
    }
  }
}
186

187
/**
188
 * Implement hook_node_prepare().
189
 */
190
function forum_node_prepare($node) {
191
  if (_forum_node_check_node_type($node)) {
192
193
    if (empty($node->nid)) {
      // New topic
194
      $node->taxonomy_forums[0]['tid'] =  arg(3);
195
196
197
198
199
    }
  }
}

/**
200
 * Implement hook_node_validate().
201
 *
202
203
 * Check in particular that only a "leaf" term in the associated taxonomy.
 */
204
function forum_node_validate($node, $form) {
205
206
  if (_forum_node_check_node_type($node)) {
    $langcode = $form['taxonomy_forums']['#language'];
207
    // vocabulary is selected, not a "container" term.
208
    if (!empty($node->taxonomy_forums[$langcode])) {
209
210
      // Extract the node's proper topic ID.
      $containers = variable_get('forum_containers', array());
211
212
      foreach ($node->taxonomy_forums[$langcode] as $item) {
        $term = taxonomy_term_load($item['tid']);
213
214
215
        $used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid',0 , 1, array(
          ':tid' => $term->tid,
          ':vid' => $term->vid,
216
        ))->fetchField();
217
        if ($used && in_array($term->tid, $containers)) {
218
          form_set_error('taxonomy_forums', t('The item %forum is only a forum container. Select one of the forums below it.', array('%forum' => $term->name)));
219
220
        }
      }
221
222
223
    }
  }
}
224

225
/**
226
 * Implement hook_node_presave().
227
228
229
 *
 * Assign forum taxonomy when adding a topic from within a forum.
 */
230
function forum_node_presave($node) {
231
  if (_forum_node_check_node_type($node)) {
232
233
    // Make sure all fields are set properly:
    $node->icon = !empty($node->icon) ? $node->icon : '';
234
235
    $langcode = array_shift(array_keys($node->taxonomy_forums));
    if (!empty($node->taxonomy_forums[$langcode])) {
236
      $node->forum_tid = $node->taxonomy_forums[$langcode][0]['tid'];
237
      $old_tid = db_query_range("SELECT f.tid FROM {forum} f INNER JOIN {node} n ON f.vid = n.vid WHERE n.nid = :nid ORDER BY f.vid DESC", 0, 1, array(':nid' => $node->nid))->fetchField();
238
      if ($old_tid && isset($node->forum_tid) && ($node->forum_tid != $old_tid) && !empty($node->shadow)) {
239
        // A shadow copy needs to be created. Retain new term and add old term.
240
        $node->taxonomy_forums[$langcode][] = array('tid' => $old_tid);
241
242
243
244
      }
    }
  }
}
245

246
/**
247
 * Implement hook_node_update().
248
 */
249
function forum_node_update($node) {
250
  if (_forum_node_check_node_type($node)) {
251
    if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
252
      if (!empty($node->forum_tid)) {
253
        db_update('forum')
254
          ->fields(array('tid' => $node->forum_tid))
255
256
          ->condition('vid', $node->vid)
          ->execute();
257
258
259
      }
      // The node is removed from the forum.
      else {
260
261
262
        db_delete('forum')
          ->condition('nid', $node->nid)
          ->execute();
263
264
265
      }
    }
    else {
266
      if (!empty($node->forum_tid)) {
267
268
        db_insert('forum')
          ->fields(array(
269
            'tid' => $node->forum_tid,
270
271
272
273
            'vid' => $node->vid,
            'nid' => $node->nid,
          ))
          ->execute();
274
      }
275
    }
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
    // If the node has a shadow forum topic, update the record for this
    // revision.
    if ($node->shadow) {
      db_delete('forum')
        ->condition('nid', $node->nid)
        ->condition('vid', $node->vid)
        ->execute();
      db_insert('forum')
        ->fields(array(
          'nid' => $node->nid,
          'vid' => $node->vid,
          'tid' => $node->forum_tid,
        ))
        ->execute();
     }
291
292
  }
}
293

294
/**
295
 * Implement hook_node_insert().
296
 */
297
function forum_node_insert($node) {
298
299
  if (_forum_node_check_node_type($node)) {
    if (!empty($node->forum_tid)) {
300
301
      $nid = db_insert('forum')
        ->fields(array(
302
          'tid' => $node->forum_tid,
303
304
305
306
          'vid' => $node->vid,
          'nid' => $node->nid,
        ))
        ->execute();
307
308
309
    }
  }
}
310

311
/**
312
 * Implement hook_node_delete().
313
 */
314
function forum_node_delete($node) {
315
  if (_forum_node_check_node_type($node)) {
316
317
318
    db_delete('forum')
      ->condition('nid', $node->nid)
      ->execute();
319
320
321
    db_delete('forum_index')
      ->condition('nid', $node->nid)
      ->execute();
322
  }
323
}
324

325
/**
326
 * Implement hook_node_load().
327
 */
328
function forum_node_load($nodes) {
329
330
  $node_vids = array();
  foreach ($nodes as $node) {
331
    if (_forum_node_check_node_type($node)) {
332
333
334
335
      $node_vids[] = $node->vid;
    }
  }
  if (!empty($node_vids)) {
336
337
338
339
340
    $query = db_select('forum', 'f');
    $query
      ->fields('f', array('nid', 'tid'))
      ->condition('f.vid', $node_vids);
    $result = $query->execute();
341
342
343
    foreach ($result as $record) {
      $nodes[$record->nid]->forum_tid = $record->tid;
    }
344
  }
345
346
}

347
/**
348
 * Implement hook_node_info().
349
 */
350
function forum_node_info() {
351
352
  return array(
    'forum' => array(
353
      'name' => t('Forum topic'),
354
      'base' => 'forum',
355
      'description' => t('A <em>forum topic</em> starts a new discussion thread within a forum.'),
356
357
358
      'title_label' => t('Subject'),
    )
  );
Dries's avatar
   
Dries committed
359
360
}

361
/**
362
 * Implement hook_permission().
363
 */
364
function forum_permission() {
365
  $perms = array(
366
367
368
    'administer forums' => array(
      'title' => t('Administer forums'),
    ),
369
370
  );
  return $perms;
Dries's avatar
   
Dries committed
371
}
Dries's avatar
   
Dries committed
372

373
/**
374
 * Implement hook_taxonomy().
375
 */
376
377
378
379
380
381
382
383
384
function forum_taxonomy_term_delete($tid) {
  // For containers, remove the tid from the forum_containers variable.
  $containers = variable_get('forum_containers', array());
  $key = array_search($tid, $containers);
  if ($key !== FALSE) {
    unset($containers[$key]);
  }
  variable_set('forum_containers', $containers);
}
385

386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
/**
 * Implement hook_comment_publish().
 *
 * This actually handles the insert and update of published nodes since
 * comment_save() calls hook_comment_publish() for all published comments.
 */
function forum_comment_publish($comment) {
  _forum_update_forum_index($comment->nid);
}

/**
 * Implement forum_comment_update().
 *
 * Comment module doesn't call hook_comment_unpublish() when saving individual
 * comments so we need to check for those here.
 */
function forum_comment_update($comment) {
  // comment_save() calls hook_comment_publish() for all published comments
  // so we to handle all other values here.
  if (!$comment->status) {
    _forum_update_forum_index($comment->nid);
  }
}

/**
411
 * Implements forum_comment_unpublish().
412
413
414
415
416
417
418
419
420
421
422
423
424
 */
function forum_comment_unpublish($comment) {
  _forum_update_forum_index($comment->nid);
}

/**
 * Implement forum_comment_delete().
 */
function forum_comment_delete($comment) {
  _forum_update_forum_index($comment->nid);
}

/**
425
 * Implement hook_field_storage_pre_insert().
426
 */
427
function forum_field_storage_pre_insert($obj_type, $object, &$skip_fields) {
428
429
430
  if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {
    $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
    foreach ($object->taxonomy_forums as $language) {
431
      foreach ($language as $item) {
432
433
        $query->values(array(
          'nid' => $object->nid,
434
          'title' => $object->title[FIELD_LANGUAGE_NONE][0]['value'],
435
          'tid' => $item['tid'],
436
437
438
439
440
441
442
443
444
445
446
447
          'sticky' => $object->sticky,
          'created' => $object->created,
          'comment_count' => 0,
          'last_comment_timestamp' => $object->created,
        ));
      }
    }
    $query->execute();
  }
}

/**
448
 * Implement hook_field_storage_pre_update().
449
 */
450
function forum_field_storage_pre_update($obj_type, $object, &$skip_fields) {
451
452
453
454
455
456
457
458
459
460
461
462
463
464
  $first_call = &drupal_static(__FUNCTION__, array());

  if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {
    // We don't maintain data for old revisions, so clear all previous values
    // from the table. Since this hook runs once per field, per object, make
    // sure we only wipe values once.
    if (!isset($first_call[$object->nid])) {
      $first_call[$object->nid] = FALSE;
      db_delete('forum_index')->condition('nid', $object->nid)->execute();
    }
    // Only save data to the table if the node is published.
    if ($object->status) {
      $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
      foreach ($object->taxonomy_forums as $language) {
465
        foreach ($language as $item) {
466
467
          $query->values(array(
            'nid' => $object->nid,
468
            'title' => $object->title[FIELD_LANGUAGE_NONE][0]['value'],
469
            'tid' => $item['tid'],
470
471
472
473
474
            'sticky' => $object->sticky,
            'created' => $object->created,
            'comment_count' => 0,
            'last_comment_timestamp' => $object->created,
          ));
475
        }
476
477
478
479
480
      }
      $query->execute();
      // The logic for determining last_comment_count is fairly complex, so
      // call _forum_update_forum_index() too.
      _forum_update_forum_index($object->nid);
Dries's avatar
   
Dries committed
481
    }
482
483
  }
}
Dries's avatar
   
Dries committed
484

485
/**
486
 * Implement hook_form_alter().
487
 */
488
function forum_form_alter(&$form, $form_state, $form_id) {
489
  $vid = variable_get('forum_nav_vocabulary', 0);
490
491
492
  if (isset($form['vid']) && $form['vid']['#value'] == $vid) {
    // Hide critical options from forum vocabulary.
    if ($form_id == 'taxonomy_form_vocabulary') {
493
      $form['help_forum_vocab'] = array(
494
        '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
495
496
497
        '#weight' => -1,
      );
      $form['hierarchy'] = array('#type' => 'value', '#value' => 1);
498
      $form['delete']['#access'] = FALSE;
499
    }
500
501
502
    // Hide multiple parents select from forum terms.
    elseif ($form_id == 'taxonomy_form_term') {
      $form['advanced']['parent']['#access'] = FALSE;
503
    }
504
  }
505
  if ($form_id == 'forum_node_form') {
506
    $langcode = $form['taxonomy_forums']['#language'];
507
    // Make the vocabulary required for 'real' forum-nodes.
508
509
    $form['taxonomy_forums'][$langcode]['#required'] = TRUE;
    $form['taxonomy_forums'][$langcode]['#multiple'] = FALSE;
510
  }
511
512
}

513
/**
514
 * Implement hook_block_info().
515
 */
516
function forum_block_info() {
517
518
519
520
521
522
523
524
  $blocks['active'] = array(
    'info' => t('Active forum topics'),
    'cache' => DRUPAL_CACHE_CUSTOM,
  );
  $blocks['new'] = array(
    'info' => t('New forum topics'),
    'cache' => DRUPAL_CACHE_CUSTOM,
  );
525
526
527
528
  return $blocks;
}

/**
529
 * Implement hook_block_configure().
530
531
532
533
534
535
536
 */
function forum_block_configure($delta = '') {
  $form['forum_block_num_' . $delta] = array('#type' => 'select', '#title' => t('Number of topics'), '#default_value' => variable_get('forum_block_num_' . $delta, '5'), '#options' => drupal_map_assoc(array(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)));
  return $form;
}

/**
537
 * Implement hook_block_save().
538
539
540
541
542
543
 */
function forum_block_save($delta = '', $edit = array()) {
  variable_set('forum_block_num_' . $delta, $edit['forum_block_num_' . $delta]);
}

/**
544
 * Implement hook_block_view().
545
546
547
548
 *
 * Generates a block containing the currently active forum topics and the
 * most recently added forum topics.
 */
549
function forum_block_view($delta = '') {
550
551
  $query = db_select('forum_index', 'f')
    ->fields('f')
552
553
554
555
556
    ->addTag('node_access');
  switch ($delta) {
    case 'active':
      $title = t('Active forum topics');
      $query
557
        ->orderBy('f.last_comment_timestamp', 'DESC')
558
559
560
561
562
563
        ->range(0, variable_get('forum_block_num_active', '5'));
      break;

    case 'new':
      $title = t('New forum topics');
      $query
564
        ->orderBy('f.created', 'DESC')
565
566
567
        ->range(0, variable_get('forum_block_num_new', '5'));
      break;
  }
Dries's avatar
   
Dries committed
568

569
570
571
572
573
574
575
  $cache_keys = array_merge(array('forum', $delta), drupal_render_cid_parts());
  // Cache based on the altered query. Enables us to cache with node access enabled.
  $query->preExecute();
  $cache_keys[] = md5(serialize(array((string) $query, $query->getArguments())));

  $block['subject'] = $title;
  $block['content'] = array(
576
577
578
579
580
581
582
     '#access' => user_access('access content'),
     '#pre_render' => array('forum_block_view_pre_render'),
     '#cache' => array(
        'keys' => $cache_keys,
        'expire' => CACHE_TEMPORARY,
     ),
     '#query' => $query,
583
584
  );
  return $block;
585
}
586

587
588
589
590
591
592
593
594
595
596
597
598
/**
* A #pre_render callback. Lists nodes based on the element's #query property.
*
* @see forum_block_view().
*
* @return
*   A renderable array.
*/
function forum_block_view_pre_render($elements) {
  $result = $elements['#query']->execute();
  if ($node_title_list = node_title_list($result)) {
    $elements['forum_list'] = array('#markup' => $node_title_list);
599
    $elements['forum_more'] = array('#markup' => theme('more_link', array('url' => url('forum'), 'title' => t('Read the latest forum topics.'))));
600
  }
601
  return $elements;
Dries's avatar
   
Dries committed
602
603
}

604
/**
605
 * Implement hook_form().
606
 */
607
function forum_form($node, $form_state) {
608
  $type = node_type_get_type($node);
609

610
  if (!empty($node->nid)) {
611
612
    $forum_terms = $node->taxonomy_forums;
    // If editing, give option to leave shadows
613
    $shadow = (count($forum_terms) > 1);
614
    $form['shadow'] = array('#type' => 'checkbox', '#title' => t('Leave shadow copy'), '#default_value' => $shadow, '#description' => t('If you move this topic, you can leave a link in the old forum to the new forum.'));
615
    $form['forum_tid'] = array('#type' => 'value', '#value' => $node->forum_tid);
Dries's avatar
Dries committed
616
  }
Dries's avatar
   
Dries committed
617

618
619
620
  $form['#submit'][] = 'forum_submit';
  // Assign the forum topic submit handler.

621
  return $form;
Dries's avatar
   
Dries committed
622
623
}

624
/**
625
 * Implement hook_url_outbound_alter().
626
 */
627
628
629
630
631
632
633
function forum_url_outbound_alter(&$path, &$options, $original_path) {
  if (preg_match('!^taxonomy/term/(\d+)!', $path, $matches)) {
    $term = taxonomy_term_load($matches[1]);
    if ($term && $term->vocabulary_machine_name == 'forums') {
      $path = 'forum/' . $matches[1];
    }
  }
634
635
}

Dries's avatar
   
Dries committed
636
637
638
/**
 * Returns a list of all forums for a given taxonomy id
 *
639
 * Forum objects contain the following fields
Dries's avatar
   
Dries committed
640
641
642
643
644
645
646
647
648
 * -num_topics Number of topics in the forum
 * -num_posts Total number of posts in all topics
 * -last_post Most recent post for the forum
 *
 * @param $tid
 *   Taxonomy ID of the vocabulary that holds the forum list.
 * @return
 *   Array of object containing the forum information.
 */
Dries's avatar
   
Dries committed
649
function forum_get_forums($tid = 0) {
650

Dries's avatar
   
Dries committed
651
  $forums = array();
652
  $vid = variable_get('forum_nav_vocabulary', 0);
653
  $_forums = taxonomy_get_tree($vid, $tid);
Dries's avatar
   
Dries committed
654

Dries's avatar
   
Dries committed
655
  if (count($_forums)) {
656
657
    $query = db_select('node', 'n');
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
658
    $query->join('forum', 'f', 'n.vid = f.vid');
659
660
661
    $query->addExpression('COUNT(n.nid)', 'topic_count');
    $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
    $counts = $query
662
      ->fields('f', array('tid'))
663
664
665
666
667
      ->condition('status', 1)
      ->groupBy('tid')
      ->addTag('node_access')
      ->execute()
      ->fetchAllAssoc('tid');
Dries's avatar
   
Dries committed
668
  }
Dries's avatar
   
Dries committed
669

Dries's avatar
   
Dries committed
670
671
672
673
  foreach ($_forums as $forum) {
    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
      $forum->container = 1;
    }
Dries's avatar
   
Dries committed
674

675
    if (!empty($counts[$forum->tid])) {
Dries's avatar
   
Dries committed
676
677
678
679
680
681
682
683
      $forum->num_topics = $counts[$forum->tid]->topic_count;
      $forum->num_posts = $counts[$forum->tid]->topic_count + $counts[$forum->tid]->comment_count;
    }
    else {
      $forum->num_topics = 0;
      $forum->num_posts = 0;
    }

684
685
    $query = db_select('node', 'n');
    $query->join('users', 'u1', 'n.uid = u1.uid');
686
    $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid));
687
688
689
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
    $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid');
    $query->addExpression('IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name)', 'last_comment_name');
690

691
692
693
694
695
696
697
698
    $topic = $query
      ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid'))
      ->condition('n.status', 1)
      ->orderBy('last_comment_timestamp', 'DESC')
      ->range(0, 1)
      ->addTag('node_access')
      ->execute()
      ->fetchObject();
699

700
    $last_post = new stdClass();
701
    if (!empty($topic->last_comment_timestamp)) {
702
      $last_post->created = $topic->last_comment_timestamp;
703
704
705
      $last_post->name = $topic->last_comment_name;
      $last_post->uid = $topic->last_comment_uid;
    }
Dries's avatar
   
Dries committed
706
    $forum->last_post = $last_post;
Dries's avatar
   
Dries committed
707

Dries's avatar
   
Dries committed
708
709
710
711
    $forums[$forum->tid] = $forum;
  }

  return $forums;
Dries's avatar
   
Dries committed
712
713
}

714
715
716
717
718
/**
 * Calculate the number of nodes the user has not yet read and are newer
 * than NODE_NEW_LIMIT.
 */
function _forum_topics_unread($term, $uid) {
719
  $query = db_select('node', 'n');
720
  $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term));
721
722
723
724
725
726
727
728
729
  $query->join('history', 'h', 'n.nid = h.nid AND h.uid = :uid', array(':uid' => $uid));
  $query->addExpression('COUNT(n.nid)', 'count');
  return $query
    ->condition('status', 1)
    ->condition('n.created', NODE_NEW_LIMIT, '>')
    ->isNull('h.nid')
    ->addTag('node_access')
    ->execute()
    ->fetchField();
Dries's avatar
   
Dries committed
730
731
}

Dries's avatar
Dries committed
732
function forum_get_topics($tid, $sortby, $forum_per_page) {
Dries's avatar
   
Dries committed
733
  global $user, $forum_topic_list_header;
Dries's avatar
   
Dries committed
734

Dries's avatar
   
Dries committed
735
  $forum_topic_list_header = array(
736
    NULL,
737
738
739
    array('data' => t('Topic'), 'field' => 'f.title'),
    array('data' => t('Replies'), 'field' => 'f.comment_count'),
    array('data' => t('Last reply'), 'field' => 'f.last_comment_timestamp'),
Dries's avatar
   
Dries committed
740
  );
Dries's avatar
   
Dries committed
741

Dries's avatar
   
Dries committed
742
  $order = _forum_get_topic_order($sortby);
Dries's avatar
   
Dries committed
743
  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
Dries's avatar
   
Dries committed
744
745
    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
      $forum_topic_list_header[$i]['sort'] = $order['sort'];
Dries's avatar
   
Dries committed
746
747
748
    }
  }

749
750
  $query = db_select('forum_index', 'f')->extend('PagerDefault')->extend('TableSort');
  $query->fields('f');
751
  $query
752
    ->condition('f.tid', $tid)
753
    ->addTag('node_access')
754
    ->orderBy('f.sticky', 'DESC')
755
    ->orderByHeader($forum_topic_list_header)
756
    ->orderBy('f.last_comment_timestamp', 'DESC')
757
    ->limit($forum_per_page);
758

759
760
  $count_query = db_select('forum_index', 'f');
  $count_query->condition('f.tid', $tid);
761
  $count_query->addExpression('COUNT(*)');
762
  $count_query->addTag('node_access');
763

764
765
  $query->setCountQuery($count_query);
  $result = $query->execute();
766
767
768
769
770
771
772
773
774
775
776
  $nids = array();
  foreach ($result as $record) {
    $nids[] = $record->nid;
  }
  if ($nids) {
    $result = db_query("SELECT n.title, n.nid, n.sticky, n.created, n.uid, n.comment AS comment_mode, ncs.*, f.tid AS forum_tid, u.name, IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name) AS last_comment_name FROM {node} n INNER JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid INNER JOIN {forum} f ON n.vid = f.vid INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {users} u2 ON ncs.last_comment_uid = u2.uid WHERE n.nid IN (:nids)", array(':nids' => $nids));
  }
  else {
    $result = array();
  }

777
  $topics = array();
778
  foreach ($result as $topic) {
Dries's avatar
Dries committed
779
780
    if ($user->uid) {
      // folder is new if topic is new or there are new comments since last visit
781
      if ($topic->forum_tid != $tid) {
Dries's avatar
Dries committed
782
        $topic->new = 0;
Dries's avatar
   
Dries committed
783
784
      }
      else {
Dries's avatar
   
Dries committed
785
        $history = _forum_user_last_visit($topic->nid);
Dries's avatar
   
Dries committed
786
        $topic->new_replies = comment_num_new($topic->nid, $history);
787
        $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history);
Dries's avatar
   
Dries committed
788
      }
789
790
    }
    else {
791
      // Do not track "new replies" status for topics if the user is anonymous.
Dries's avatar
Dries committed
792
793
      $topic->new_replies = 0;
      $topic->new = 0;
794
    }
Dries's avatar
   
Dries committed
795

796
    if ($topic->comment_count > 0) {
797
      $last_reply = new stdClass();
798
      $last_reply->created = $topic->last_comment_timestamp;
Dries's avatar
   
Dries committed
799
800
801
802
      $last_reply->name = $topic->last_comment_name;
      $last_reply->uid = $topic->last_comment_uid;
      $topic->last_reply = $last_reply;
    }
Dries's avatar
   
Dries committed
803
804
805
    $topics[] = $topic;
  }

Dries's avatar
Dries committed
806
  return $topics;
Dries's avatar
   
Dries committed
807
808
}

Dries's avatar
   
Dries committed
809
/**
810
 * Process variables for forums.tpl.php
811
812
813
814
815
816
817
818
 *
 * The $variables array contains the following arguments:
 * - $forums
 * - $topics
 * - $parents
 * - $tid
 * - $sortby
 * - $forum_per_page
Dries's avatar
   
Dries committed
819
 *
820
 * @see forums.tpl.php
Dries's avatar
   
Dries committed
821
 */
822
function template_preprocess_forums(&$variables) {
Dries's avatar
   
Dries committed
823
  global $user;
Dries's avatar
   
Dries committed
824

825
  $vid = variable_get('forum_nav_vocabulary', 0);
826
  $vocabulary = taxonomy_vocabulary_load($vid);
827
  $title = !empty($vocabulary->name) ? $vocabulary->name : '';
Dries's avatar
   
Dries committed
828

Dries's avatar
   
Dries committed
829
  // Breadcrumb navigation:
830
  $breadcrumb[] = l(t('Home'), NULL);
831
  if ($variables['tid']) {
832
    $breadcrumb[] = l($vocabulary->name, 'forum');
Dries's avatar
   
Dries committed
833
  }
834
835
836
837
  if ($variables[