forum.module 39.2 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/build/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. To rearrange forums and containers, grab a drag-and-drop handle under the <em>Name</em> column and drag the forum or container to a new location in the list. (Grab a handle by clicking and holding the mouse while hovering over a handle icon.) Remember that your changes will not be saved until you click the <em>Save</em> button at the bottom of the page.') . '</p>';
26
    case 'admin/build/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/build/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/build/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/content/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/build/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/build/forum/list'] = array(
103
    'title' => 'List',
104 105 106
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
107
  $items['admin/build/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/build/forum',
114
  );
115
  $items['admin/build/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/build/forum',
122
  );
123
  $items['admin/build/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/build/forum',
131
  );
132
  $items['admin/build/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/build/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/build/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_access().
409
 */
410
function forum_access($op, $node, $account) {
411 412
  switch ($op) {
    case 'create':
413
      return user_access('create forum content', $account);
414
    case 'update':
415
      return user_access('edit any forum content', $account) || (user_access('edit own forum content', $account) && ($account->uid == $node->uid));
416
    case 'delete':
417
      return user_access('delete any forum content', $account) || (user_access('delete own forum content', $account) && ($account->uid == $node->uid));
Dries's avatar
 
Dries committed
418
  }
Dries's avatar
 
Dries committed
419 420
}

421
/**
422
 * Implement hook_perm().
423
 */
Dries's avatar
 
Dries committed
424
function forum_perm() {
425
  $perms = array(
426 427 428 429
    'administer forums' => array(
      'title' => t('Administer forums'),
      'description' => t('Manage forums and configure forum administration settings.'),
    ),
430 431 432
  );
  $perms += node_list_permissions('forum');
  return $perms;
Dries's avatar
 
Dries committed
433
}
Dries's avatar
 
Dries committed
434

435
/**
436
 * Implement hook_taxonomy().
437
 */
438
function forum_taxonomy($op, $type, $term = NULL) {
439
  if ($op == 'delete' && $term['vid'] == variable_get('forum_nav_vocabulary', 0)) {
440 441
    switch ($type) {
      case 'term':
442 443
        $result = db_query('SELECT tn.nid FROM {taxonomy_term_node} tn WHERE tn.tid = :tid', array(':tid' => $term['tid']));
        foreach ($result as $node) {
444 445 446 447 448 449
          // 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());
450 451
        $key = array_search($term['tid'], $containers);
        if ($key !== FALSE) {
452 453 454 455 456 457
          unset($containers[$key]);
        }
        variable_set('forum_containers', $containers);
        break;
      case 'vocabulary':
        variable_del('forum_nav_vocabulary');
Dries's avatar
 
Dries committed
458
    }
459 460
  }
}
Dries's avatar
 
Dries committed
461

462
/**
463
 * Implement hook_form_alter().
464
 */
465
function forum_form_alter(&$form, $form_state, $form_id) {
466
  $vid = variable_get('forum_nav_vocabulary', 0);
467 468 469
  if (isset($form['vid']) && $form['vid']['#value'] == $vid) {
    // Hide critical options from forum vocabulary.
    if ($form_id == 'taxonomy_form_vocabulary') {
470
      $form['help_forum_vocab'] = array(
471
        '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
472 473
        '#weight' => -1,
      );
474
      $form['content_types']['nodes']['#required'] = TRUE;
475
      $form['hierarchy'] = array('#type' => 'value', '#value' => 1);
476 477 478 479
      $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);
480
      unset($form['delete']);
481
    }
482 483 484
    // Hide multiple parents select from forum terms.
    elseif ($form_id == 'taxonomy_form_term') {
      $form['advanced']['parent']['#access'] = FALSE;
485
    }
486
  }
487 488
  if ($form_id == 'forum_node_form') {
    // Make the vocabulary required for 'real' forum-nodes.
489
    $vid = variable_get('forum_nav_vocabulary', 0);
490 491 492
    $form['taxonomy'][$vid]['#required'] = TRUE;
    $form['taxonomy'][$vid]['#options'][''] = t('- Please choose -');
  }
493 494
}

495
/**
496
 * Implement hook_block_list().
497 498 499 500 501 502 503 504
 */
function forum_block_list() {
  $blocks['active']['info'] = t('Active forum topics');
  $blocks['new']['info'] = t('New forum topics');
  return $blocks;
}

/**
505
 * Implement hook_block_configure().
506 507 508 509 510 511 512
 */
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;
}

/**
513
 * Implement hook_block_save().
514 515 516 517 518 519
 */
function forum_block_save($delta = '', $edit = array()) {
  variable_set('forum_block_num_' . $delta, $edit['forum_block_num_' . $delta]);
}

/**
520
 * Implement hook_block_view().
521 522 523 524
 *
 * Generates a block containing the currently active forum topics and the
 * most recently added forum topics.
 */
525 526
function forum_block_view($delta = '') {
  if (user_access('access content')) {
527 528 529 530 531 532 533 534 535 536
    $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');
537 538 539
    switch ($delta) {
      case 'active':
        $title = t('Active forum topics');
540 541 542
        $query
          ->orderBy('ncs.last_comment_timestamp', 'DESC')
          ->range(0, variable_get('forum_block_num_active', '5'));
543
        break;
Dries's avatar
 
Dries committed
544

545 546
      case 'new':
        $title = t('New forum topics');
547 548 549
        $query
          ->orderBy('n.nid', 'DESC')
          ->range(0, variable_get('forum_block_num_new', '5'));
550 551 552
        break;
    }

553 554
    $result = $query->execute();
    $content = node_title_list($result);
555 556 557 558 559
    if (!empty($content)) {
      $block['subject'] = $title;
      $block['content'] = $content . theme('more_link', url('forum'), t('Read the latest forum topics.'));
      return $block;
    }
560
  }
Dries's avatar
 
Dries committed
561 562
}

563
/**
564
 * Implement hook_form().
565
 */
566
function forum_form($node, $form_state) {
567
  $type = node_type_get_type($node);
568
  $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => !empty($node->title) ? $node->title : '', '#required' => TRUE, '#weight' => -5);
569

570
  if (!empty($node->nid)) {
571
    $vid = variable_get('forum_nav_vocabulary', 0);
572
    $forum_terms = taxonomy_node_get_terms_by_vocabulary($node, $vid);
Dries's avatar
Dries committed
573
    // if editing, give option to leave shadows
574
    $shadow = (count($forum_terms) > 1);
575
    $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
576
  }
Dries's avatar
 
Dries committed
577

578 579 580
  $form['#submit'][] = 'forum_submit';
  // Assign the forum topic submit handler.

581
  return $form;
Dries's avatar
 
Dries committed
582 583
}

584
/**
585
 * Implement hook_term_path().
586 587 588
 */
function forum_term_path($term) {
  return 'forum/' . $term->tid;
589 590
}

Dries's avatar
 
Dries committed
591 592 593
/**
 * Returns a list of all forums for a given taxonomy id
 *
594
 * Forum objects contain the following fields
Dries's avatar
 
Dries committed
595 596 597 598 599 600 601 602 603
 * -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
604
function forum_get_forums($tid = 0) {
605

Dries's avatar
 
Dries committed
606
  $forums = array();
607
  $vid = variable_get('forum_nav_vocabulary', 0);
608
  $_forums = taxonomy_get_tree($vid, $tid);
Dries's avatar
 
Dries committed
609

Dries's avatar
 
Dries committed
610
  if (count($_forums)) {
611 612 613 614 615 616 617 618 619 620 621 622
    $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
623
  }
Dries's avatar
 
Dries committed
624

Dries's avatar
 
Dries committed
625 626 627 628
  foreach ($_forums as $forum) {
    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
      $forum->container = 1;
    }
Dries's avatar
 
Dries committed
629

630
    if (!empty($counts[$forum->tid])) {
Dries's avatar
 
Dries committed
631 632 633 634 635 636 637 638
      $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;
    }

639 640 641 642 643 644
    $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');
645

646 647 648 649 650 651 652 653
    $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();
654

655
    $last_post = new stdClass();
656 657 658 659 660
    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
661
    $forum->last_post = $last_post;
Dries's avatar
 
Dries committed
662

Dries's avatar
 
Dries committed
663 664 665 666
    $forums[$forum->tid] = $forum;
  }

  return $forums;
Dries's avatar
 
Dries committed
667 668
}

669 670 671 672 673
/**
 * Calculate the number of nodes the user has not yet read and are newer
 * than NODE_NEW_LIMIT.
 */
function _forum_topics_unread($term, $uid) {
674 675 676 677 678 679 680 681 682 683 684
  $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
685 686
}

Dries's avatar
Dries committed
687
function forum_get_topics($tid, $sortby, $forum_per_page) {
Dries's avatar
 
Dries committed
688
  global $user, $forum_topic_list_header;
Dries's avatar
 
Dries committed
689

Dries's avatar
 
Dries committed
690
  $forum_topic_list_header = array(
691
    NULL,
Dries's avatar
 
Dries committed
692
    array('data' => t('Topic'), 'field' => 'n.title'),
693
    array('data' => t('Replies'), 'field' => 'ncs.comment_count'),
Dries's avatar
 
Dries committed
694
    array('data' => t('Created'), 'field' => 'n.created'),
695
    array('data' => t('Last reply'), 'field' => 'ncs.last_comment_timestamp'),
Dries's avatar
 
Dries committed
696
  );
Dries's avatar
 
Dries committed
697

Dries's avatar
 
Dries committed
698
  $order = _forum_get_topic_order($sortby);
Dries's avatar
 
Dries committed
699
  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
Dries's avatar
 
Dries committed
700 701
    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
      $forum_topic_list_header[$i]['sort'] = $order['sort'];
Dries's avatar
 
Dries committed
702 703 704
    }
  }

705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
  $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);
727

728 729 730 731 732 733
  $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');
734

735 736
  $query->setCountQuery($count_query);
  $result = $query->execute();
737
  $topics = array();
738
  foreach ($result as $topic) {
Dries's avatar
Dries committed
739 740
    if ($user->uid) {
      // folder is new if topic is new or there are new comments since last visit
Dries's avatar
 
Dries committed
741
      if ($topic->tid != $tid) {
Dries's avatar
Dries committed
742
        $topic->new = 0;
Dries's avatar
 
Dries committed
743 744
      }
      else {
Dries's avatar
 
Dries committed
745
        $history = _forum_user_last_visit($topic->nid);
Dries's avatar
 
Dries committed
746
        $topic->new_replies = comment_num_new($topic->nid, $history);
747
        $topic->new = $topic->new_replies || ($topic->timestamp > $history);
Dries's avatar
 
Dries committed
748
      }
749 750
    }
    else {
751
      // Do not track "new replies" status for topics if the user is anonymous.
Dries's avatar
Dries committed
752 753
      $topic->new_replies = 0;
      $topic->new = 0;
754
    }
Dries's avatar
 
Dries committed
755

Dries's avatar
 
Dries committed
756
    if ($topic->num_comments > 0) {
757
      $last_reply = new stdClass();
Dries's avatar
 
Dries committed
758 759 760 761 762
      $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
763