forum.module 38.3 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 31
    case 'admin/structure/forum/settings':
      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/edit/vocabulary/' . variable_get('forum_nav_vocabulary', 0)))) . '</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 43 44
      'arguments' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
    ),
    'forum_list' => array(
45
      'template' => 'forum-list',
46 47 48
      'arguments' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
    ),
    'forum_topic_list' => array(
49
      'template' => 'forum-topic-list',
50 51 52
      'arguments' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
    ),
    'forum_icon' => array(
53
      'template' => 'forum-icon',
54 55 56
      'arguments' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0),
    ),
    'forum_topic_navigation' => array(
57
      'template' => 'forum-topic-navigation',
58 59
      'arguments' => array('node' => NULL),
    ),
60
    'forum_submitted' => array(
61
      'template' => 'forum-submitted',
62 63
      'arguments' => array('topic' => NULL),
    ),
64 65 66
  );
}

67 68 69 70 71 72 73 74 75 76
/**
 * Fetch a forum term.
 *
 * @param $tid
 *   The ID of the term which should be loaded.
 *
 * @return
 *   An associative array containing the term data or FALSE if the term cannot be loaded, or is not part of the forum vocabulary.
 */
function forum_term_load($tid) {
77 78 79 80 81 82 83
  return db_select('taxonomy_term_data', 't')
    ->fields('t', array('tid', 'vid', 'name', 'description', 'weight'))
    ->condition('tid', $tid)
    ->condition('vid', variable_get('forum_nav_vocabulary', 0))
    ->addTag('term_access')
    ->execute()
    ->fetchAssoc();
84 85
}

86
/**
87
 * Implement hook_menu().
88
 */
89 90
function forum_menu() {
  $items['forum'] = array(
91
    'title' => 'Forums',
92 93 94
    'page callback' => 'forum_page',
    'access arguments' => array('access content'),
  );
95
  $items['admin/structure/forum'] = array(
96 97
    'title' => 'Forums',
    'description' => 'Control forums and their hierarchy and change forum settings.',
98 99
    'page callback' => 'drupal_get_form',
    'page arguments' => array('forum_overview'),
100 101
    'access arguments' => array('administer forums'),
  );
102
  $items['admin/structure/forum/list'] = array(
103
    'title' => 'List',
104 105 106
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
107
  $items['admin/structure/forum/add/container'] = array(
108
    'title' => 'Add container',
109 110
    'page callback' => 'forum_form_main',
    'page arguments' => array('container'),
111
    'access arguments' => array('administer forums'),
112
    'type' => MENU_LOCAL_TASK,
113
    'parent' => 'admin/structure/forum',
114
  );
115
  $items['admin/structure/forum/add/forum'] = array(
116
    'title' => 'Add forum',
117 118
    'page callback' => 'forum_form_main',
    'page arguments' => array('forum'),
119
    'access arguments' => array('administer forums'),
120
    'type' => MENU_LOCAL_TASK,
121
    'parent' => 'admin/structure/forum',
122
  );
123
  $items['admin/structure/forum/settings'] = array(
124
    'title' => 'Settings',
125 126
    'page callback' => 'drupal_get_form',
    'page arguments' => array('forum_admin_settings'),
127
    'access arguments' => array('administer forums'),
128 129
    'weight' => 5,
    'type' => MENU_LOCAL_TASK,
130
    'parent' => 'admin/structure/forum',
131
  );
132
  $items['admin/structure/forum/edit/%forum_term'] = array(
133
    'page callback' => 'forum_form_main',
134
    'access arguments' => array('administer forums'),
135 136
    'type' => MENU_CALLBACK,
  );
137
  $items['admin/structure/forum/edit/container/%forum_term'] = array(
138
    'title' => 'Edit container',
139
    'page callback' => 'forum_form_main',
140
    'page arguments' => array('container', 5),
141
    'access arguments' => array('administer forums'),
142
    'type' => MENU_CALLBACK,
143
  );
144
  $items['admin/structure/forum/edit/forum/%forum_term'] = array(
145
    'title' => 'Edit forum',
146 147
    'page callback' => 'forum_form_main',
    'page arguments' => array('forum', 5),
148
    'access arguments' => array('administer forums'),
149
    'type' => MENU_CALLBACK,
150 151 152
  );
  return $items;
}
153

154 155

/**
156
 * Implement hook_init().
157
 */
158
function forum_init() {
159
  drupal_add_css(drupal_get_path('module', 'forum') . '/forum.css');
160
}
161

162
/**
163
 * _forum_node_check_node_type
164 165
 *
 * @param mixed $node
166 167 168
 * @param mixed $vocabulary
 * @access protected
 * @return bool
169
 */
170
function _forum_node_check_node_type($node, $vocabulary) {
171
  // We are going to return if $node->type is not one of the node
172
  // types assigned to the forum vocabulary. If forum_nav_vocabulary
173 174 175
  // is undefined or the vocabulary does not exist, it clearly cannot
  // be assigned to $node->type, so return to avoid E_ALL warnings.
  if (empty($vocabulary)) {
176
    return FALSE;
177
  }
178 179 180

  // Operate only on node types assigned for the forum vocabulary.
  if (!in_array($node->type, $vocabulary->nodes)) {
181
    return FALSE;
182 183
  }

184 185
  return TRUE;
}
186

187
/**
188
 * Implement hook_node_view().
189
 */
190
function forum_node_view($node, $build_mode) {
191
  $vid = variable_get('forum_nav_vocabulary', 0);
192
  $vocabulary = taxonomy_vocabulary_load($vid);
193
  if (_forum_node_check_node_type($node, $vocabulary)) {
194
    if ((bool)menu_get_object() && taxonomy_node_get_terms_by_vocabulary($node, $vid) && $tree = taxonomy_get_tree($vid)) {
195 196 197 198 199 200 201
      // Get the forum terms from the (cached) tree
      foreach ($tree as $term) {
        $forum_terms[] = $term->tid;
      }
      foreach ($node->taxonomy as $term_id => $term) {
        if (in_array($term_id, $forum_terms)) {
          $node->tid = $term_id;
202 203
        }
      }
204 205 206 207 208 209 210 211 212 213
      // Breadcrumb navigation
      $breadcrumb[] = l(t('Home'), NULL);
      $breadcrumb[] = l($vocabulary->name, 'forum');
      if ($parents = taxonomy_get_parents_all($node->tid)) {
        $parents = array_reverse($parents);
        foreach ($parents as $p) {
          $breadcrumb[] = l($p->name, 'forum/' . $p->tid);
        }
      }
      drupal_set_breadcrumb($breadcrumb);
214

215
      if ($build_mode == 'full') {
216 217 218 219
        $node->content['forum_navigation'] = array(
          '#markup' => theme('forum_topic_navigation', $node),
          '#weight' => 100,
        );
220
      }
221 222 223
    }
  }
}
224

225
/**
226
 * Implement hook_node_prepare().
227
 */
228
function forum_node_prepare($node) {
229
  $vid = variable_get('forum_nav_vocabulary', 0);
230
  $vocabulary = taxonomy_vocabulary_load($vid);
231
  if (_forum_node_check_node_type($node, $vocabulary)) {
232 233
    if (empty($node->nid)) {
      // New topic
234 235 236 237
      $node->taxonomy[arg(3)] = (object) array(
        'vid' => $vid,
        'tid' => arg(3),
      );
238 239 240 241 242
    }
  }
}

/**
243
 * Implement hook_node_validate().
244
 *
245 246
 * Check in particular that only a "leaf" term in the associated taxonomy.
 */
247
function forum_node_validate($node, $form) {
248
  $vid = variable_get('forum_nav_vocabulary', 0);
249
  $vocabulary = taxonomy_vocabulary_load($vid);
250
  if (_forum_node_check_node_type($node, $vocabulary)) {
251
    // vocabulary is selected, not a "container" term.
252 253 254 255 256
    if ($node->taxonomy) {
      // Extract the node's proper topic ID.
      $vocabulary = $vid;
      $containers = variable_get('forum_containers', array());
      foreach ($node->taxonomy as $term) {
257 258 259 260 261 262 263
        $used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid', array(
          ':tid' => $term,
          ':vid' => $vocabulary,
        ), 0, 1)->fetchField();
        if ($used && in_array($term, $containers)) {
          $term = taxonomy_term_load($term);
          form_set_error('taxonomy', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name)));
264 265
        }
      }
266 267 268
    }
  }
}
269

270
/**
271
 * Implement hook_node_presave().
272 273 274
 *
 * Assign forum taxonomy when adding a topic from within a forum.
 */
275
function forum_node_presave($node) {
276
  $vid = variable_get('forum_nav_vocabulary', 0);
277
  $vocabulary = taxonomy_vocabulary_load($vid);
278
  if (_forum_node_check_node_type($node, $vocabulary)) {
279 280
    // Make sure all fields are set properly:
    $node->icon = !empty($node->icon) ? $node->icon : '';
281

282 283 284 285
    if ($node->taxonomy && $tree = taxonomy_get_tree($vid)) {
      // Get the forum terms from the (cached) tree if we have a taxonomy.
      foreach ($tree as $term) {
        $forum_terms[] = $term->tid;
286
      }
287 288 289
      foreach ($node->taxonomy as $term_id) {
        if (in_array($term_id, $forum_terms)) {
          $node->tid = $term_id;
290
        }
291
      }
292
      $old_tid = db_query_range("SELECT t.tid FROM {taxonomy_term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.nid = :nid ORDER BY t.vid DESC", array(':nid' => $node->nid), 0, 1)->fetchField();
293 294 295 296 297 298 299
      if ($old_tid && isset($node->tid) && ($node->tid != $old_tid) && !empty($node->shadow)) {
        // A shadow copy needs to be created. Retain new term and add old term.
        $node->taxonomy[] = $old_tid;
      }
    }
  }
}
300

301
/**
302
 * Implement hook_node_update().
303
 */
304
function forum_node_update($node) {
305
  $vid = variable_get('forum_nav_vocabulary', 0);
306
  $vocabulary = taxonomy_vocabulary_load($vid);
307
  if (_forum_node_check_node_type($node, $vocabulary)) {
308
    if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
309
      if (!empty($node->tid)) {
310 311 312 313
        db_update('forum')
          ->fields(array('tid' => $node->tid))
          ->condition('vid', $node->vid)
          ->execute();
314 315 316
      }
      // The node is removed from the forum.
      else {
317 318 319
        db_delete('forum')
          ->condition('nid', $node->nid)
          ->execute();
320 321 322
      }
    }
    else {
323
      if (!empty($node->tid)) {
324 325 326 327 328 329 330
        db_insert('forum')
          ->fields(array(
            'tid' => $node->tid,
            'vid' => $node->vid,
            'nid' => $node->nid,
          ))
          ->execute();
331
      }
332 333 334
    }
  }
}
335

336
/**
337
 * Implement hook_node_insert().
338
 */
339
function forum_node_insert($node) {
340
  $vid = variable_get('forum_nav_vocabulary', 0);
341
  $vocabulary = taxonomy_vocabulary_load($vid);
342
  if (_forum_node_check_node_type($node, $vocabulary)) {
343
    if (!empty($node->tid)) {
344 345 346 347 348 349 350
      $nid = db_insert('forum')
        ->fields(array(
          'tid' => $node->tid,
          'vid' => $node->vid,
          'nid' => $node->nid,
        ))
        ->execute();
351 352 353
    }
  }
}
354

355
/**
356
 * Implement hook_node_delete().
357
 */
358
function forum_node_delete($node) {
359
  $vid = variable_get('forum_nav_vocabulary', 0);
360
  $vocabulary = taxonomy_vocabulary_load($vid);
361
  if (_forum_node_check_node_type($node, $vocabulary)) {
362 363 364
    db_delete('forum')
      ->condition('nid', $node->nid)
      ->execute();
365
  }
366
}
367

368
/**
369
 * Implement hook_node_load().
370
 */
371
function forum_node_load($nodes, $types) {
372
  $vid = variable_get('forum_nav_vocabulary', 0);
373 374 375 376
  // If no forum vocabulary is set up, return.
  if ($vid == '') {
    return;
  }
377
  $vocabulary = taxonomy_vocabulary_load($vid);
378 379 380 381 382 383 384 385

  $node_vids = array();
  foreach ($nodes as $node) {
    if (isset($vocabulary->nodes[$node->type])) {
      $node_vids[] = $node->vid;
    }
  }
  if (!empty($node_vids)) {
386
    $result = db_query('SELECT nid, tid FROM {forum} WHERE vid IN(:node_vids)', array(':node_vids' => $node_vids));
387 388 389
    foreach ($result as $record) {
      $nodes[$record->nid]->forum_tid = $record->tid;
    }
390
  }
391 392
}

393
/**
394
 * Implement hook_node_info().
395
 */
396
function forum_node_info() {
397 398
  return array(
    'forum' => array(
399
      'name' => t('Forum topic'),
400
      'base' => 'forum',
401
      'description' => t('A <em>forum topic</em> is the initial post to a new discussion thread within a forum.'),
402 403 404
      'title_label' => t('Subject'),
    )
  );
Dries's avatar
 
Dries committed
405 406
}

407
/**
408
 * Implement hook_permission().
409
 */
410
function forum_permission() {
411
  $perms = array(
412 413 414 415
    'administer forums' => array(
      'title' => t('Administer forums'),
      'description' => t('Manage forums and configure forum administration settings.'),
    ),
416 417
  );
  return $perms;
Dries's avatar
 
Dries committed
418
}
Dries's avatar
 
Dries committed
419

420
/**
421
 * Implement hook_taxonomy().
422
 */
423
function forum_taxonomy($op, $type, $term = NULL) {
424
  if ($op == 'delete' && $term['vid'] == variable_get('forum_nav_vocabulary', 0)) {
425 426
    switch ($type) {
      case 'term':
427 428
        $result = db_query('SELECT tn.nid FROM {taxonomy_term_node} tn WHERE tn.tid = :tid', array(':tid' => $term['tid']));
        foreach ($result as $node) {
429 430 431 432 433 434
          // node_delete will also remove any association with non-forum vocabularies.
          node_delete($node->nid);
        }

        // For containers, remove the tid from the forum_containers variable.
        $containers = variable_get('forum_containers', array());
435 436
        $key = array_search($term['tid'], $containers);
        if ($key !== FALSE) {
437 438 439 440 441 442
          unset($containers[$key]);
        }
        variable_set('forum_containers', $containers);
        break;
      case 'vocabulary':
        variable_del('forum_nav_vocabulary');
Dries's avatar
 
Dries committed
443
    }
444 445
  }
}
Dries's avatar
 
Dries committed
446

447
/**
448
 * Implement hook_form_alter().
449
 */
450
function forum_form_alter(&$form, $form_state, $form_id) {
451
  $vid = variable_get('forum_nav_vocabulary', 0);
452 453 454
  if (isset($form['vid']) && $form['vid']['#value'] == $vid) {
    // Hide critical options from forum vocabulary.
    if ($form_id == 'taxonomy_form_vocabulary') {
455
      $form['help_forum_vocab'] = array(
456
        '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
457 458
        '#weight' => -1,
      );
459
      $form['content_types']['nodes']['#required'] = TRUE;
460
      $form['hierarchy'] = array('#type' => 'value', '#value' => 1);
461 462 463 464
      $form['settings']['required'] = array('#type' => 'value', '#value' => FALSE);
      $form['settings']['relations'] = array('#type' => 'value', '#value' => FALSE);
      $form['settings']['tags'] = array('#type' => 'value', '#value' => FALSE);
      $form['settings']['multiple'] = array('#type' => 'value', '#value' => FALSE);
465
      unset($form['delete']);
466
    }
467 468 469
    // Hide multiple parents select from forum terms.
    elseif ($form_id == 'taxonomy_form_term') {
      $form['advanced']['parent']['#access'] = FALSE;
470
    }
471
  }
472 473
  if ($form_id == 'forum_node_form') {
    // Make the vocabulary required for 'real' forum-nodes.
474
    $vid = variable_get('forum_nav_vocabulary', 0);
475 476 477
    $form['taxonomy'][$vid]['#required'] = TRUE;
    $form['taxonomy'][$vid]['#options'][''] = t('- Please choose -');
  }
478 479
}

480
/**
481
 * Implement hook_block_list().
482 483 484 485 486 487 488 489
 */
function forum_block_list() {
  $blocks['active']['info'] = t('Active forum topics');
  $blocks['new']['info'] = t('New forum topics');
  return $blocks;
}

/**
490
 * Implement hook_block_configure().
491 492 493 494 495 496 497
 */
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;
}

/**
498
 * Implement hook_block_save().
499 500 501 502 503 504
 */
function forum_block_save($delta = '', $edit = array()) {
  variable_set('forum_block_num_' . $delta, $edit['forum_block_num_' . $delta]);
}

/**
505
 * Implement hook_block_view().
506 507 508 509
 *
 * Generates a block containing the currently active forum topics and the
 * most recently added forum topics.
 */
510 511
function forum_block_view($delta = '') {
  if (user_access('access content')) {
512 513 514 515 516 517 518 519 520 521
    $query = db_select('node', 'n');
    $query->join('taxonomy_term_node', 'tn', 'tn.vid = n.vid');
    $query->join('taxonomy_term_data', 'td', 'td.tid = tn.tid');
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
    $query
      ->fields('n', array('nid', 'title'))
      ->fields('ncs', array('comment_count', 'last_comment_timestamp'))
      ->condition('n.status', 1)
      ->condition('td.vid', variable_get('forum_nav_vocabulary', 0))
      ->addTag('node_access');
522 523 524
    switch ($delta) {
      case 'active':
        $title = t('Active forum topics');
525 526 527
        $query
          ->orderBy('ncs.last_comment_timestamp', 'DESC')
          ->range(0, variable_get('forum_block_num_active', '5'));
528
        break;
Dries's avatar
 
Dries committed
529

530 531
      case 'new':
        $title = t('New forum topics');
532 533 534
        $query
          ->orderBy('n.nid', 'DESC')
          ->range(0, variable_get('forum_block_num_new', '5'));
535 536 537
        break;
    }

538 539
    $result = $query->execute();
    $content = node_title_list($result);
540 541 542 543 544
    if (!empty($content)) {
      $block['subject'] = $title;
      $block['content'] = $content . theme('more_link', url('forum'), t('Read the latest forum topics.'));
      return $block;
    }
545
  }
Dries's avatar
 
Dries committed
546 547
}

548
/**
549
 * Implement hook_form().
550
 */
551
function forum_form($node, $form_state) {
552
  $type = node_type_get_type($node);
553
  $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => !empty($node->title) ? $node->title : '', '#required' => TRUE, '#weight' => -5);
554

555
  if (!empty($node->nid)) {
556
    $vid = variable_get('forum_nav_vocabulary', 0);
557
    $forum_terms = taxonomy_node_get_terms_by_vocabulary($node, $vid);
Dries's avatar
Dries committed
558
    // if editing, give option to leave shadows
559
    $shadow = (count($forum_terms) > 1);
560
    $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.'));
Dries's avatar
Dries committed
561
  }
Dries's avatar
 
Dries committed
562

563 564 565
  $form['#submit'][] = 'forum_submit';
  // Assign the forum topic submit handler.

566
  return $form;
Dries's avatar
 
Dries committed
567 568
}

569
/**
570
 * Implement hook_term_path().
571 572 573
 */
function forum_term_path($term) {
  return 'forum/' . $term->tid;
574 575
}

Dries's avatar
 
Dries committed
576 577 578
/**
 * Returns a list of all forums for a given taxonomy id
 *
579
 * Forum objects contain the following fields
Dries's avatar
 
Dries committed
580 581 582 583 584 585 586 587 588
 * -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
589
function forum_get_forums($tid = 0) {
590

Dries's avatar
 
Dries committed
591
  $forums = array();
592
  $vid = variable_get('forum_nav_vocabulary', 0);
593
  $_forums = taxonomy_get_tree($vid, $tid);
Dries's avatar
 
Dries committed
594

Dries's avatar
 
Dries committed
595
  if (count($_forums)) {
596 597 598 599 600 601 602 603 604 605 606 607
    $query = db_select('node', 'n');
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
    $query->join('taxonomy_term_node', 'r', 'n.vid = r.vid');
    $query->addExpression('COUNT(n.nid)', 'topic_count');
    $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
    $counts = $query
      ->fields('r', array('tid'))
      ->condition('status', 1)
      ->groupBy('tid')
      ->addTag('node_access')
      ->execute()
      ->fetchAllAssoc('tid');
Dries's avatar
 
Dries committed
608
  }
Dries's avatar
 
Dries committed
609

Dries's avatar
 
Dries committed
610 611 612 613
  foreach ($_forums as $forum) {
    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
      $forum->container = 1;
    }
Dries's avatar
 
Dries committed
614

615
    if (!empty($counts[$forum->tid])) {
Dries's avatar
 
Dries committed
616 617 618 619 620 621 622 623
      $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;
    }

624 625 626 627 628 629
    $query = db_select('node', 'n');
    $query->join('users', 'u1', 'n.uid = u1.uid');
    $query->join('taxonomy_term_node', 'tn', 'n.vid = tn.vid AND tn.tid = :tid', array(':tid' => $forum->tid));
    $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');
630

631 632 633 634 635 636 637 638
    $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();
639

640
    $last_post = new stdClass();
641 642 643 644 645
    if (!empty($topic->last_comment_timestamp)) {
      $last_post->timestamp = $topic->last_comment_timestamp;
      $last_post->name = $topic->last_comment_name;
      $last_post->uid = $topic->last_comment_uid;
    }
Dries's avatar
 
Dries committed
646
    $forum->last_post = $last_post;
Dries's avatar
 
Dries committed
647

Dries's avatar
 
Dries committed
648 649 650 651
    $forums[$forum->tid] = $forum;
  }

  return $forums;
Dries's avatar
 
Dries committed
652 653
}

654 655 656 657 658
/**
 * Calculate the number of nodes the user has not yet read and are newer
 * than NODE_NEW_LIMIT.
 */
function _forum_topics_unread($term, $uid) {
659 660 661 662 663 664 665 666 667 668 669
  $query = db_select('node', 'n');
  $query->join('taxonomy_term_node', 'tn', 'n.vid = tn.vid AND tn.tid = :tid', array(':tid' => $term));
  $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
670 671
}

Dries's avatar
Dries committed
672
function forum_get_topics($tid, $sortby, $forum_per_page) {
Dries's avatar
 
Dries committed
673
  global $user, $forum_topic_list_header;
Dries's avatar
 
Dries committed
674

Dries's avatar
 
Dries committed
675
  $forum_topic_list_header = array(
676
    NULL,
Dries's avatar
 
Dries committed
677
    array('data' => t('Topic'), 'field' => 'n.title'),
678 679
    array('data' => t('Replies'), 'field' => 'ncs.comment_count'),
    array('data' => t('Last reply'), 'field' => 'ncs.last_comment_timestamp'),
Dries's avatar
 
Dries committed
680
  );
Dries's avatar
 
Dries committed
681

Dries's avatar
 
Dries committed
682
  $order = _forum_get_topic_order($sortby);
Dries's avatar
 
Dries committed
683
  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
Dries's avatar
 
Dries committed
684 685
    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
      $forum_topic_list_header[$i]['sort'] = $order['sort'];
Dries's avatar
 
Dries committed
686 687 688
    }
  }

689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
  $query = db_select('node_comment_statistics', 'ncs')->extend('PagerDefault')->extend('TableSort');
  $query->join('node', 'n', 'n.nid = ncs.nid');
  $query->join('users', 'cu', 'ncs.last_comment_uid = cu.uid');
  $query->join('taxonomy_term_node', 'r', 'n.vid = r.vid AND r.tid = :tid', array(':tid' => $tid));
  $query->join('users', 'u', 'n.uid = u.uid');
  $query->join('forum', 'f', 'n.vid = f.vid');
  $query->addExpression('IF(ncs.last_comment_uid != 0, cu.name, ncs.last_comment_name)', 'last_comment_name');
  $query->addField('n', 'created', 'timestamp');
  $query->addField('n', 'comment', 'comment_mode');
  $query->addField('ncs', 'comment_count', 'num_comments');
  $query->addField('f', 'tid', 'forum_tid');
  $query
    ->addTag('node_access')
    ->fields('n', array('nid', 'title', 'type', 'sticky'))
    ->fields('r', array('tid'))
    ->fields('u', array('name', 'uid'))
    ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid'))
    ->condition('n.status', 1)
    ->orderBy('n.sticky', 'DESC')
    ->orderByHeader($forum_topic_list_header)
    ->orderBy('n.created', 'DESC')
    ->limit($forum_per_page);
711

712 713 714 715 716 717
  $count_query = db_select('node', 'n');
  $count_query->join('taxonomy_term_node', 'r', 'n.vid = r.vid AND r.tid = :tid', array(':tid' => $tid));
  $count_query->addExpression('COUNT(*)');
  $count_query
    ->condition('n.status', 1)
    ->addTag('node_access');
718

719 720
  $query->setCountQuery($count_query);
  $result = $query->execute();
721
  $topics = array();
722
  foreach ($result as $topic) {
Dries's avatar
Dries committed
723 724
    if ($user->uid) {
      // folder is new if topic is new or there are new comments since last visit
Dries's avatar
 
Dries committed
725
      if ($topic->tid != $tid) {
Dries's avatar
Dries committed
726
        $topic->new = 0;
Dries's avatar
 
Dries committed
727 728
      }
      else {
Dries's avatar
 
Dries committed
729
        $history = _forum_user_last_visit($topic->nid);
Dries's avatar
 
Dries committed
730
        $topic->new_replies = comment_num_new($topic->nid, $history);
731
        $topic->new = $topic->new_replies || ($topic->timestamp > $history);
Dries's avatar
 
Dries committed
732
      }
733 734
    }
    else {
735
      // Do not track "new replies" status for topics if the user is anonymous.
Dries's avatar
Dries committed
736 737
      $topic->new_replies = 0;
      $topic->new = 0;
738
    }
Dries's avatar
 
Dries committed
739

Dries's avatar
 
Dries committed
740
    if ($topic->num_comments > 0) {
741
      $last_reply = new stdClass();
Dries's avatar
 
Dries committed
742 743 744 745 746
      $last_reply->timestamp = $topic->last_comment_timestamp;
      $last_reply->name = $topic->last_comment_name;
      $last_reply->uid = $topic->last_comment_uid;
      $topic->last_reply = $last_reply;
    }
Dries's avatar
 
Dries committed
747 748 749
    $topics[] = $topic;
  }

Dries's avatar
Dries committed
750
  return $topics;
Dries's avatar
 
Dries committed
751 752
}

Dries's avatar
 
Dries committed
753
/**
754
 * Process variables for forums.tpl.php
755 756 757 758 759 760 761 762
 *
 * The $variables array contains the following arguments:
 * - $forums
 * - $topics
 * - $parents
 * - $tid
 * - $sortby
 * - $forum_per_page
Dries's avatar
 
Dries committed
763
 *
764
 * @see forums.tpl.php
Dries's avatar
 
Dries committed
765
 */
766
function template_preprocess_forums(&$variables) {
Dries's avatar
 
Dries committed
767
  global $user;
Dries's avatar
 
Dries committed
768

769
  $vid = variable_get('forum_nav_vocabulary', 0);
770
  $vocabulary = taxonomy_vocabulary_load($vid);
771
  $title = !empty($vocabulary->name) ? $vocabulary->name : '';
Dries's avatar