forum.module 36.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
      'arguments' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0),
    ),
56
    'forum_submitted' => array(
57
      'template' => 'forum-submitted',
58 59
      'arguments' => array('topic' => NULL),
    ),
60 61 62
  );
}

63 64 65 66 67 68 69 70 71 72
/**
 * 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) {
73 74 75 76 77 78 79
  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();
80 81
}

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

150 151

/**
152
 * Implement hook_init().
153
 */
154
function forum_init() {
155
  drupal_add_css(drupal_get_path('module', 'forum') . '/forum.css');
156
}
157

158
/**
159
 * _forum_node_check_node_type
160 161
 *
 * @param mixed $node
162 163 164
 * @param mixed $vocabulary
 * @access protected
 * @return bool
165
 */
166
function _forum_node_check_node_type($node, $vocabulary) {
167
  // We are going to return if $node->type is not one of the node
168
  // types assigned to the forum vocabulary. If forum_nav_vocabulary
169 170 171
  // 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)) {
172
    return FALSE;
173
  }
174 175 176

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

180 181
  return TRUE;
}
182

183
/**
184
 * Implement hook_node_view().
185
 */
186
function forum_node_view($node, $build_mode) {
187
  $vid = variable_get('forum_nav_vocabulary', 0);
188
  $vocabulary = taxonomy_vocabulary_load($vid);
189
  if (_forum_node_check_node_type($node, $vocabulary)) {
190
    if ((bool)menu_get_object() && taxonomy_node_get_terms_by_vocabulary($node, $vid) && $tree = taxonomy_get_tree($vid)) {
191 192 193 194 195 196 197
      // 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;
198 199
        }
      }
200 201 202 203 204 205 206 207 208 209
      // 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);
210

211 212 213
    }
  }
}
214

215
/**
216
 * Implement hook_node_prepare().
217
 */
218
function forum_node_prepare($node) {
219
  $vid = variable_get('forum_nav_vocabulary', 0);
220
  $vocabulary = taxonomy_vocabulary_load($vid);
221
  if (_forum_node_check_node_type($node, $vocabulary)) {
222 223
    if (empty($node->nid)) {
      // New topic
224 225 226 227
      $node->taxonomy[arg(3)] = (object) array(
        'vid' => $vid,
        'tid' => arg(3),
      );
228 229 230 231 232
    }
  }
}

/**
233
 * Implement hook_node_validate().
234
 *
235 236
 * Check in particular that only a "leaf" term in the associated taxonomy.
 */
237
function forum_node_validate($node, $form) {
238
  $vid = variable_get('forum_nav_vocabulary', 0);
239
  $vocabulary = taxonomy_vocabulary_load($vid);
240
  if (_forum_node_check_node_type($node, $vocabulary)) {
241
    // vocabulary is selected, not a "container" term.
242 243 244 245 246
    if ($node->taxonomy) {
      // Extract the node's proper topic ID.
      $vocabulary = $vid;
      $containers = variable_get('forum_containers', array());
      foreach ($node->taxonomy as $term) {
247 248 249 250 251 252 253
        $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)));
254 255
        }
      }
256 257 258
    }
  }
}
259

260
/**
261
 * Implement hook_node_presave().
262 263 264
 *
 * Assign forum taxonomy when adding a topic from within a forum.
 */
265
function forum_node_presave($node) {
266
  $vid = variable_get('forum_nav_vocabulary', 0);
267
  $vocabulary = taxonomy_vocabulary_load($vid);
268
  if (_forum_node_check_node_type($node, $vocabulary)) {
269 270
    // Make sure all fields are set properly:
    $node->icon = !empty($node->icon) ? $node->icon : '';
271

272 273 274 275
    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;
276
      }
277 278 279
      foreach ($node->taxonomy as $term_id) {
        if (in_array($term_id, $forum_terms)) {
          $node->tid = $term_id;
280
        }
281
      }
282
      $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", array(':nid' => $node->nid), 0, 1)->fetchField();
283 284 285 286 287 288 289
      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;
      }
    }
  }
}
290

291
/**
292
 * Implement hook_node_update().
293
 */
294
function forum_node_update($node) {
295
  $vid = variable_get('forum_nav_vocabulary', 0);
296
  $vocabulary = taxonomy_vocabulary_load($vid);
297
  if (_forum_node_check_node_type($node, $vocabulary)) {
298
    if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
299
      if (!empty($node->tid)) {
300 301 302 303
        db_update('forum')
          ->fields(array('tid' => $node->tid))
          ->condition('vid', $node->vid)
          ->execute();
304 305 306
      }
      // The node is removed from the forum.
      else {
307 308 309
        db_delete('forum')
          ->condition('nid', $node->nid)
          ->execute();
310 311 312
      }
    }
    else {
313
      if (!empty($node->tid)) {
314 315 316 317 318 319 320
        db_insert('forum')
          ->fields(array(
            'tid' => $node->tid,
            'vid' => $node->vid,
            'nid' => $node->nid,
          ))
          ->execute();
321
      }
322 323 324
    }
  }
}
325

326
/**
327
 * Implement hook_node_insert().
328
 */
329
function forum_node_insert($node) {
330
  $vid = variable_get('forum_nav_vocabulary', 0);
331
  $vocabulary = taxonomy_vocabulary_load($vid);
332
  if (_forum_node_check_node_type($node, $vocabulary)) {
333
    if (!empty($node->tid)) {
334 335 336 337 338 339 340
      $nid = db_insert('forum')
        ->fields(array(
          'tid' => $node->tid,
          'vid' => $node->vid,
          'nid' => $node->nid,
        ))
        ->execute();
341 342 343
    }
  }
}
344

345
/**
346
 * Implement hook_node_delete().
347
 */
348
function forum_node_delete($node) {
349
  $vid = variable_get('forum_nav_vocabulary', 0);
350
  $vocabulary = taxonomy_vocabulary_load($vid);
351
  if (_forum_node_check_node_type($node, $vocabulary)) {
352 353 354
    db_delete('forum')
      ->condition('nid', $node->nid)
      ->execute();
355
  }
356
}
357

358
/**
359
 * Implement hook_node_load().
360
 */
361
function forum_node_load($nodes, $types) {
362
  $vid = variable_get('forum_nav_vocabulary', 0);
363 364 365 366
  // If no forum vocabulary is set up, return.
  if ($vid == '') {
    return;
  }
367
  $vocabulary = taxonomy_vocabulary_load($vid);
368 369 370 371 372 373 374 375

  $node_vids = array();
  foreach ($nodes as $node) {
    if (isset($vocabulary->nodes[$node->type])) {
      $node_vids[] = $node->vid;
    }
  }
  if (!empty($node_vids)) {
376
    $result = db_query('SELECT nid, tid FROM {forum} WHERE vid IN(:node_vids)', array(':node_vids' => $node_vids));
377 378 379
    foreach ($result as $record) {
      $nodes[$record->nid]->forum_tid = $record->tid;
    }
380
  }
381 382
}

383
/**
384
 * Implement hook_node_info().
385
 */
386
function forum_node_info() {
387 388
  return array(
    'forum' => array(
389
      'name' => t('Forum topic'),
390
      'base' => 'forum',
391
      'description' => t('A <em>forum topic</em> is the initial post to a new discussion thread within a forum.'),
392 393 394
      'title_label' => t('Subject'),
    )
  );
Dries's avatar
 
Dries committed
395 396
}

397
/**
398
 * Implement hook_permission().
399
 */
400
function forum_permission() {
401
  $perms = array(
402 403 404 405
    'administer forums' => array(
      'title' => t('Administer forums'),
      'description' => t('Manage forums and configure forum administration settings.'),
    ),
406 407
  );
  return $perms;
Dries's avatar
 
Dries committed
408
}
Dries's avatar
 
Dries committed
409

410
/**
411
 * Implement hook_taxonomy().
412
 */
413
function forum_taxonomy($op, $type, $term = NULL) {
414
  if ($op == 'delete' && $term['vid'] == variable_get('forum_nav_vocabulary', 0)) {
415 416
    switch ($type) {
      case 'term':
417
        $result = db_query('SELECT f.nid FROM {forum} f WHERE f.tid = :tid', array(':tid' => $term['tid']));
418
        foreach ($result as $node) {
419 420 421 422 423 424
          // 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());
425 426
        $key = array_search($term['tid'], $containers);
        if ($key !== FALSE) {
427 428 429 430 431 432
          unset($containers[$key]);
        }
        variable_set('forum_containers', $containers);
        break;
      case 'vocabulary':
        variable_del('forum_nav_vocabulary');
Dries's avatar
 
Dries committed
433
    }
434 435
  }
}
Dries's avatar
 
Dries committed
436

437
/**
438
 * Implement hook_form_alter().
439
 */
440
function forum_form_alter(&$form, $form_state, $form_id) {
441
  $vid = variable_get('forum_nav_vocabulary', 0);
442 443 444
  if (isset($form['vid']) && $form['vid']['#value'] == $vid) {
    // Hide critical options from forum vocabulary.
    if ($form_id == 'taxonomy_form_vocabulary') {
445
      $form['help_forum_vocab'] = array(
446
        '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
447 448
        '#weight' => -1,
      );
449
      $form['content_types']['nodes']['#required'] = TRUE;
450
      $form['hierarchy'] = array('#type' => 'value', '#value' => 1);
451 452 453 454
      $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);
455
      unset($form['delete']);
456
    }
457 458 459
    // Hide multiple parents select from forum terms.
    elseif ($form_id == 'taxonomy_form_term') {
      $form['advanced']['parent']['#access'] = FALSE;
460
    }
461
  }
462 463
  if ($form_id == 'forum_node_form') {
    // Make the vocabulary required for 'real' forum-nodes.
464
    $vid = variable_get('forum_nav_vocabulary', 0);
465 466 467
    $form['taxonomy'][$vid]['#required'] = TRUE;
    $form['taxonomy'][$vid]['#options'][''] = t('- Please choose -');
  }
468 469
}

470
/**
471
 * Implement hook_block_list().
472 473 474 475 476 477 478 479
 */
function forum_block_list() {
  $blocks['active']['info'] = t('Active forum topics');
  $blocks['new']['info'] = t('New forum topics');
  return $blocks;
}

/**
480
 * Implement hook_block_configure().
481 482 483 484 485 486 487
 */
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;
}

/**
488
 * Implement hook_block_save().
489 490 491 492 493 494
 */
function forum_block_save($delta = '', $edit = array()) {
  variable_set('forum_block_num_' . $delta, $edit['forum_block_num_' . $delta]);
}

/**
495
 * Implement hook_block_view().
496 497 498 499
 *
 * Generates a block containing the currently active forum topics and the
 * most recently added forum topics.
 */
500 501
function forum_block_view($delta = '') {
  if (user_access('access content')) {
502
    $query = db_select('node', 'n');
503 504
    $query->join('forum', 'f', 'f.vid = n.vid');
    $query->join('taxonomy_term_data', 'td', 'td.tid = f.tid');
505 506 507 508 509 510 511
    $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');
512 513 514
    switch ($delta) {
      case 'active':
        $title = t('Active forum topics');
515 516 517
        $query
          ->orderBy('ncs.last_comment_timestamp', 'DESC')
          ->range(0, variable_get('forum_block_num_active', '5'));
518
        break;
Dries's avatar
 
Dries committed
519

520 521
      case 'new':
        $title = t('New forum topics');
522 523 524
        $query
          ->orderBy('n.nid', 'DESC')
          ->range(0, variable_get('forum_block_num_new', '5'));
525 526 527
        break;
    }

528 529
    $result = $query->execute();
    $content = node_title_list($result);
530 531 532 533 534
    if (!empty($content)) {
      $block['subject'] = $title;
      $block['content'] = $content . theme('more_link', url('forum'), t('Read the latest forum topics.'));
      return $block;
    }
535
  }
Dries's avatar
 
Dries committed
536 537
}

538
/**
539
 * Implement hook_form().
540
 */
541
function forum_form($node, $form_state) {
542
  $type = node_type_get_type($node);
543
  $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => !empty($node->title) ? $node->title : '', '#required' => TRUE, '#weight' => -5);
544

545
  if (!empty($node->nid)) {
546
    $vid = variable_get('forum_nav_vocabulary', 0);
547
    $forum_terms = taxonomy_node_get_terms_by_vocabulary($node, $vid);
Dries's avatar
Dries committed
548
    // if editing, give option to leave shadows
549
    $shadow = (count($forum_terms) > 1);
550
    $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
551
  }
Dries's avatar
 
Dries committed
552

553 554 555
  $form['#submit'][] = 'forum_submit';
  // Assign the forum topic submit handler.

556
  return $form;
Dries's avatar
 
Dries committed
557 558
}

559
/**
560
 * Implement hook_term_path().
561 562 563
 */
function forum_term_path($term) {
  return 'forum/' . $term->tid;
564 565
}

Dries's avatar
 
Dries committed
566 567 568
/**
 * Returns a list of all forums for a given taxonomy id
 *
569
 * Forum objects contain the following fields
Dries's avatar
 
Dries committed
570 571 572 573 574 575 576 577 578
 * -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
579
function forum_get_forums($tid = 0) {
580

Dries's avatar
 
Dries committed
581
  $forums = array();
582
  $vid = variable_get('forum_nav_vocabulary', 0);
583
  $_forums = taxonomy_get_tree($vid, $tid);
Dries's avatar
 
Dries committed
584

Dries's avatar
 
Dries committed
585
  if (count($_forums)) {
586 587
    $query = db_select('node', 'n');
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
588
    $query->join('forum', 'f', 'f.vid = f.vid');
589 590 591
    $query->addExpression('COUNT(n.nid)', 'topic_count');
    $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
    $counts = $query
592
      ->fields('f', array('tid'))
593 594 595 596 597
      ->condition('status', 1)
      ->groupBy('tid')
      ->addTag('node_access')
      ->execute()
      ->fetchAllAssoc('tid');
Dries's avatar
 
Dries committed
598
  }
Dries's avatar
 
Dries committed
599

Dries's avatar
 
Dries committed
600 601 602 603
  foreach ($_forums as $forum) {
    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
      $forum->container = 1;
    }
Dries's avatar
 
Dries committed
604

605
    if (!empty($counts[$forum->tid])) {
Dries's avatar
 
Dries committed
606 607 608 609 610 611 612 613
      $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;
    }

614 615
    $query = db_select('node', 'n');
    $query->join('users', 'u1', 'n.uid = u1.uid');
616
    $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid));
617 618 619
    $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');
620

621 622 623 624 625 626 627 628
    $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();
629

630
    $last_post = new stdClass();
631 632 633 634 635
    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
636
    $forum->last_post = $last_post;
Dries's avatar
 
Dries committed
637

Dries's avatar
 
Dries committed
638 639 640 641
    $forums[$forum->tid] = $forum;
  }

  return $forums;
Dries's avatar
 
Dries committed
642 643
}

644 645 646 647 648
/**
 * Calculate the number of nodes the user has not yet read and are newer
 * than NODE_NEW_LIMIT.
 */
function _forum_topics_unread($term, $uid) {
649
  $query = db_select('node', 'n');
650
  $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term));
651 652 653 654 655 656 657 658 659
  $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
660 661
}

Dries's avatar
Dries committed
662
function forum_get_topics($tid, $sortby, $forum_per_page) {
Dries's avatar
 
Dries committed
663
  global $user, $forum_topic_list_header;
Dries's avatar
 
Dries committed
664

Dries's avatar
 
Dries committed
665
  $forum_topic_list_header = array(
666
    NULL,
Dries's avatar
 
Dries committed
667
    array('data' => t('Topic'), 'field' => 'n.title'),
668 669
    array('data' => t('Replies'), 'field' => 'ncs.comment_count'),
    array('data' => t('Last reply'), 'field' => 'ncs.last_comment_timestamp'),
Dries's avatar
 
Dries committed
670
  );
Dries's avatar
 
Dries committed
671

Dries's avatar
 
Dries committed
672
  $order = _forum_get_topic_order($sortby);
Dries's avatar
 
Dries committed
673
  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
Dries's avatar
 
Dries committed
674 675
    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
      $forum_topic_list_header[$i]['sort'] = $order['sort'];
Dries's avatar
 
Dries committed
676 677 678
    }
  }

679 680 681
  $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');
682
  $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $tid));
683 684 685 686 687 688 689 690 691
  $query->join('users', 'u', 'n.uid = u.uid');
  $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'))
692
    ->fields('f', array('tid'))
693 694 695 696 697 698 699
    ->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);
700

701
  $count_query = db_select('node', 'n');
702
  $count_query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $tid));
703 704 705 706
  $count_query->addExpression('COUNT(*)');
  $count_query
    ->condition('n.status', 1)
    ->addTag('node_access');
707

708 709
  $query->setCountQuery($count_query);
  $result = $query->execute();
710
  $topics = array();
711
  foreach ($result as $topic) {
Dries's avatar
Dries committed
712 713
    if ($user->uid) {
      // folder is new if topic is new or there are new comments since last visit
Dries's avatar
 
Dries committed
714
      if ($topic->tid != $tid) {
Dries's avatar
Dries committed
715
        $topic->new = 0;
Dries's avatar
 
Dries committed
716 717
      }
      else {
Dries's avatar
 
Dries committed
718
        $history = _forum_user_last_visit($topic->nid);
Dries's avatar
 
Dries committed
719
        $topic->new_replies = comment_num_new($topic->nid, $history);
720
        $topic->new = $topic->new_replies || ($topic->timestamp > $history);
Dries's avatar
 
Dries committed
721
      }
722 723
    }
    else {
724
      // Do not track "new replies" status for topics if the user is anonymous.
Dries's avatar
Dries committed
725 726
      $topic->new_replies = 0;
      $topic->new = 0;
727
    }
Dries's avatar
 
Dries committed
728

Dries's avatar
 
Dries committed
729
    if ($topic->num_comments > 0) {
730
      $last_reply = new stdClass();
Dries's avatar
 
Dries committed
731 732 733 734 735
      $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
736 737 738
    $topics[] = $topic;
  }

Dries's avatar
Dries committed
739
  return $topics;
Dries's avatar
 
Dries committed
740 741
}

Dries's avatar
 
Dries committed
742
/**
743
 * Process variables for forums.tpl.php
744 745 746 747 748 749 750 751
 *
 * The $variables array contains the following arguments:
 * - $forums
 * - $topics
 * - $parents
 * - $tid
 * - $sortby
 * - $forum_per_page
Dries's avatar
 
Dries committed
752
 *
753
 * @see forums.tpl.php
Dries's avatar
 
Dries committed