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

Dries's avatar
 
Dries committed
4 5
/**
 * @file
6
 * Provides discussion forums.
Dries's avatar
 
Dries committed
7 8
 */

9
/**
10
 * Implement hook_help().
11
 */
12 13
function forum_help($path, $arg) {
  switch ($path) {
14
    case 'admin/help#forum':
15
      $output = '<p>' . t('The forum module lets you create threaded discussion forums with functionality similar to other message board systems. Forums are useful because they allow community members to discuss topics with one another while ensuring those conversations are archived for later reference. The <a href="@create-topic">forum topic</a> menu item (under <em>Add new content</em> on the Navigation menu) creates the initial post of a new threaded discussion, or thread.', array('@create-topic' => url('node/add/forum'))) . '</p>';
16 17 18 19
      $output .= '<p>' . t('A threaded discussion occurs as people leave comments on a forum topic (or on other comments within that topic). A forum topic is contained within a forum, which may hold many similar or related forum topics. Forums are (optionally) nested within a container, which may hold many similar or related forums. Both containers and forums may be nested within other containers and forums, and provide structure for your message board. By carefully planning this structure, you make it easier for users to find and comment on a specific forum topic.') . '</p>';
      $output .= '<p>' . t('When administering a forum, note that:') . '</p>';
      $output .= '<ul><li>' . t('a forum topic (and all of its comments) may be moved between forums by selecting a different forum while editing a forum topic.') . '</li>';
      $output .= '<li>' . t('when moving a forum topic between forums, the <em>Leave shadow copy</em> option creates a link in the original forum pointing to the new location.') . '</li>';
20
      $output .= '<li>' . t('selecting <em>Closed</em> under <em>Comment settings</em> while editing a forum topic will lock (prevent new comments) on the thread.') . '</li>';
21 22
      $output .= '<li>' . t('selecting <em>Disabled</em> under <em>Comment settings</em> while editing a forum topic will hide all existing comments on the thread, and prevent new ones.') . '</li></ul>';
      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@forum">Forum module</a>.', array('@forum' => 'http://drupal.org/handbook/modules/forum/')) . '</p>';
23
      return $output;
24
    case 'admin/structure/forum':
25
      return '<p>' . t('This page displays a list of existing forums and containers. Containers (optionally) hold forums, and forums hold forum topics (a forum topic is the initial post to a threaded discussion). To provide structure, both containers and forums may be placed inside other containers and forums.') . '</p>';
26
    case 'admin/structure/forum/add/container':
27
      return '<p>' . t('By grouping related or similar forums, containers help organize forums. For example, a container named "Food" may hold two forums named "Fruit" and "Vegetables", respectively.') . '</p>';
28
    case 'admin/structure/forum/add/forum':
29
      return '<p>' . t('A forum holds related or similar forum topics (a forum topic is the initial post to a threaded discussion). For example, a forum named "Fruit" may contain forum topics titled "Apples" and "Bananas", respectively.') . '</p>';
30 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
    'forum_form' => array(
      'arguments' => array('form' => NULL),
      'file' => 'forum.admin.inc',
    ),
64 65 66
  );
}

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

143 144

/**
145
 * Implement hook_init().
146
 */
147
function forum_init() {
148
  drupal_add_css(drupal_get_path('module', 'forum') . '/forum.css');
149
}
150

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

164
  return is_array($field);
165
}
166

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

186 187 188
    }
  }
}
189

190
/**
191
 * Implement hook_node_prepare().
192
 */
193
function forum_node_prepare($node) {
194
  if (_forum_node_check_node_type($node)) {
195 196
    if (empty($node->nid)) {
      // New topic
197
      $node->taxonomy_forums[0]['value'] =  arg(3);
198 199 200 201 202
    }
  }
}

/**
203
 * Implement hook_node_validate().
204
 *
205 206
 * Check in particular that only a "leaf" term in the associated taxonomy.
 */
207
function forum_node_validate($node, $form) {
208 209
  if (_forum_node_check_node_type($node)) {
    $langcode = $form['taxonomy_forums']['#language'];
210
    // vocabulary is selected, not a "container" term.
211
    if (!empty($node->taxonomy_forums[$langcode])) {
212 213
      // Extract the node's proper topic ID.
      $containers = variable_get('forum_containers', array());
214 215 216 217 218
      foreach ($node->taxonomy_forums[$langcode] as $tid) {
        $term = taxonomy_term_load($tid['value']);
        $used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid',0 , 1, array(
          ':tid' => $term->tid,
          ':vid' => $term->vid,
219
        ))->fetchField();
220 221
        if ($used && in_array($term->tid, $containers)) {
          form_set_error('taxonomy_forums', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name)));
222 223
        }
      }
224 225 226
    }
  }
}
227

228
/**
229
 * Implement hook_node_presave().
230 231 232
 *
 * Assign forum taxonomy when adding a topic from within a forum.
 */
233
function forum_node_presave($node) {
234
  if (_forum_node_check_node_type($node)) {
235 236
    // Make sure all fields are set properly:
    $node->icon = !empty($node->icon) ? $node->icon : '';
237 238 239
    $langcode = array_shift(array_keys($node->taxonomy_forums));
    if (!empty($node->taxonomy_forums[$langcode])) {
      $node->forum_tid = $node->taxonomy_forums[$langcode][0]['value'];
240
      $old_tid = db_query_range("SELECT f.tid FROM {forum} f INNER JOIN {node} n ON f.vid = n.vid WHERE n.nid = :nid ORDER BY f.vid DESC", 0, 1, array(':nid' => $node->nid))->fetchField();
241
      if ($old_tid && isset($node->forum_tid) && ($node->forum_tid != $old_tid) && !empty($node->shadow)) {
242
        // A shadow copy needs to be created. Retain new term and add old term.
243
        $node->taxonomy_forums[$langcode][] = array('value' => $old_tid);
244 245 246 247
      }
    }
  }
}
248

249
/**
250
 * Implement hook_node_update().
251
 */
252
function forum_node_update($node) {
253
  if (_forum_node_check_node_type($node)) {
254
    if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
255
      if (!empty($node->forum_tid)) {
256
        db_update('forum')
257
          ->fields(array('tid' => $node->forum_tid))
258 259
          ->condition('vid', $node->vid)
          ->execute();
260 261 262
      }
      // The node is removed from the forum.
      else {
263 264 265
        db_delete('forum')
          ->condition('nid', $node->nid)
          ->execute();
266 267 268
      }
    }
    else {
269
      if (!empty($node->forum_tid)) {
270 271
        db_insert('forum')
          ->fields(array(
272
            'tid' => $node->forum_tid,
273 274 275 276
            'vid' => $node->vid,
            'nid' => $node->nid,
          ))
          ->execute();
277
      }
278
    }
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
    // If the node has a shadow forum topic, update the record for this
    // revision.
    if ($node->shadow) {
      db_delete('forum')
        ->condition('nid', $node->nid)
        ->condition('vid', $node->vid)
        ->execute();
      db_insert('forum')
        ->fields(array(
          'nid' => $node->nid,
          'vid' => $node->vid,
          'tid' => $node->forum_tid,
        ))
        ->execute();
     }
294 295
  }
}
296

297
/**
298
 * Implement hook_node_insert().
299
 */
300
function forum_node_insert($node) {
301 302
  if (_forum_node_check_node_type($node)) {
    if (!empty($node->forum_tid)) {
303 304
      $nid = db_insert('forum')
        ->fields(array(
305
          'tid' => $node->forum_tid,
306 307 308 309
          'vid' => $node->vid,
          'nid' => $node->nid,
        ))
        ->execute();
310 311 312
    }
  }
}
313

314
/**
315
 * Implement hook_node_delete().
316
 */
317
function forum_node_delete($node) {
318
  if (_forum_node_check_node_type($node)) {
319 320 321
    db_delete('forum')
      ->condition('nid', $node->nid)
      ->execute();
322 323 324
    db_delete('forum_index')
      ->condition('nid', $node->nid)
      ->execute();
325
  }
326
}
327

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

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

364
/**
365
 * Implement hook_permission().
366
 */
367
function forum_permission() {
368
  $perms = array(
369 370 371 372
    'administer forums' => array(
      'title' => t('Administer forums'),
      'description' => t('Manage forums and configure forum administration settings.'),
    ),
373 374
  );
  return $perms;
Dries's avatar
 
Dries committed
375
}
Dries's avatar
 
Dries committed
376

377
/**
378
 * Implement hook_taxonomy().
379
 */
380 381 382 383 384 385 386 387 388
function forum_taxonomy_term_delete($tid) {
  // For containers, remove the tid from the forum_containers variable.
  $containers = variable_get('forum_containers', array());
  $key = array_search($tid, $containers);
  if ($key !== FALSE) {
    unset($containers[$key]);
  }
  variable_set('forum_containers', $containers);
}
389

390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
/**
 * Implement hook_comment_publish().
 *
 * This actually handles the insert and update of published nodes since
 * comment_save() calls hook_comment_publish() for all published comments.
 */
function forum_comment_publish($comment) {
  _forum_update_forum_index($comment->nid);
}

/**
 * Implement forum_comment_update().
 *
 * Comment module doesn't call hook_comment_unpublish() when saving individual
 * comments so we need to check for those here.
 */
function forum_comment_update($comment) {
  // comment_save() calls hook_comment_publish() for all published comments
  // so we to handle all other values here.
  if (!$comment->status) {
    _forum_update_forum_index($comment->nid);
  }
}

/**
 * Implement forum_comment_unpublish() {
 */
function forum_comment_unpublish($comment) {
  _forum_update_forum_index($comment->nid);
}

/**
 * Implement forum_comment_delete().
 */
function forum_comment_delete($comment) {
  _forum_update_forum_index($comment->nid);
}

/**
 * Implement hook_field_attach_pre_insert().
 */
function forum_field_attach_pre_insert($obj_type, $object, $skip_fields) {
  if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {
    $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
    foreach ($object->taxonomy_forums as $language) {
      foreach ($language as $delta) {
        $query->values(array(
          'nid' => $object->nid,
438
          'title' => $object->title[FIELD_LANGUAGE_NONE][0]['value'],
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
          'tid' => $delta['value'],
          'sticky' => $object->sticky,
          'created' => $object->created,
          'comment_count' => 0,
          'last_comment_timestamp' => $object->created,
        ));
      }
    }
    $query->execute();
  }
}

/**
 * Implement hook_field_attach_pre_update().
 */
function forum_field_attach_pre_update($obj_type, $object, $skip_fields) {
  $first_call = &drupal_static(__FUNCTION__, array());

  if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {
    // We don't maintain data for old revisions, so clear all previous values
    // from the table. Since this hook runs once per field, per object, make
    // sure we only wipe values once.
    if (!isset($first_call[$object->nid])) {
      $first_call[$object->nid] = FALSE;
      db_delete('forum_index')->condition('nid', $object->nid)->execute();
    }
    // Only save data to the table if the node is published.
    if ($object->status) {
      $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
      foreach ($object->taxonomy_forums as $language) {
        foreach ($language as $delta) {
          $query->values(array(
            'nid' => $object->nid,
            'title' => $object->title,
            'tid' => $delta['value'],
            'sticky' => $object->sticky,
            'created' => $object->created,
            'comment_count' => 0,
            'last_comment_timestamp' => $object->created,
          ));
479
        }
480 481 482 483 484
      }
      $query->execute();
      // The logic for determining last_comment_count is fairly complex, so
      // call _forum_update_forum_index() too.
      _forum_update_forum_index($object->nid);
Dries's avatar
 
Dries committed
485
    }
486 487
  }
}
Dries's avatar
 
Dries committed
488

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

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

/**
533
 * Implement hook_block_configure().
534 535 536 537 538 539 540
 */
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;
}

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

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

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

573 574 575 576 577 578 579
  $cache_keys = array_merge(array('forum', $delta), drupal_render_cid_parts());
  // Cache based on the altered query. Enables us to cache with node access enabled.
  $query->preExecute();
  $cache_keys[] = md5(serialize(array((string) $query, $query->getArguments())));

  $block['subject'] = $title;
  $block['content'] = array(
580 581 582 583 584 585 586
     '#access' => user_access('access content'),
     '#pre_render' => array('forum_block_view_pre_render'),
     '#cache' => array(
        'keys' => $cache_keys,
        'expire' => CACHE_TEMPORARY,
     ),
     '#query' => $query,
587 588
  );
  return $block;
589
}
590

591 592 593 594 595 596 597 598 599 600 601 602
/**
* A #pre_render callback. Lists nodes based on the element's #query property.
*
* @see forum_block_view().
*
* @return
*   A renderable array.
*/
function forum_block_view_pre_render($elements) {
  $result = $elements['#query']->execute();
  if ($node_title_list = node_title_list($result)) {
    $elements['forum_list'] = array('#markup' => $node_title_list);
603
    $elements['forum_more'] = array('#markup' => theme('more_link', array('url' => url('forum'), 'title' => t('Read the latest forum topics.'))));
604
  }
605
  return $elements;
Dries's avatar
 
Dries committed
606 607
}

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

622 623 624
  $form['#submit'][] = 'forum_submit';
  // Assign the forum topic submit handler.

625
  return $form;
Dries's avatar
 
Dries committed
626 627
}

628
/**
629
 * Implement hook_term_path().
630 631 632
 */
function forum_term_path($term) {
  return 'forum/' . $term->tid;
633 634
}

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

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

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

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

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

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

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

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

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

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

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

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

Dries's avatar
 
Dries committed
734
  $forum_topic_list_header = array(
735
    NULL,
736 737 738
    array('data' => t('Topic'), 'field' => 'f.title'),
    array('data' => t('Replies'), 'field' => 'f.comment_count'),
    array('data' => t('Last reply'), 'field' => 'f.last_comment_timestamp'),
Dries's avatar
 
Dries committed
739
  );
Dries's avatar
 
Dries committed
740

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

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

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

763 764
  $query->setCountQuery($count_query);
  $result = $query->execute();
765 766 767 768 769 770 771 772 773 774 775
  $nids = array();
  foreach ($result as $record) {
    $nids[] = $record->nid;
  }
  if ($nids) {
    $result = db_query("SELECT n.title, n.nid, n.sticky, n.created, n.uid, n.comment AS comment_mode, ncs.*, f.tid AS forum_tid, u.name, IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name) AS last_comment_name FROM {node} n INNER JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid INNER JOIN {forum} f ON n.vid = f.vid INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {users} u2 ON ncs.last_comment_uid = u2.uid WHERE n.nid IN (:nids)", array(':nids' => $nids));
  }
  else {
    $result = array();
  }

776
  $topics = array();
777
  foreach ($result as $topic) {
Dries's avatar
Dries committed
778 779
    if ($user->uid) {
      // folder is new if topic is new or there are new comments since last visit
780
      if ($topic->forum_tid != $tid) {
Dries's avatar
Dries committed
781
        $topic->new = 0;
Dries's avatar
 
Dries committed
782 783
      }
      else {
Dries's avatar
 
Dries committed
784
        $history = _forum_user_last_visit($topic->nid);
Dries's avatar
 
Dries committed
785
        $topic->new_replies = comment_num_new($topic->nid, $history);
786
        $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history);
Dries's avatar
 
Dries committed
787
      }