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

Dries's avatar
   
Dries committed
4
5
6
7
8
/**
 * @file
 * Enable threaded discussions about general topics.
 */

9
10
11
/**
 * Implementation of hook_help().
 */
12
13
function forum_help($path, $arg) {
  switch ($path) {
14
    case 'admin/help#forum':
15
16
17
18
19
20
      $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>Create 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>';
      $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>';
      $output .= '<li>'. t('selecting <em>Read only</em> under <em>Comment settings</em> while editing a forum topic will lock (prevent new comments) on the thread.') .'</li>';
21
      $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>';
22
      $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/content/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/content/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/content/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/content/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
37
38
39
/**
 * Implementation of hook_theme()
 */
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
77
78
79
80
/**
 * 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) {
  $result = db_query(db_rewrite_sql('SELECT t.tid, t.vid, t.name, t.description, t.weight FROM {term_data} t WHERE t.tid = %d AND t.vid = %d', 't', 'tid'), $tid, variable_get('forum_nav_vocabulary', ''));
  return db_fetch_array($result);
}

81
82
83
/**
 * Implementation of hook_menu().
 */
84
85
function forum_menu() {
  $items['forum'] = array(
86
    'title' => 'Forums',
87
88
89
    'page callback' => 'forum_page',
    'access arguments' => array('access content'),
    'type' => MENU_SUGGESTED_ITEM,
90
    'file' => 'forum.pages.inc',
91
92
  );
  $items['admin/content/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/content/forum/list'] = array(
101
    'title' => 'List',
102
103
104
105
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/content/forum/add/container'] = array(
106
    'title' => 'Add container',
107
108
109
    'page callback' => 'forum_form_main',
    'page arguments' => array('container'),
    'type' => MENU_LOCAL_TASK,
110
    'parent' => 'admin/content/forum',
111
    'file' => 'forum.admin.inc',
112
113
  );
  $items['admin/content/forum/add/forum'] = array(
114
    'title' => 'Add forum',
115
116
117
    'page callback' => 'forum_form_main',
    'page arguments' => array('forum'),
    'type' => MENU_LOCAL_TASK,
118
    'parent' => 'admin/content/forum',
119
    'file' => 'forum.admin.inc',
120
121
  );
  $items['admin/content/forum/settings'] = array(
122
    'title' => 'Settings',
123
124
125
126
    'page callback' => 'drupal_get_form',
    'page arguments' => array('forum_admin_settings'),
    'weight' => 5,
    'type' => MENU_LOCAL_TASK,
127
    'parent' => 'admin/content/forum',
128
    'file' => 'forum.admin.inc',
129
  );
130
  $items['admin/content/forum/edit/%forum_term'] = array(
131
132
    'page callback' => 'forum_form_main',
    'type' => MENU_CALLBACK,
133
    'file' => 'forum.admin.inc',
134
  );
135
  $items['admin/content/forum/edit/container/%forum_term'] = array(
136
    'title' => 'Edit container',
137
    'page callback' => 'forum_form_main',
138
    'page arguments' => array('container', 5),
139
    'type' => MENU_CALLBACK,
140
    'file' => 'forum.admin.inc',
141
  );
142
  $items['admin/content/forum/edit/forum/%forum_term'] = array(
143
    'title' => 'Edit forum',
144
145
    'page callback' => 'forum_form_main',
    'page arguments' => array('forum', 5),
146
    'type' => MENU_CALLBACK,
147
    'file' => 'forum.admin.inc',
148
149
150
  );
  return $items;
}
151

152
153
154
155

/**
 * Implementation of hook_init().
 */
156
157
158
function forum_init() {
  drupal_add_css(drupal_get_path('module', 'forum') .'/forum.css');
}
159

160
161
162
163
/**
 * Implementation of hook_nodeapi().
 */
function forum_nodeapi(&$node, $op, $teaser, $page) {
164
165
166
167
  // We are going to return if $node->type is not one of the node
  // types assigned to the forum vocabulary.  If forum_nav_vocabulary
  // is undefined or the vocabulary does not exist, it clearly cannot
  // be assigned to $node->type, so return to avoid E_ALL warnings.
168
169
  $vid = variable_get('forum_nav_vocabulary', '');
  $vocabulary = taxonomy_vocabulary_load($vid);
170
171
172
  if (empty($vocabulary)) {
    return;
  }
173
174
175
176
177
178
179
180

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

  switch ($op) {
    case 'view':
181
      if ($page && taxonomy_node_get_terms_by_vocabulary($node, $vid) && $tree = taxonomy_get_tree($vid)) {
182
        // Get the forum terms from the (cached) tree
183
184
        foreach ($tree as $term) {
          $forum_terms[] = $term->tid;
185
186
187
188
189
190
191
        }
        foreach ($node->taxonomy as $term_id => $term) {
          if (in_array($term_id, $forum_terms)) {
            $node->tid = $term_id;
          }
        }
        // Breadcrumb navigation
192
193
        $breadcrumb[] = l(t('Home'), NULL);
        $breadcrumb[] = l($vocabulary->name, 'forum');
194
195
196
        if ($parents = taxonomy_get_parents_all($node->tid)) {
          $parents = array_reverse($parents);
          foreach ($parents as $p) {
197
            $breadcrumb[] = l($p->name, 'forum/'. $p->tid);
198
199
          }
        }
200
        drupal_set_breadcrumb($breadcrumb);
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237

        if (!$teaser) {
          $node->content['forum_navigation'] = array(
            '#value' => theme('forum_topic_navigation', $node),
            '#weight' => 100,
          );
        }
      }
      break;

    case 'prepare':
      if (empty($node->nid)) {
        // New topic
        $node->taxonomy[arg(3)]->vid = $vid;
        $node->taxonomy[arg(3)]->tid = arg(3);
      }
      break;

    // Check in particular that only a "leaf" term in the associated taxonomy
    // vocabulary is selected, not a "container" term.
    case 'validate':
      if ($node->taxonomy) {
        // Extract the node's proper topic ID.
        $vocabulary = $vid;
        $containers = variable_get('forum_containers', array());
        foreach ($node->taxonomy as $term) {
          if (db_result(db_query('SELECT COUNT(*) FROM {term_data} WHERE tid = %d AND vid = %d', $term, $vocabulary))) {
            if (in_array($term, $containers)) {
              $term = taxonomy_get_term($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)));
            }
          }
        }
      }
      break;

    // Assign forum taxonomy when adding a topic from within a forum.
238
    case 'presave':
239
240
241
      // Make sure all fields are set properly:
      $node->icon = !empty($node->icon) ? $node->icon : '';

242
243
244
245
      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;
246
        }
247
        foreach ($node->taxonomy as $term_id) {
248
249
250
251
          if (in_array($term_id, $forum_terms)) {
            $node->tid = $term_id;
          }
        }
252
        $old_tid = db_result(db_query_range("SELECT t.tid FROM {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));
253
254
255
        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;
256
257
258
        }
      }
      break;
259

260
    case 'update':
261
262
263
264
265
266
267
268
      if (!$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);
        }
269
270
        break;
      }
271
272
      // Deliberate no break -- for new revisions and for previously unassigned terms we need an insert.

273
    case 'insert':
274
275
276
      if (!empty($node->tid)) {
        db_query('INSERT INTO {forum} (tid, vid, nid) VALUES (%d, %d, %d)', $node->tid, $node->vid, $node->nid);
      }
277
      break;
278

279
280
281
    case 'delete':
      db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid);
      break;
282

283
    case 'load':
284
      return db_fetch_array(db_query('SELECT tid AS forum_tid FROM {forum} WHERE vid = %d', $node->vid));
285
286
287
  }

  return;
288
289
}

290
/**
291
 * Implementation of hook_node_info().
292
 */
293
function forum_node_info() {
294
295
  return array(
    'forum' => array(
296
      'name' => t('Forum topic'),
297
      'module' => 'forum',
298
      'description' => t('A <em>forum topic</em> is the initial post to a new discussion thread within a forum.'),
299
300
301
      'title_label' => t('Subject'),
    )
  );
Dries's avatar
   
Dries committed
302
303
}

304
305
306
/**
 * Implementation of hook_access().
 */
307
function forum_access($op, $node, $account) {
Dries's avatar
   
Dries committed
308
  if ($op == 'create') {
309
    return user_access('create forum topics', $account);
Dries's avatar
   
Dries committed
310
  }
Dries's avatar
   
Dries committed
311
312

  if ($op == 'update' || $op == 'delete') {
313
    if (user_access('edit own forum topics', $account) && ($account->uid == $node->uid)) {
Dries's avatar
   
Dries committed
314
315
      return TRUE;
    }
316
317
318
    if (user_access('edit any forum topic')) {
      return TRUE;
    }
Dries's avatar
   
Dries committed
319
  }
Dries's avatar
   
Dries committed
320
321
}

322
323
324
/**
 * Implementation of hook_perm().
 */
Dries's avatar
   
Dries committed
325
function forum_perm() {
326
  return array('create forum topics', 'edit own forum topics', 'edit any forum topic', 'administer forums');
Dries's avatar
   
Dries committed
327
}
Dries's avatar
   
Dries committed
328

329
330
331
/**
 * Implementation of hook_taxonomy().
 */
332
function forum_taxonomy($op, $type, $term = NULL) {
333
  if ($op == 'delete' && $term['vid'] == variable_get('forum_nav_vocabulary', '')) {
334
335
    switch ($type) {
      case 'term':
336
        $results = db_query('SELECT tn.nid FROM {term_node} tn WHERE tn.tid = %d', $term['tid']);
337
338
339
340
341
342
343
        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());
344
345
        $key = array_search($term['tid'], $containers);
        if ($key !== FALSE) {
346
347
348
349
350
351
          unset($containers[$key]);
        }
        variable_set('forum_containers', $containers);
        break;
      case 'vocabulary':
        variable_del('forum_nav_vocabulary');
Dries's avatar
   
Dries committed
352
    }
353
354
  }
}
Dries's avatar
   
Dries committed
355

356
357
358
/**
 * Implementation of hook_form_alter().
 */
359
function forum_form_alter(&$form, $form_state, $form_id) {
360
  // Hide critical options from forum vocabulary
361
  if ($form_id == 'taxonomy_form_vocabulary') {
362
363
    $vid = variable_get('forum_nav_vocabulary', '');
    if (isset($form['vid']) && $form['vid']['#value'] == $vid) {
364
365
366
367
      $form['help_forum_vocab'] = array(
        '#value' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
        '#weight' => -1,
      );
368
      $form['nodes']['#required'] = TRUE;
369
      $form['hierarchy'] = array('#type' => 'value', '#value' => 1);
370
371
372
373
      $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);
374
      unset($form['delete']);
375
376
    }
  }
377
378
379
380
  // Hide multiple parents select from forum terms.
  if ($form_id == 'taxonomy_form_term') {
    unset($form['advanced']['parent']);
  }
381
382
383
384
385
386
  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 -');
  }
387
388
}

389
390
391
/**
 * Implementation of hook_load().
 */
Dries's avatar
   
Dries committed
392
function forum_load($node) {
393
  $forum = db_fetch_object(db_query('SELECT * FROM {term_node} WHERE vid = %d', $node->vid));
Dries's avatar
   
Dries committed
394
395
396
397

  return $forum;
}

398
399
400
401
402
403
/**
 * Implementation of hook_block().
 *
 * Generates a block containing the currently active forum topics and the
 * most recently added forum topics.
 */
404
405
406
function forum_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
Dries's avatar
   
Dries committed
407
408
      $blocks[0]['info'] = t('Active forum topics');
      $blocks[1]['info'] = t('New forum topics');
409
      return $blocks;
Dries's avatar
   
Dries committed
410

411
    case 'configure':
412
      $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)));
413
      return $form;
Dries's avatar
   
Dries committed
414

415
    case 'save':
416
      variable_set('forum_block_num_'. $delta, $edit['forum_block_num_'. $delta]);
417
      break;
Dries's avatar
   
Dries committed
418

419
420
    case 'view':
      if (user_access('access content')) {
Dries's avatar
   
Dries committed
421
422
        switch ($delta) {
          case 0:
423
            $title = t('Active forum topics');
424
425
            $sql = db_rewrite_sql("SELECT n.nid, n.title, l.comment_count, l.last_comment_timestamp FROM {node} n INNER JOIN {term_node} tn ON tn.nid = n.nid INNER JOIN {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 l.last_comment_timestamp DESC");
            $result = db_query_range($sql, variable_get('forum_nav_vocabulary', ''), 0, variable_get('forum_block_num_0', '5'));
426
            $content = node_title_list($result);
427
            break;
Dries's avatar
   
Dries committed
428
429

          case 1:
430
            $title = t('New forum topics');
431
432
            $sql = db_rewrite_sql("SELECT n.nid, n.title, l.comment_count FROM {node} n INNER JOIN {term_node} tn ON tn.nid = n.nid INNER JOIN {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");
            $result = db_query_range($sql, variable_get('forum_nav_vocabulary', ''), 0, variable_get('forum_block_num_1', '5'));
433
            $content = node_title_list($result);
434
            break;
Dries's avatar
   
Dries committed
435
        }
Dries's avatar
   
Dries committed
436

437
438
        if (!empty($content)) {
          $block['subject'] = $title;
439
          $block['content'] = $content . theme('more_link', url('forum'), t('Read the latest forum topics.'));
440
          return $block;
441
442
443
        }
      }
  }
Dries's avatar
   
Dries committed
444
445
}

446
447
448
/**
 * Implementation of hook_form().
 */
449
function forum_form(&$node, $form_state) {
450
  $type = node_get_types('type', $node);
451
  $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => !empty($node->title) ? $node->title : '', '#required' => TRUE, '#weight' => -5);
452

453
  if (!empty($node->nid)) {
454
455
    $vid = variable_get('forum_nav_vocabulary', '');
    $forum_terms = taxonomy_node_get_terms_by_vocabulary($node, $vid);
Dries's avatar
Dries committed
456
    // if editing, give option to leave shadows
457
    $shadow = (count($forum_terms) > 1);
458
    $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
459
  }
Dries's avatar
   
Dries committed
460

461
  $form['body_field'] = node_body_field($node, $type->body_label, 1);
Dries's avatar
   
Dries committed
462

463
464
465
  $form['#submit'][] = 'forum_submit';
  // Assign the forum topic submit handler.

466
  return $form;
Dries's avatar
   
Dries committed
467
468
}

469
function forum_link_alter(&$links, $node) {
470
  foreach ($links as $module => $link) {
471
    if (strstr($module, 'taxonomy_term')) {
472
473
      // Link back to the forum and not the taxonomy term page. We'll only
      // do this if the taxonomy term in question belongs to forums.
474
      $tid = str_replace('taxonomy/term/', '', $link['href']);
475
      $vid = variable_get('forum_nav_vocabulary', '');
476
      $term = taxonomy_get_term($tid);
477
      if ($term->vid == $vid) {
478
        $links[$module]['href'] = str_replace('taxonomy/term', 'forum', $link['href']);
479
      }
480
481
    }
  }
482
483
}

Dries's avatar
   
Dries committed
484
485
486
/**
 * Returns a list of all forums for a given taxonomy id
 *
487
 * Forum objects contain the following fields
Dries's avatar
   
Dries committed
488
489
490
491
492
493
494
495
496
 * -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
497
function forum_get_forums($tid = 0) {
498

Dries's avatar
   
Dries committed
499
  $forums = array();
500
501
  $vid = variable_get('forum_nav_vocabulary', '');
  $_forums = taxonomy_get_tree($vid, $tid);
Dries's avatar
   
Dries committed
502

Dries's avatar
   
Dries committed
503
  if (count($_forums)) {
Dries's avatar
   
Dries committed
504

Dries's avatar
   
Dries committed
505
506
    $counts = array();

507
    $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 {term_node} r ON n.nid = r.nid WHERE n.status = 1 GROUP BY r.tid";
508
    $sql = db_rewrite_sql($sql);
509
    $_counts = db_query($sql);
Dries's avatar
   
Dries committed
510
511
512
    while ($count = db_fetch_object($_counts)) {
      $counts[$count->tid] = $count;
    }
Dries's avatar
   
Dries committed
513
  }
Dries's avatar
   
Dries committed
514

Dries's avatar
   
Dries committed
515
516
517
518
  foreach ($_forums as $forum) {
    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
      $forum->container = 1;
    }
Dries's avatar
   
Dries committed
519

520
    if (!empty($counts[$forum->tid])) {
Dries's avatar
   
Dries committed
521
522
523
524
525
526
527
528
529
530
      $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
531
    // used to join node_comment_statistics to users.
532
    $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 {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";
533
    $sql = db_rewrite_sql($sql);
Dries's avatar
   
Dries committed
534
    $topic = db_fetch_object(db_query_range($sql, $forum->tid, 0, 1));
535

536
    $last_post = new stdClass();
537
538
539
540
541
    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
542
    $forum->last_post = $last_post;
Dries's avatar
   
Dries committed
543

Dries's avatar
   
Dries committed
544
545
546
547
    $forums[$forum->tid] = $forum;
  }

  return $forums;
Dries's avatar
   
Dries committed
548
549
}

550
551
552
553
554
/**
 * Calculate the number of nodes the user has not yet read and are newer
 * than NODE_NEW_LIMIT.
 */
function _forum_topics_unread($term, $uid) {
555
  $sql = "SELECT COUNT(n.nid) FROM {node} n INNER JOIN {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";
556
  $sql = db_rewrite_sql($sql);
557
  return db_result(db_query($sql, $term, $uid, NODE_NEW_LIMIT));
Dries's avatar
   
Dries committed
558
559
}

Dries's avatar
Dries committed
560
function forum_get_topics($tid, $sortby, $forum_per_page) {
Dries's avatar
   
Dries committed
561
  global $user, $forum_topic_list_header;
Dries's avatar
   
Dries committed
562

Dries's avatar
   
Dries committed
563
  $forum_topic_list_header = array(
564
    array('data' => '&nbsp;', 'field' => NULL),
Dries's avatar
   
Dries committed
565
    array('data' => t('Topic'), 'field' => 'n.title'),
Dries's avatar
   
Dries committed
566
    array('data' => t('Replies'), 'field' => 'l.comment_count'),
Dries's avatar
   
Dries committed
567
    array('data' => t('Created'), 'field' => 'n.created'),
Dries's avatar
   
Dries committed
568
    array('data' => t('Last reply'), 'field' => 'l.last_comment_timestamp'),
Dries's avatar
   
Dries committed
569
  );
Dries's avatar
   
Dries committed
570

Dries's avatar
   
Dries committed
571
  $order = _forum_get_topic_order($sortby);
Dries's avatar
   
Dries committed
572
  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
Dries's avatar
   
Dries committed
573
574
    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
      $forum_topic_list_header[$i]['sort'] = $order['sort'];
Dries's avatar
   
Dries committed
575
576
577
578
    }
  }

  $term = taxonomy_get_term($tid);
Dries's avatar
   
Dries committed
579

580
  $sql = db_rewrite_sql("SELECT n.nid, r.tid, n.title, 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 {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
581
  $sql .= tablesort_sql($forum_topic_list_header, 'n.sticky DESC,');
582
  $sql .= ', n.created DESC';  // Always add a secondary sort order so that the news forum topics are on top.
Dries's avatar
Dries committed
583

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

Steven Wittens's avatar
Steven Wittens committed
586
  $result = pager_query($sql, $forum_per_page, 0, $sql_count, $tid);
587
  $topics = array();
Dries's avatar
   
Dries committed
588
  while ($topic = db_fetch_object($result)) {
Dries's avatar
Dries committed
589
590
    if ($user->uid) {
      // folder is new if topic is new or there are new comments since last visit
Dries's avatar
   
Dries committed
591
      if ($topic->tid != $tid) {
Dries's avatar
Dries committed
592
        $topic->new = 0;
Dries's avatar
   
Dries committed
593
594
      }
      else {
Dries's avatar
   
Dries committed
595
        $history = _forum_user_last_visit($topic->nid);
Dries's avatar
   
Dries committed
596
        $topic->new_replies = comment_num_new($topic->nid, $history);
597
        $topic->new = $topic->new_replies || ($topic->timestamp > $history);
Dries's avatar
   
Dries committed
598
      }
599
600
    }
    else {
601
      // Do not track "new replies" status for topics if the user is anonymous.
Dries's avatar
Dries committed
602
603
      $topic->new_replies = 0;
      $topic->new = 0;
604
    }
Dries's avatar
   
Dries committed
605

Dries's avatar
   
Dries committed
606
    if ($topic->num_comments > 0) {
607
      $last_reply = new stdClass();
Dries's avatar
   
Dries committed
608
609
610
611
612
      $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
613
614
615
    $topics[] = $topic;
  }

Dries's avatar
Dries committed
616
  return $topics;
Dries's avatar
   
Dries committed
617
618
}

Dries's avatar
   
Dries committed
619
620
621
/**
 * Finds the first unread node for a given forum.
 */
Dries's avatar
Dries committed
622
function _forum_new($tid) {
Dries's avatar
   
Dries committed
623
624
  global $user;

625
  $sql = "SELECT n.nid FROM {node} n LEFT JOIN {history} h ON n.nid = h.nid AND h.uid = %d INNER JOIN {term_node} r ON n.nid = r.nid AND r.tid = %d WHERE n.status = 1 AND h.nid IS NULL AND n.created > %d ORDER BY created";
626
  $sql = db_rewrite_sql($sql);
Dries's avatar
   
Dries committed
627
  $nid = db_result(db_query_range($sql, $user->uid, $tid, NODE_NEW_LIMIT, 0, 1));
Dries's avatar
   
Dries committed
628
629
630
631

  return $nid ? $nid : 0;
}

Dries's avatar
   
Dries committed
632
/**
633
 * Process variables for forums.tpl.php
634
635
636
637
638
639
640
641
 *
 * The $variables array contains the following arguments:
 * - $forums
 * - $topics
 * - $parents
 * - $tid
 * - $sortby
 * - $forum_per_page
Dries's avatar
   
Dries committed
642
 *
643
 * @see forums.tpl.php
Dries's avatar
   
Dries committed
644
 */
645
function template_preprocess_forums(&$variables) {
Dries's avatar
   
Dries committed
646
  global $user;
Dries's avatar
   
Dries committed
647

648
649
  $vid = variable_get('forum_nav_vocabulary', '');
  $vocabulary = taxonomy_vocabulary_load($vid);
650
  $title = !empty($vocabulary->name) ? $vocabulary->name : '';
Dries's avatar
   
Dries committed
651

Dries's avatar
   
Dries committed
652
  // Breadcrumb navigation:
653
  $breadcrumb[] = l(t('Home'), NULL);
654
  if ($variables['tid']) {
655
    $breadcrumb[] = l($vocabulary->name, 'forum');
Dries's avatar
   
Dries committed
656
  }
657
658
659
660
  if ($variables['parents']) {
    $variables['parents'] = array_reverse($variables['parents']);
    foreach ($variables['parents'] as $p) {
      if ($p->tid == $variables['tid']) {
661
        $title = $p->name;
Dries's avatar
   
Dries committed
662
663
      }
      else {
664
        $breadcrumb[] = l($p->name, 'forum/'. $p->tid);
Dries's avatar
   
Dries committed
665
666
667
      }
    }
  }
668
  drupal_set_breadcrumb($breadcrumb);
Dries's avatar
Dries committed
669
  drupal_set_title(check_plain($title));
670

671
  if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
672
673
674
675
676
677
678
679
680
    // Format the "post new content" links listing.
    $forum_types = array();

    // Loop through all node types for forum vocabulary.
    foreach ($vocabulary->nodes as $type) {
      // Check if the current user has the 'create' permission for this node type.
      if (node_access('create', $type)) {
        // Fetch the "General" name of the content type;
        // Push the link with title and url to the array.
681
        $forum_types[$type] = array('title' => t('Post new @node_type', array('@node_type' => node_get_types('name', $type))), 'href' => "node/add/$type/$variables[tid]");
682
      }
Dries's avatar
   
Dries committed
683
    }
684

685
    if (empty($forum_types)) {
686
687
      // The user is logged-in; but denied access to create any new forum content type.
      if ($user->uid) {
688
        $forum_types['disallowed'] = array('title' => t('You are not allowed to post new content in forum.'));
689
690
691
      }
      // The user is not logged-in; and denied access to create any new forum content type.
      else {
692
        $forum_types['login'] = array('title' => t('<a href="@login">Login</a> to post new content in forum.', array('@login' => url('user/login', array('query' => drupal_get_destination())))), 'html' => TRUE);
693
      }
Dries's avatar
   
Dries committed
694
    }
695
    $variables['links'] = $forum_types;
Dries's avatar
   
Dries committed
696

697
698
699
700
701
702
    if (!empty($variables['forums'])) {
      $variables['forums'] = theme('forum_list', $variables['forums'], $variables['parents'], $variables['tid']);
    }
    else {
      $variables['forums'] = '';
    }
Dries's avatar
   
Dries committed
703

704
705
706
    if ($variables['tid'] && !in_array($variables['tid'], variable_get('forum_containers', array()))) {
      $variables['topics'] = theme('forum_topic_list', $variables['tid'], $variables['topics'], $variables['sortby'], $variables['forum_per_page']);
      drupal_add_feed(url('taxonomy/term/'. $variables['tid'] .'/0/feed'), 'RSS - '. $title);
Dries's avatar
   
Dries committed
707
    }
708
709
710
711
712
    else {
      $variables['topics'] = '';
    }

    // Provide separate template suggestions based on what's being output. Topic id is also accounted for.
713
    // Check both variables to be safe then the inverse. Forums with topic ID's take precedence.
714
    if ($variables['forums'] && !$variables['topics']) {
715
      $variables['template_files'][] = 'forums-containers';
716
      $variables['template_files'][] = 'forums-'. $variables['tid'];
717
      $variables['template_files'][] = 'forums-containers-'. $variables['tid'];
718
    }
719
    elseif (!$variables['forums'] && $variables['topics']) {
720
      $variables['template_files'][] = 'forums-topics';
721
      $variables['template_files'][] = 'forums-'. $variables['tid'];
722
      $variables['template_files'][] = 'forums-topics-'. $variables['tid'];
723
    }
724
725
726
    else {
      $variables['template_files'][] = 'forums-'. $variables['tid'];
    }
727

Dries's avatar
   
Dries committed
728
729
  }
  else {
730
    drupal_set_title(t('No forums defined'));
731
732
733
    $variables['links'] = array();
    $variables['forums'] = '';
    $variables['topics'] = '';
Dries's avatar
   
Dries committed
734
735
736
  }
}

Dries's avatar
   
Dries committed
737
/**
738
 * Process variables to format a forum listing.
Dries's avatar
   
Dries committed
739
 *
740
741
742
743
744
745
746
 * $variables contains the following information:
 * - $forums
 * - $parents
 * - $tid
 *
 * @see forum-list.tpl.php
 * @see theme_forum_list()
Dries's avatar
   
Dries committed
747
 */
748
function template_preprocess_forum_list(&$variables) {
Dries's avatar
   
Dries committed
749
  global $user;
750
  $row = 0;
751
752
753
754
755
756
  // Sanitize each forum so that the template can safely print the data.
  foreach ($variables['forums'] as $id => $forum) {
    $variables['forums'][$id]->description = !empty($forum->description) ? filter_xss_admin($forum->description) : '';
    $variables['forums'][$id]->link = url("forum/$forum->tid");
    $variables['forums'][$id]->name = check_plain($forum->name);
    $variables['forums'][$id]->is_container = !empty($forum->container);
757
758
759
760
761
762
763
    $variables['forums'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
    $row++;

    $variables['forums'][$id]->new_text = '';
    $variables['forums'][$id]->new_url = '';
    $variables['forums'][$id]->new_topics = 0;
    $variables['forums'][$id]->old_topics = $forum->num_topics;
764
765
766
    if ($user->uid) {
      $variables['forums'][$id]->new_topics = _forum_topics_unread($forum->tid, $user->uid);
      if ($variables['forums'][$id]->new_topics) {
767
        $variables['forums'][$id]->new_text = t('!count new', array('!count' => $variables['forums'][$id]->new_topics));
768
        $variables['forums'][$id]->new_url = url("forum/$forum->tid", array('fragment' => 'new'));
Dries's avatar
   
Dries committed
769
      }
770
      $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
Dries's avatar
   
Dries committed
771
    }
772
    $variables['forums'][$id]->last_reply = theme('forum_submitted', $forum->last_post);
Dries's avatar
   
Dries committed
773
  }
774
775
776
  // Give meaning to $tid for themers. $tid actually stands for term id.
  $variables['forum_id'] = $variables['tid'];
  unset($variables['tid']);
Dries's avatar
   
Dries committed
777
778
}

Dries's avatar
   
Dries committed
779
/**
780
781
782
783
784
785
786
 * Preprocess variables to format the topic listing.
 *
 * $variables contains the following data:
 * - $tid
 * - $topics
 * - $sortby
 * - $forum_per_page
Dries's avatar
   
Dries committed
787
 *
788
789
 * @see forum-topic-list.tpl.php
 * @see theme_forum_topic_list()
Dries's avatar
   
Dries committed
790
 */
791
function template_preprocess_forum_topic_list(&$variables) {
Dries's avatar
   
Dries committed
792
  global $forum_topic_list_header;
Dries's avatar
   
Dries committed
793

794
795
796
797
798
799
800
801
802
803
  // Create the tablesorting header.
  $ts = tablesort_init($forum_topic_list_header);
  $header = '';
  foreach ($forum_topic_list_header as $cell) {
    $cell = tablesort_header($cell, $forum_topic_list_header, $ts);
    $header .= _theme_table_cell($cell, TRUE);
  }
  $variables['header'] = $header;

  if (!empty($variables['topics'])) {
804
    $row = 0;
805
806
    foreach ($variables['topics'] as $id => $topic) {
      $variables['topics'][$id]->icon = theme('forum_icon', $topic->new, $topic->num_comments, $topic->comment_mode, $topic->sticky);
807
808
809
      $variables['topics'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
      $row++;

810
811
812
813
814
815
816
817
818
819
820
821
822
      // We keep the actual tid in forum table, if it's different from the
      // current tid then it means the topic appears in two forums, one of
      // them is a shadow copy.
      if ($topic->forum_tid != $variables['tid']) {
        $variables['topics'][$id]->moved = TRUE;
        $variables['topics'][$id]->title = check_plain($topic->title);
        $variables['topics'][$id]->message = l(t('This topic has been moved'), "forum/$topic->forum_tid");
      }
      else {
        $variables['topics'][$id]->moved = FALSE;
        $variables['topics'][$id]->title = l($topic->title, "node/$topic->nid");
        $variables['topics'][$id]->message = '';
      }
823
824
825
826
827
828
829
      $variables['topics'][$id]->created = theme('forum_submitted', $topic);
      $variables['topics'][$id]->last_reply = theme('forum_submitted', isset($topic->last_reply) ? $topic->last_reply : NULL);

      $variables['topics'][$id]->new_text = '';
      $variables['topics'][$id]->new_url = '';
      if ($topic->new_replies) {
        $variables['topics'][$id]->new_text = t('!count new', array('!count' => $variables['forums'][$id]->new_topics));
830
        $variables['topics'][$id]->new_url = url("node/$topic->nid", array('query' => comment_new_page_count($topic->num_comments, $topic->new_replies, $topic), 'fragment' => 'new'));
831
      }
832

Dries's avatar
   
Dries committed
833
    }
Dries's avatar
   
Dries committed
834
  }
835
836
837
838
  else {
    // Make this safe for the template
    $variables['topics'] = array();
  }
839
840
841
  // Give meaning to $tid for themers. $tid actually stands for term id.
  $variables['topic_id'] = $variables['tid'];
  unset($variables['tid']);
Dries's avatar
   
Dries committed
842

843
  $variables['pager'] = theme('pager', NULL, $variables['forum_per_page'], 0);
Dries's avatar
   
Dries committed
844
845
}

846
/**
847
 * Process variables to format the icon for each individual topic.
848
 *
849
850
851
852
853
854
855
856
 * $variables contains the following data:
 * - $new_posts
 * - $num_posts = 0
 * - $comment_mode = 0
 * - $sticky = 0
 *
 * @see forum-icon.tpl.php
 * @see theme_forum_icon()
857
 */
858
859
860
861
function template_preprocess_forum_icon(&$variables) {
  $variables['hot_threshold'] = variable_get('forum_hot_topic', 15);
  if ($variables['num_posts'] > $variables['hot_threshold']) {
    $variables['icon'] = $variables['new_posts'] ? 'hot-new' : 'hot';
Dries's avatar
   
Dries committed
862
863
  }
  else {
864
    $variables['icon'] = $variables['new_posts'] ? 'new' : 'default';
Dries's avatar
   
Dries committed
865
  }
Dries's avatar
   
Dries committed
866

867
868
  if ($variables['comment_mode'] == COMMENT_NODE_READ_ONLY || $variables['comment_mode'] == COMMENT_NODE_DISABLED) {
    $variables['icon'] = 'closed';
869
870
  }

871
872
  if ($variables['sticky'] == 1) {
    $variables['icon'] = 'sticky';
873
  }
874
875
876
}

/**
877
878
879
 * Preprocess variables to format the next/previous forum topic navigation links.
 *
 * $variables contains $node.
880
 *
881
882
 * @see forum-topic-navigation.tpl.php
 * @see theme_forum_topic_navigation
883
 */
884
function template_preprocess_forum_topic_navigation(&$variables) {
885
886
887
  $output = '';

  // get previous and next topic
888
  $sql = "SELECT n.nid, n.title, n.sticky, l.comment_count, l.last_comment_timestamp FROM {node} n INNER JOIN {node_comment_statistics} l ON n.nid = l.nid INNER JOIN {term_node} r ON n.nid = r.nid AND r.tid = %d WHERE n.status = 1 ORDER BY n.sticky DESC, ". _forum_get_topic_order_sql(variable_get('forum_order', 1));
889
890
891
  $result = db_query(db_rewrite_sql($sql), isset($variables['node']->tid) ? $variables['node']->tid : 0);

  $stop = $variables['prev'] = $variables['next'] = 0;
892
893
894

  while ($topic = db_fetch_object($result)) {
    if ($stop == 1) {
895
896
897
      $variables['next'] = $topic->nid;
      $variables['next_title'] = check_plain($topic->title);
      $variables['next_url'] = url("node/$topic->nid");
898
899
      break;
    }
900
    if ($topic->nid == $variables['node']->nid) {
901
902
903
      $stop = 1;
    }
    else {
904
905
906
      $variables['prev'] = $topic->nid;
      $variables['prev_title'] = check_plain($topic->title);
      $variables['prev_url'] = url("node/$topic->nid");