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

139 140

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

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

160
  return is_array($field);
161
}
162

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

182 183 184
    }
  }
}
185

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

/**
199
 * Implement hook_node_validate().
200
 *
201 202
 * Check in particular that only a "leaf" term in the associated taxonomy.
 */
203
function forum_node_validate($node, $form) {
204 205
  if (_forum_node_check_node_type($node)) {
    $langcode = $form['taxonomy_forums']['#language'];
206
    // vocabulary is selected, not a "container" term.
207
    if (!empty($node->taxonomy_forums[$langcode])) {
208 209
      // Extract the node's proper topic ID.
      $containers = variable_get('forum_containers', array());
210 211 212 213 214
      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,
215
        ))->fetchField();
216 217
        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)));
218 219
        }
      }
220 221 222
    }
  }
}
223

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

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

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

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

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

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

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

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

386 387 388 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 438 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
/**
 * 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,
          'title' => $object->title,
          '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,
          ));
475
        }
476 477 478 479 480
      }
      $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
481
    }
482 483
  }
}
Dries's avatar
 
Dries committed
484

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

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

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

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

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

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

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

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

604
/**
605
 * Implement hook_form().
606
 */
607
function forum_form($node, $form_state) {
608
  $type = node_type_get_type($node);
609
  $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => !empty($node->title) ? $node->title : '', '#required' => TRUE, '#weight' => -5);
610

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

619 620 621
  $form['#submit'][] = 'forum_submit';
  // Assign the forum topic submit handler.

622
  return $form;
Dries's avatar
 
Dries committed
623 624
}

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

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

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

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

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

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

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

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

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

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

  return $forums;
Dries's avatar
 
Dries committed
708 709
}

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

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

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

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

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

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

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

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