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

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

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

35
/**
36
 * Implement hook_theme().
37 38 39
 */
function forum_theme() {
  return array(
40
    'forums' => array(
41
      'template' => 'forums',
42 43 44
      'arguments' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
    ),
    'forum_list' => array(
45
      'template' => 'forum-list',
46 47 48
      'arguments' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
    ),
    'forum_topic_list' => array(
49
      'template' => 'forum-topic-list',
50 51 52
      'arguments' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
    ),
    'forum_icon' => array(
53
      'template' => 'forum-icon',
54 55
      'arguments' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0),
    ),
56
    'forum_submitted' => array(
57
      'template' => 'forum-submitted',
58 59
      'arguments' => array('topic' => NULL),
    ),
60 61 62
  );
}

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

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

158 159

/**
160
 * Implement hook_init().
161
 */
162
function forum_init() {
163
  drupal_add_css(drupal_get_path('module', 'forum') . '/forum.css');
164
}
165

166
/**
167
 * _forum_node_check_node_type
168 169
 *
 * @param mixed $node
170 171 172
 * @param mixed $vocabulary
 * @access protected
 * @return bool
173
 */
174
function _forum_node_check_node_type($node, $vocabulary) {
175
  // We are going to return if $node->type is not one of the node
176
  // types assigned to the forum vocabulary. If forum_nav_vocabulary
177 178 179
  // 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)) {
180
    return FALSE;
181
  }
182 183 184

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

188 189
  return TRUE;
}
190

191
/**
192
 * Implement hook_node_view().
193
 */
194
function forum_node_view($node, $build_mode) {
195
  $vid = variable_get('forum_nav_vocabulary', 0);
196
  $vocabulary = taxonomy_vocabulary_load($vid);
197
  if (_forum_node_check_node_type($node, $vocabulary)) {
198
    if ((bool)menu_get_object() && taxonomy_node_get_terms_by_vocabulary($node, $vid) && $tree = taxonomy_get_tree($vid)) {
199 200 201 202 203 204 205
      // 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;
206 207
        }
      }
208 209 210 211 212 213 214 215 216 217
      // 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);
218

219 220 221
    }
  }
}
222

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

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

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

280 281 282 283
    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;
284
      }
285 286 287
      foreach ($node->taxonomy as $term_id) {
        if (in_array($term_id, $forum_terms)) {
          $node->tid = $term_id;
288
        }
289
      }
290
      $old_tid = db_query_range("SELECT f.tid FROM {forum} f INNER JOIN {node} n ON f.vid = n.vid WHERE n.nid = :nid ORDER BY f.vid DESC", array(':nid' => $node->nid), 0, 1)->fetchField();
291 292 293 294 295 296 297
      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;
      }
    }
  }
}
298

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

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

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

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

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

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

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

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

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

478
/**
479
 * Implement hook_block_info().
480
 */
481
function forum_block_info() {
482 483 484 485 486 487 488 489
  $blocks['active'] = array(
    'info' => t('Active forum topics'),
    'cache' => DRUPAL_CACHE_CUSTOM,
  );
  $blocks['new'] = array(
    'info' => t('New forum topics'),
    'cache' => DRUPAL_CACHE_CUSTOM,
  );
490 491 492 493
  return $blocks;
}

/**
494
 * Implement hook_block_configure().
495 496 497 498 499 500 501
 */
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;
}

/**
502
 * Implement hook_block_save().
503 504 505 506 507 508
 */
function forum_block_save($delta = '', $edit = array()) {
  variable_set('forum_block_num_' . $delta, $edit['forum_block_num_' . $delta]);
}

/**
509
 * Implement hook_block_view().
510 511 512 513
 *
 * Generates a block containing the currently active forum topics and the
 * most recently added forum topics.
 */
514
function forum_block_view($delta = '') {
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
  $query = db_select('node', 'n');
  $query->join('forum', 'f', 'f.vid = n.vid');
  $query->join('taxonomy_term_data', 'td', 'td.tid = f.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');
  switch ($delta) {
    case 'active':
      $title = t('Active forum topics');
      $query
        ->orderBy('ncs.last_comment_timestamp', 'DESC')
        ->range(0, variable_get('forum_block_num_active', '5'));
      break;

    case 'new':
      $title = t('New forum topics');
      $query
        ->orderBy('n.nid', 'DESC')
        ->range(0, variable_get('forum_block_num_new', '5'));
      break;
  }
Dries's avatar
 
Dries committed
540

541 542 543 544 545 546 547
  $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(
548 549 550 551 552 553 554
     '#access' => user_access('access content'),
     '#pre_render' => array('forum_block_view_pre_render'),
     '#cache' => array(
        'keys' => $cache_keys,
        'expire' => CACHE_TEMPORARY,
     ),
     '#query' => $query,
555 556
  );
  return $block;
557
}
558

559 560 561 562 563 564 565 566 567 568 569 570 571
/**
* 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);
    $elements['forum_more'] = array('#markup' => theme('more_link', url('forum'), t('Read the latest forum topics.')));
572
  }
573
  return $elements;
Dries's avatar
 
Dries committed
574 575
}

576
/**
577
 * Implement hook_form().
578
 */
579
function forum_form($node, $form_state) {
580
  $type = node_type_get_type($node);
581
  $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => !empty($node->title) ? $node->title : '', '#required' => TRUE, '#weight' => -5);
582

583
  if (!empty($node->nid)) {
584
    $vid = variable_get('forum_nav_vocabulary', 0);
585
    $forum_terms = taxonomy_node_get_terms_by_vocabulary($node, $vid);
Dries's avatar
Dries committed
586
    // if editing, give option to leave shadows
587
    $shadow = (count($forum_terms) > 1);
588
    $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
589
  }
Dries's avatar
 
Dries committed
590

591 592 593
  $form['#submit'][] = 'forum_submit';
  // Assign the forum topic submit handler.

594
  return $form;
Dries's avatar
 
Dries committed
595 596
}

597
/**
598
 * Implement hook_term_path().
599 600 601
 */
function forum_term_path($term) {
  return 'forum/' . $term->tid;
602 603
}

Dries's avatar
 
Dries committed
604 605 606
/**
 * Returns a list of all forums for a given taxonomy id
 *
607
 * Forum objects contain the following fields
Dries's avatar
 
Dries committed
608 609 610 611 612 613 614 615 616
 * -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
617
function forum_get_forums($tid = 0) {
618

Dries's avatar
 
Dries committed
619
  $forums = array();
620
  $vid = variable_get('forum_nav_vocabulary', 0);
621
  $_forums = taxonomy_get_tree($vid, $tid);
Dries's avatar
 
Dries committed
622

Dries's avatar
 
Dries committed
623
  if (count($_forums)) {
624 625
    $query = db_select('node', 'n');
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
626
    $query->join('forum', 'f', 'f.vid = f.vid');
627 628 629
    $query->addExpression('COUNT(n.nid)', 'topic_count');
    $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
    $counts = $query
630
      ->fields('f', array('tid'))
631 632 633 634 635
      ->condition('status', 1)
      ->groupBy('tid')
      ->addTag('node_access')
      ->execute()
      ->fetchAllAssoc('tid');
Dries's avatar
 
Dries committed
636
  }
Dries's avatar
 
Dries committed
637

Dries's avatar
 
Dries committed
638 639 640 641
  foreach ($_forums as $forum) {
    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
      $forum->container = 1;
    }
Dries's avatar
 
Dries committed
642

643
    if (!empty($counts[$forum->tid])) {
Dries's avatar
 
Dries committed
644 645 646 647 648 649 650 651
      $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;
    }

652 653
    $query = db_select('node', 'n');
    $query->join('users', 'u1', 'n.uid = u1.uid');
654
    $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid));
655 656 657
    $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');
658

659 660 661 662 663 664 665 666
    $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();
667

668
    $last_post = new stdClass();
669 670 671 672 673
    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
674
    $forum->last_post = $last_post;
Dries's avatar
 
Dries committed
675

Dries's avatar
 
Dries committed
676 677 678 679
    $forums[$forum->tid] = $forum;
  }

  return $forums;
Dries's avatar
 
Dries committed
680 681
}

682 683 684 685 686
/**
 * Calculate the number of nodes the user has not yet read and are newer
 * than NODE_NEW_LIMIT.
 */
function _forum_topics_unread($term, $uid) {
687
  $query = db_select('node', 'n');
688
  $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term));
689 690 691 692 693 694 695 696 697
  $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
698 699
}

Dries's avatar
Dries committed
700
function forum_get_topics($tid, $sortby, $forum_per_page) {
Dries's avatar
 
Dries committed
701
  global $user, $forum_topic_list_header;
Dries's avatar
 
Dries committed
702

Dries's avatar
 
Dries committed
703
  $forum_topic_list_header = array(
704
    NULL,
Dries's avatar
 
Dries committed
705
    array('data' => t('Topic'), 'field' => 'n.title'),
706 707
    array('data' => t('Replies'), 'field' => 'ncs.comment_count'),
    array('data' => t('Last reply'), 'field' => 'ncs.last_comment_timestamp'),
Dries's avatar
 
Dries committed
708
  );
Dries's avatar
 
Dries committed
709

Dries's avatar
 
Dries committed
710
  $order = _forum_get_topic_order($sortby);
Dries's avatar
 
Dries committed
711
  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
Dries's avatar
 
Dries committed
712 713
    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
      $forum_topic_list_header[$i]['sort'] = $order['sort'];
Dries's avatar
 
Dries committed
714 715 716
    }
  }

717 718 719
  $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');
720
  $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $tid));
721 722 723 724 725 726 727 728 729
  $query->join('users', 'u', 'n.uid = u.uid');
  $query->addExpression('IF(ncs.last_comment_uid != 0, cu.name, ncs.last_comment_name)', 'last_comment_name');
  $query->addField('n', 'created', 'timestamp');
  $query->addField('n', 'comment', 'comment_mode');
  $query->addField('ncs', 'comment_count', 'num_comments');
  $query->addField('f', 'tid', 'forum_tid');
  $query
    ->addTag('node_access')
    ->fields('n', array('nid', 'title', 'type', 'sticky'))
730
    ->fields('f', array('tid'))
731 732 733 734 735 736 737
    ->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);
738

739
  $count_query = db_select('node', 'n');
740
  $count_query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $tid));
741 742 743 744
  $count_query->addExpression('COUNT(*)');
  $count_query
    ->condition('n.status', 1)
    ->addTag('node_access');
745

746 747
  $query->setCountQuery($count_query);
  $result = $query->execute();
748
  $topics = array();
749
  foreach ($result as $topic) {
Dries's avatar
Dries committed
750 751
    if ($user->uid) {
      // folder is new if topic is new or there are new comments since last visit
Dries's avatar
 
Dries committed
752
      if ($topic->tid != $tid) {
Dries's avatar
Dries committed
753
        $topic->new = 0;
Dries's avatar
 
Dries committed
754 755
      }
      else {
Dries's avatar
 
Dries committed
756
        $history = _forum_user_last_visit($topic->nid);
Dries's avatar
 
Dries committed
757
        $topic->new_replies = comment_num_new($topic->nid, $history);
758
        $topic->new = $topic->new_replies || ($topic->timestamp > $history);
Dries's avatar
 
Dries committed
759
      }
760 761
    }
    else {