forum.module 39.5 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
      $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. The <a href="@create-topic">forum topic</a> menu item (under <em>Add new content</em> on the Navigation menu) creates the initial post of a new threaded discussion, or thread.', array('@create-topic' => url('node/add/forum'))) . '</p>';
16 17 18 19
      $output .= '<p>' . t('A threaded discussion occurs as people leave comments on a forum topic (or on other comments within that topic). A forum topic is contained within a forum, which may hold many similar or related forum topics. Forums are (optionally) nested within a container, which may hold many similar or related forums. Both containers and forums may be nested within other containers and forums, and provide structure for your message board. By carefully planning this structure, you make it easier for users to find and comment on a specific forum topic.') . '</p>';
      $output .= '<p>' . t('When administering a forum, note that:') . '</p>';
      $output .= '<ul><li>' . t('a forum topic (and all of its comments) may be moved between forums by selecting a different forum while editing a forum topic.') . '</li>';
      $output .= '<li>' . t('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.') . '</li>';
20
      $output .= '<li>' . t('selecting <em>Closed</em> under <em>Comment settings</em> while editing a forum topic will lock (prevent new comments) on the thread.') . '</li>';
21 22
      $output .= '<li>' . t('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.') . '</li></ul>';
      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@forum">Forum module</a>.', array('@forum' => 'http://drupal.org/handbook/modules/forum/')) . '</p>';
23
      return $output;
24
    case 'admin/structure/forum':
25
      return '<p>' . t('This page displays a list of existing forums and containers. Containers (optionally) hold forums, and forums hold forum topics (a forum topic is the initial post to a threaded discussion). To provide structure, both containers and forums may be placed inside other containers and forums.') . '</p>';
26
    case 'admin/structure/forum/add/container':
27
      return '<p>' . t('By grouping related or similar forums, containers help organize forums. For example, a container named "Food" may hold two forums named "Fruit" and "Vegetables", respectively.') . '</p>';
28
    case 'admin/structure/forum/add/forum':
29
      return '<p>' . t('A forum holds related or similar forum topics (a forum topic is the initial post to a threaded discussion). For example, a forum named "Fruit" may contain forum topics titled "Apples" and "Bananas", respectively.') . '</p>';
30
    case 'admin/structure/forum/settings':
31
      return '<p>' . t('These settings allow you to adjust the display of your forum topics. The content types available for use within a forum may be selected by editing the <em>Content types</em> on the <a href="@forum-vocabulary">forum vocabulary page</a>.', array('@forum-vocabulary' => url('admin/structure/taxonomy/' . variable_get('forum_nav_vocabulary', 0) . '/edit'))) . '</p>';
Dries's avatar
 
Dries committed
32 33 34
  }
}

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

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

137 138

/**
139
 * Implement hook_init().
140
 */
141
function forum_init() {
142
  drupal_add_css(drupal_get_path('module', 'forum') . '/forum.css');
143
}
144

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

158
  return is_array($field);
159
}
160

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

180 181 182
    }
  }
}
183

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

/**
197
 * Implement hook_node_validate().
198
 *
199 200
 * Check in particular that only a "leaf" term in the associated taxonomy.
 */
201
function forum_node_validate($node, $form) {
202 203
  if (_forum_node_check_node_type($node)) {
    $langcode = $form['taxonomy_forums']['#language'];
204
    // vocabulary is selected, not a "container" term.
205
    if (!empty($node->taxonomy_forums[$langcode])) {
206 207
      // Extract the node's proper topic ID.
      $containers = variable_get('forum_containers', array());
208 209 210 211 212
      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,
213
        ))->fetchField();
214 215
        if ($used && in_array($term->tid, $containers)) {
          form_set_error('taxonomy_forums', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name)));
216 217
        }
      }
218 219 220
    }
  }
}
221

222
/**
223
 * Implement hook_node_presave().
224 225 226
 *
 * Assign forum taxonomy when adding a topic from within a forum.
 */
227
function forum_node_presave($node) {
228
  if (_forum_node_check_node_type($node)) {
229 230
    // Make sure all fields are set properly:
    $node->icon = !empty($node->icon) ? $node->icon : '';
231 232 233
    $langcode = array_shift(array_keys($node->taxonomy_forums));
    if (!empty($node->taxonomy_forums[$langcode])) {
      $node->forum_tid = $node->taxonomy_forums[$langcode][0]['value'];
234
      $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();
235
      if ($old_tid && isset($node->forum_tid) && ($node->forum_tid != $old_tid) && !empty($node->shadow)) {
236
        // A shadow copy needs to be created. Retain new term and add old term.
237
        $node->taxonomy_forums[$langcode][] = array('value' => $old_tid);
238 239 240 241
      }
    }
  }
}
242

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

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

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

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

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

358
/**
359
 * Implement hook_permission().
360
 */
361
function forum_permission() {
362
  $perms = array(
363 364 365 366
    'administer forums' => array(
      'title' => t('Administer forums'),
      'description' => t('Manage forums and configure forum administration settings.'),
    ),
367 368
  );
  return $perms;
Dries's avatar
 
Dries committed
369
}
Dries's avatar
 
Dries committed
370

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

384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
/**
 * 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);
  }
}

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

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

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

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

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

/**
527
 * Implement hook_block_configure().
528 529 530 531 532 533 534
 */
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;
}

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

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

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

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

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

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

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

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

619
  return $form;
Dries's avatar
 
Dries committed
620 621
}

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

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

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

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

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

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

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

689 690 691 692 693 694 695 696
    $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();
697

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

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

  return $forums;
Dries's avatar
 
Dries committed
710 711
}

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

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

Dries's avatar
 
Dries committed
733
  $forum_topic_list_header = array(
734
    NULL,
735 736 737
    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
738
  );
Dries's avatar
 
Dries committed
739

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

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

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

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