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

Dries Buytaert's avatar
   
Dries Buytaert committed
4
5
/**
 * @file
6
 * Provides discussion forums.
Dries Buytaert's avatar
   
Dries Buytaert 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 Buytaert's avatar
   
Dries Buytaert 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]['value'] =  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
213
214
215
      foreach ($node->taxonomy_forums[$langcode] as $tid) {
        $term = taxonomy_term_load($tid['value']);
        $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
236
    $langcode = array_shift(array_keys($node->taxonomy_forums));
    if (!empty($node->taxonomy_forums[$langcode])) {
      $node->forum_tid = $node->taxonomy_forums[$langcode][0]['value'];
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('value' => $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 Buytaert's avatar
   
Dries Buytaert committed
359
360
}

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

374
/**
375
 * Implement hook_taxonomy().
376
 */
377
378
379
380
381
382
383
384
385
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);
}
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
411
/**
 * 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);
  }
}

/**
412
 * Implements forum_comment_unpublish().
413
414
415
416
417
418
419
420
421
422
423
424
425
 */
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);
}

/**
426
 * Implement hook_field_storage_pre_insert().
427
 */
428
function forum_field_storage_pre_insert($obj_type, $object, &$skip_fields) {
429
430
431
432
433
434
  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) {
      foreach ($language as $delta) {
        $query->values(array(
          'nid' => $object->nid,
435
          'title' => $object->title[FIELD_LANGUAGE_NONE][0]['value'],
436
437
438
439
440
441
442
443
444
445
446
447
448
          'tid' => $delta['value'],
          'sticky' => $object->sticky,
          'created' => $object->created,
          'comment_count' => 0,
          'last_comment_timestamp' => $object->created,
        ));
      }
    }
    $query->execute();
  }
}

/**
449
 * Implement hook_field_storage_pre_update().
450
 */
451
function forum_field_storage_pre_update($obj_type, $object, &$skip_fields) {
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
  $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) {
        foreach ($language as $delta) {
          $query->values(array(
            'nid' => $object->nid,
469
            'title' => $object->title[FIELD_LANGUAGE_NONE][0]['value'],
470
471
472
473
474
475
            'tid' => $delta['value'],
            'sticky' => $object->sticky,
            'created' => $object->created,
            'comment_count' => 0,
            'last_comment_timestamp' => $object->created,
          ));
476
        }
477
478
479
480
481
      }
      $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 Buytaert's avatar
   
Dries Buytaert committed
482
    }
483
484
  }
}
Dries Buytaert's avatar
   
Dries Buytaert committed
485

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

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

/**
530
 * Implement hook_block_configure().
531
532
533
534
535
536
537
 */
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;
}

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

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

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

570
571
572
573
574
575
576
  $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(
577
578
579
580
581
582
583
     '#access' => user_access('access content'),
     '#pre_render' => array('forum_block_view_pre_render'),
     '#cache' => array(
        'keys' => $cache_keys,
        'expire' => CACHE_TEMPORARY,
     ),
     '#query' => $query,
584
585
  );
  return $block;
586
}
587

588
589
590
591
592
593
594
595
596
597
598
599
/**
* 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);
600
    $elements['forum_more'] = array('#markup' => theme('more_link', array('url' => url('forum'), 'title' => t('Read the latest forum topics.'))));
601
  }
602
  return $elements;
Dries Buytaert's avatar
   
Dries Buytaert committed
603
604
}

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

611
  if (!empty($node->nid)) {
612
613
    $forum_terms = $node->taxonomy_forums;
    // If editing, give option to leave shadows
614
    $shadow = (count($forum_terms) > 1);
615
    $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.'));
616
    $form['forum_tid'] = array('#type' => 'value', '#value' => $node->forum_tid);
Dries Buytaert's avatar
Dries Buytaert committed
617
  }
Dries Buytaert's avatar
   
Dries Buytaert committed
618

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

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

625
/**
626
 * Implement hook_url_outbound_alter().
627
 */
628
629
630
631
632
633
634
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];
    }
  }
635
636
}

Dries Buytaert's avatar
   
Dries Buytaert committed
637
638
639
/**
 * Returns a list of all forums for a given taxonomy id
 *
640
 * Forum objects contain the following fields
Dries Buytaert's avatar
   
Dries Buytaert committed
641
642
643
644
645
646
647
648
649
 * -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 Buytaert's avatar
   
Dries Buytaert committed
650
function forum_get_forums($tid = 0) {
651

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

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

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

676
    if (!empty($counts[$forum->tid])) {
Dries Buytaert's avatar
   
Dries Buytaert committed
677
678
679
680
681
682
683
684
      $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;
    }

685
686
    $query = db_select('node', 'n');
    $query->join('users', 'u1', 'n.uid = u1.uid');
687
    $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid));
688
689
690
    $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');
691

692
693
694
695
696
697
698
699
    $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();
700

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

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

  return $forums;
Dries Buytaert's avatar
   
Dries Buytaert committed
713
714
}

715
716
717
718
719
/**
 * Calculate the number of nodes the user has not yet read and are newer
 * than NODE_NEW_LIMIT.
 */
function _forum_topics_unread($term, $uid) {
720
  $query = db_select('node', 'n');
721
  $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term));
722
723
724
725
726
727
728
729
730
  $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 Buytaert's avatar
   
Dries Buytaert committed
731
732
}

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

Dries Buytaert's avatar
   
Dries Buytaert committed
736
  $forum_topic_list_header = array(
737
    NULL,
738
739
740
    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 Buytaert's avatar
   
Dries Buytaert committed
741
  );
Dries Buytaert's avatar
   
Dries Buytaert committed
742

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

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

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

765
766
  $query->setCountQuery($count_query);
  $result = $query->execute();
767
768
769
770
771
772
773
774
775
776
777
  $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();
  }

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

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

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

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

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

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