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

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

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

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

67 68 69 70 71 72 73 74 75 76
/**
 * Fetch a forum term.
 *
 * @param $tid
 *   The ID of the term which should be loaded.
 *
 * @return
 *   An associative array containing the term data or FALSE if the term cannot be loaded, or is not part of the forum vocabulary.
 */
function forum_term_load($tid) {
77
  $result = db_query(db_rewrite_sql('SELECT t.tid, t.vid, t.name, t.description, t.weight FROM {taxonomy_term_data} t WHERE t.tid = %d AND t.vid = %d', 't', 'tid'), $tid, variable_get('forum_nav_vocabulary', ''));
78 79 80
  return db_fetch_array($result);
}

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

149 150

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

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

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

179 180
  return TRUE;
}
181

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

210 211 212 213 214
      if (!$teaser) {
        $node->content['forum_navigation'] = array(
          '#markup' => theme('forum_topic_navigation', $node),
          '#weight' => 100,
        );
215
      }
216 217 218
    }
  }
}
219

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

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

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

275 276 277 278
    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;
279
      }
280 281 282
      foreach ($node->taxonomy as $term_id) {
        if (in_array($term_id, $forum_terms)) {
          $node->tid = $term_id;
283
        }
284
      }
285
      $old_tid = db_result(db_query_range("SELECT t.tid FROM {taxonomy_term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.nid = %d ORDER BY t.vid DESC", $node->nid, 0, 1));
286 287 288 289 290 291 292
      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;
      }
    }
  }
}
293

294
/**
295
 * Implement hook_node_update().
296
 */
297
function forum_node_update($node) {
298 299
  $vid = variable_get('forum_nav_vocabulary', '');
  $vocabulary = taxonomy_vocabulary_load($vid);
300
  if (_forum_node_check_node_type($node, $vocabulary)) {
301 302 303 304 305 306 307 308 309 310
    if (empty($node->revision) && db_result(db_query('SELECT tid FROM {forum} WHERE nid=%d', $node->nid))) {
      if (!empty($node->tid)) {
        db_query('UPDATE {forum} SET tid = %d WHERE vid = %d', $node->tid, $node->vid);
      }
      // The node is removed from the forum.
      else {
        db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid);
      }
    }
    else {
311 312 313
      if (!empty($node->tid)) {
        db_query('INSERT INTO {forum} (tid, vid, nid) VALUES (%d, %d, %d)', $node->tid, $node->vid, $node->nid);
      }
314 315 316
    }
  }
}
317

318
/**
319
 * Implement hook_node_insert().
320
 */
321
function forum_node_insert($node) {
322 323
  $vid = variable_get('forum_nav_vocabulary', '');
  $vocabulary = taxonomy_vocabulary_load($vid);
324
  if (_forum_node_check_node_type($node, $vocabulary)) {
325 326 327 328 329
    if (!empty($node->tid)) {
      db_query('INSERT INTO {forum} (tid, vid, nid) VALUES (%d, %d, %d)', $node->tid, $node->vid, $node->nid);
    }
  }
}
330

331
/**
332
 * Implement hook_node_delete().
333
 */
334
function forum_node_delete($node) {
335 336
  $vid = variable_get('forum_nav_vocabulary', '');
  $vocabulary = taxonomy_vocabulary_load($vid);
337
  if (_forum_node_check_node_type($node, $vocabulary)) {
338
    db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid);
339
  }
340
}
341

342
/**
343
 * Implement hook_node_load().
344
 */
345
function forum_node_load($nodes, $types) {
346
  $vid = variable_get('forum_nav_vocabulary', '');
347 348 349 350
  // If no forum vocabulary is set up, return.
  if ($vid == '') {
    return;
  }
351
  $vocabulary = taxonomy_vocabulary_load($vid);
352 353 354 355 356 357 358 359

  $node_vids = array();
  foreach ($nodes as $node) {
    if (isset($vocabulary->nodes[$node->type])) {
      $node_vids[] = $node->vid;
    }
  }
  if (!empty($node_vids)) {
360
    $result = db_query('SELECT nid, tid FROM {forum} WHERE vid IN(:node_vids)', array(':node_vids' => $node_vids));
361 362 363
    foreach ($result as $record) {
      $nodes[$record->nid]->forum_tid = $record->tid;
    }
364
  }
365 366
}

367
/**
368
 * Implement hook_node_info().
369
 */
370
function forum_node_info() {
371 372
  return array(
    'forum' => array(
373
      'name' => t('Forum topic'),
374
      'base' => 'forum',
375
      'description' => t('A <em>forum topic</em> is the initial post to a new discussion thread within a forum.'),
376 377 378
      'title_label' => t('Subject'),
    )
  );
Dries's avatar
 
Dries committed
379 380
}

381
/**
382
 * Implement hook_access().
383
 */
384
function forum_access($op, $node, $account) {
385 386
  switch ($op) {
    case 'create':
387
      return user_access('create forum content', $account);
388
    case 'update':
389
      return user_access('edit any forum content', $account) || (user_access('edit own forum content', $account) && ($account->uid == $node->uid));
390
    case 'delete':
391
      return user_access('delete any forum content', $account) || (user_access('delete own forum content', $account) && ($account->uid == $node->uid));
Dries's avatar
 
Dries committed
392
  }
Dries's avatar
 
Dries committed
393 394
}

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

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

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

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

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

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

/**
494
 * Implement hook_block_view().
495 496 497 498
 *
 * Generates a block containing the currently active forum topics and the
 * most recently added forum topics.
 */
499 500 501 502 503
function forum_block_view($delta = '') {
  if (user_access('access content')) {
    switch ($delta) {
      case 'active':
        $title = t('Active forum topics');
504
        $query = db_select('node', 'n');
505
        $tn_alias = $query->join('taxonomy_term_node', 'tn', 'tn.vid = n.vid');
506
        $td_alias = $query->join('taxonomy_term_data', 'td', 'td.tid = tn.tid');
507 508 509 510 511 512 513 514 515 516 517
        $l_alias = $query->join('node_comment_statistics', 'l', 'n.nid = l.nid');
        $query->addField('n', 'nid', 'nid');
        $query->addField('n', 'title', 'title');
        $query->addField($l_alias, 'comment_count', 'comment_count');
        $query->addField($l_alias, 'last_comment_timestamp');
        $query->condition("n.status", 1);
        $query->condition("{$td_alias}.vid", variable_get('forum_nav_vocabulary', ''));
        $query->orderBy("{$l_alias}.last_comment_timestamp", 'DESC');
        $query->range(0, variable_get('forum_block_num_active', '5'));
        $query->addTag('node_access');
        $result = $query->execute();
518 519
        $content = node_title_list($result);
        break;
Dries's avatar
 
Dries committed
520

521 522
      case 'new':
        $title = t('New forum topics');
523
        $sql = db_rewrite_sql("SELECT n.nid, n.title, l.comment_count FROM {node} n INNER JOIN {taxonomy_term_node} tn ON tn.vid = n.vid INNER JOIN {taxonomy_term_data} td ON td.tid = tn.tid INNER JOIN {node_comment_statistics} l ON n.nid = l.nid WHERE n.status = 1 AND td.vid = %d ORDER BY n.nid DESC");
524 525 526 527 528 529 530 531 532 533
        $result = db_query_range($sql, variable_get('forum_nav_vocabulary', ''), 0, variable_get('forum_block_num_new', '5'));
        $content = node_title_list($result);
        break;
    }

    if (!empty($content)) {
      $block['subject'] = $title;
      $block['content'] = $content . theme('more_link', url('forum'), t('Read the latest forum topics.'));
      return $block;
    }
534
  }
Dries's avatar
 
Dries committed
535 536
}

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

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

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

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

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

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

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

Dries's avatar
 
Dries committed
584
  if (count($_forums)) {
Dries's avatar
 
Dries committed
585

Dries's avatar
 
Dries committed
586 587
    $counts = array();

588
    $sql = "SELECT r.tid, COUNT(n.nid) AS topic_count, SUM(l.comment_count) AS comment_count FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {taxonomy_term_node} r ON n.vid = r.vid WHERE n.status = 1 GROUP BY r.tid";
589
    $sql = db_rewrite_sql($sql);
590
    $_counts = db_query($sql);
Dries's avatar
 
Dries committed
591 592 593
    while ($count = db_fetch_object($_counts)) {
      $counts[$count->tid] = $count;
    }
Dries's avatar
 
Dries committed
594
  }
Dries's avatar
 
Dries committed
595

Dries's avatar
 
Dries committed
596 597 598 599
  foreach ($_forums as $forum) {
    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
      $forum->container = 1;
    }
Dries's avatar
 
Dries committed
600

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

    // This query does not use full ANSI syntax since MySQL 3.x does not support
    // table1 INNER JOIN table2 INNER JOIN table3 ON table2_criteria ON table3_criteria
Dries's avatar
 
Dries committed
612
    // used to join node_comment_statistics to users.
613
    $sql = "SELECT ncs.last_comment_timestamp, IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name) AS last_comment_name, ncs.last_comment_uid FROM {node} n INNER JOIN {users} u1 ON n.uid = u1.uid INNER JOIN {taxonomy_term_node} tn ON n.vid = tn.vid INNER JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid INNER JOIN {users} u2 ON ncs.last_comment_uid=u2.uid WHERE n.status = 1 AND tn.tid = %d ORDER BY ncs.last_comment_timestamp DESC";
614
    $sql = db_rewrite_sql($sql);
Dries's avatar
 
Dries committed
615
    $topic = db_fetch_object(db_query_range($sql, $forum->tid, 0, 1));
616

617
    $last_post = new stdClass();
618 619 620 621 622
    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
623
    $forum->last_post = $last_post;
Dries's avatar
 
Dries committed
624

Dries's avatar
 
Dries committed
625 626 627 628
    $forums[$forum->tid] = $forum;
  }

  return $forums;
Dries's avatar
 
Dries committed
629 630
}

631 632 633 634 635
/**
 * Calculate the number of nodes the user has not yet read and are newer
 * than NODE_NEW_LIMIT.
 */
function _forum_topics_unread($term, $uid) {
636
  $sql = "SELECT COUNT(n.nid) FROM {node} n INNER JOIN {taxonomy_term_node} tn ON n.vid = tn.vid AND tn.tid = %d LEFT JOIN {history} h ON n.nid = h.nid AND h.uid = %d WHERE n.status = 1 AND n.created > %d AND h.nid IS NULL";
637
  $sql = db_rewrite_sql($sql);
638
  return db_result(db_query($sql, $term, $uid, NODE_NEW_LIMIT));
Dries's avatar
 
Dries committed
639 640
}

Dries's avatar
Dries committed
641
function forum_get_topics($tid, $sortby, $forum_per_page) {
Dries's avatar
 
Dries committed
642
  global $user, $forum_topic_list_header;
Dries's avatar
 
Dries committed
643

Dries's avatar
 
Dries committed
644
  $forum_topic_list_header = array(
645
    NULL,
Dries's avatar
 
Dries committed
646
    array('data' => t('Topic'), 'field' => 'n.title'),
Dries's avatar
 
Dries committed
647
    array('data' => t('Replies'), 'field' => 'l.comment_count'),
Dries's avatar
 
Dries committed
648
    array('data' => t('Created'), 'field' => 'n.created'),
Dries's avatar
 
Dries committed
649
    array('data' => t('Last reply'), 'field' => 'l.last_comment_timestamp'),
Dries's avatar
 
Dries committed
650
  );
Dries's avatar
 
Dries committed
651

Dries's avatar
 
Dries committed
652
  $order = _forum_get_topic_order($sortby);
Dries's avatar
 
Dries committed
653
  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
Dries's avatar
 
Dries committed
654 655
    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
      $forum_topic_list_header[$i]['sort'] = $order['sort'];
Dries's avatar
 
Dries committed
656 657 658
    }
  }

659
  $sql = db_rewrite_sql("SELECT n.nid, r.tid, n.title, n.type, n.sticky, u.name, u.uid, n.created AS timestamp, n.comment AS comment_mode, l.last_comment_timestamp, IF(l.last_comment_uid != 0, cu.name, l.last_comment_name) AS last_comment_name, l.last_comment_uid, l.comment_count AS num_comments, f.tid AS forum_tid FROM {node_comment_statistics} l INNER JOIN {node} n ON n.nid = l.nid INNER JOIN {users} cu ON l.last_comment_uid = cu.uid INNER JOIN {taxonomy_term_node} r ON n.vid = r.vid INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {forum} f ON n.vid = f.vid WHERE n.status = 1 AND r.tid = %d");
Dries's avatar
 
Dries committed
660
  $sql .= tablesort_sql($forum_topic_list_header, 'n.sticky DESC,');
661
  $sql .= ', n.created DESC';  // Always add a secondary sort order so that the news forum topics are on top.
Dries's avatar
Dries committed
662

663
  $sql_count = db_rewrite_sql("SELECT COUNT(n.nid) FROM {node} n INNER JOIN {taxonomy_term_node} r ON n.vid = r.vid AND r.tid = %d WHERE n.status = 1");
Dries's avatar
 
Dries committed
664

Steven Wittens's avatar
Steven Wittens committed
665
  $result = pager_query($sql, $forum_per_page, 0, $sql_count, $tid);
666
  $topics = array();
Dries's avatar
 
Dries committed
667
  while ($topic = db_fetch_object($result)) {
Dries's avatar
Dries committed
668 669
    if ($user->uid) {
      // folder is new if topic is new or there are new comments since last visit
Dries's avatar
 
Dries committed
670
      if ($topic->tid != $tid) {
Dries's avatar
Dries committed
671
        $topic->new = 0;
Dries's avatar
 
Dries committed
672 673
      }
      else {
Dries's avatar
 
Dries committed
674
        $history = _forum_user_last_visit($topic->nid);
Dries's avatar
 
Dries committed
675
        $topic->new_replies = comment_num_new($topic->nid, $history);
676
        $topic->new = $topic->new_replies || ($topic->timestamp > $history);
Dries's avatar
 
Dries committed
677
      }
678 679
    }
    else {
680
      // Do not track "new replies" status for topics if the user is anonymous.
Dries's avatar
Dries committed
681 682
      $topic->new_replies = 0;
      $topic->new = 0;
683
    }
Dries's avatar
 
Dries committed
684

Dries's avatar
 
Dries committed
685
    if ($topic->num_comments > 0) {
686
      $last_reply = new stdClass();
Dries's avatar
 
Dries committed
687 688 689 690 691
      $last_reply->timestamp = $topic->last_comment_timestamp;
      $last_reply->name = $topic->last_comment_name;
      $last_reply->uid = $topic->last_comment_uid;
      $topic->last_reply = $last_reply;
    }
Dries's avatar
 
Dries committed
692 693 694
    $topics[] = $topic;
  }

Dries's avatar
Dries committed
695
  return $topics;
Dries's avatar
 
Dries committed
696 697
}

Dries's avatar
 
Dries committed
698
/**
699
 * Process variables for forums.tpl.php
700 701 702 703 704 705 706 707
 *
 * The $variables array contains the following arguments:
 * - $forums
 * - $topics
 * - $parents
 * - $tid
 * - $sortby
 * - $forum_per_page
Dries's avatar
 
Dries committed
708
 *
709
 * @see forums.tpl.php
Dries's avatar
 
Dries committed
710
 */
711
function template_preprocess_forums(&$variables) {
Dries's avatar
 
Dries committed
712
  global $user;
Dries's avatar
 
Dries committed
713

714 715
  $vid = variable_get('forum_nav_vocabulary', '');
  $vocabulary = taxonomy_vocabulary_load($vid);
716
  $title = !empty($vocabulary->name) ? $vocabulary->name : '';
Dries's avatar
 
Dries committed
717

Dries's avatar
 
Dries committed
718
  // Breadcrumb navigation:
719
  $breadcrumb[] = l(t('Home'), NULL);
720
  if ($variables['tid']) {
721
    $breadcrumb[] = l($vocabulary->name, 'forum');
Dries's avatar
 
Dries committed
722
  }
723 724 725 726
  if ($variables['parents']) {
    $variables['parents'] = array_reverse($variables['parents']);
    foreach ($variables['parents'] as $p) {
      if ($p->tid == $variables['tid']) {
727
        $title = $p->name;
Dries's avatar
 
Dries committed
728 729
      }
      else {
730
        $breadcrumb[] = l($p->name, 'forum/' . $p->tid);
Dries's avatar
 
Dries committed
731 732 733
      }
    }
  }