forum.module 34.8 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
15
    case 'admin/help#forum':
      $output = '<p>'. t('The forum module lets you create threaded discussion forums for a particular topic on your site. This is similar to a message board system such as phpBB. Forums are very useful because they allow community members to discuss topics with one another, and they are archived for future reference.') .'</p>';
16
      $output .= '<p>'. t('Forums can be organized under what are called <em>containers</em>. Containers hold forums and, in turn, forums hold threaded discussions. Both containers and forums can be placed inside other containers and forums. By planning the structure of your containers and forums well, you make it easier for users to find a topic area of interest to them. Forum topics can be moved by selecting a different forum and can be left in the existing forum by selecting <em>leave a shadow copy</em>. Forum topics can also have their own URL.') .'</p>';
17
      $output .= '<p>'. t('Forums module <strong>requires Taxonomy and Comments module</strong> be enabled.') .'</p>';
18
      $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@forum">Forum page</a>.', array('@forum' => 'http://drupal.org/handbook/modules/forum/')) .'</p>';
19
      return $output;
20
    case 'admin/content/forum':
21
      return '<p>'. t('This is a list of existing containers and forums that you can edit. Containers hold forums and, in turn, forums hold threaded discussions. Both containers and forums can be placed inside other containers and forums. By planning the structure of your containers and forums well, you make it easier for users to find a topic area of interest to them.') .'</p>';
22
    case 'admin/content/forum/add/container':
23
      return '<p>'. t('Containers help you organize your forums. The job of a container is to hold, or contain, other forums that are related. For example, a container named "Food" might hold two forums named "Fruit" and "Vegetables".') .'</p>';
24
    case 'admin/content/forum/add/forum':
25
      return '<p>'. t('A forum holds discussion topics that are related. For example, a forum named "Fruit" might contain topics titled "Apples" and "Bananas".') .'</p>';
26
    case 'admin/content/forum/settings':
27
      return '<p>'. t('These settings provide the ability to fine tune the display of your forum topics. Choose the content types to use in the forums by editing the options for the <a href="@forum_vocab">forum vocabulary</a>.', array('@forum_vocab' => url('admin/content/taxonomy/edit/vocabulary/'. variable_get('forum_nav_vocabulary', '')))) .'</p>';
Dries's avatar
   
Dries committed
28
29
30
  }
}

31
32
33
34
35
/**
 * Implementation of hook_theme()
 */
function forum_theme() {
  return array(
36
    'forums' => array(
37
      'template' => 'forums',
38
39
40
      'arguments' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
    ),
    'forum_list' => array(
41
      'template' => 'forum-list',
42
43
44
      'arguments' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
    ),
    'forum_topic_list' => array(
45
      'template' => 'forum-topic-list',
46
47
48
      'arguments' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
    ),
    'forum_icon' => array(
49
      'template' => 'forum-icon',
50
51
52
      'arguments' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0),
    ),
    'forum_topic_navigation' => array(
53
      'template' => 'forum-topic-navigation',
54
55
      'arguments' => array('node' => NULL),
    ),
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
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) {
  $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);
}

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

151
152
153
154

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

159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/**
 * Implementation of hook_nodeapi().
 */
function forum_nodeapi(&$node, $op, $teaser, $page) {
  $vid = variable_get('forum_nav_vocabulary', '');
  $vocabulary = taxonomy_vocabulary_load($vid);

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

  switch ($op) {
    case 'view':
      if ($page && $node->taxonomy) {
        // Get the forum terms from the (cached) tree
        $tree = taxonomy_get_tree($vid);
        if ($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;
          }
        }
        // Breadcrumb navigation
187
188
        $breadcrumb[] = l(t('Home'), NULL);
        $breadcrumb[] = l($vocabulary->name, 'forum');
189
190
191
        if ($parents = taxonomy_get_parents_all($node->tid)) {
          $parents = array_reverse($parents);
          foreach ($parents as $p) {
192
            $breadcrumb[] = l($p->name, 'forum/'.$p->tid);
193
194
          }
        }
195
        drupal_set_breadcrumb($breadcrumb);
196
197
198
199
200
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

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

    case 'prepare':
      if (empty($node->nid)) {
        // New topic
        $node->taxonomy[arg(3)]->vid = $vid;
        $node->taxonomy[arg(3)]->tid = arg(3);
      }
      return $node;
      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.
235
    case 'presave':
236
237
238
239
240
241
242
243
244
245
246
      // Make sure all fields are set properly:
      $node->icon = !empty($node->icon) ? $node->icon : '';

      if ($node->taxonomy) {
        // Get the forum terms from the (cached) tree
        $tree = taxonomy_get_tree($vid);
        if ($tree) {
          foreach ($tree as $term) {
            $forum_terms[] = $term->tid;
          }
        }
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
256
257
258
259
260
        if ($old_tid) {
          if (($node->tid != $old_tid) && $node->shadow) {
            // A shadow copy needs to be created. Retain new term and add old term.
            $node->taxonomy[] = $old_tid;
          }
        }
      }
      break;
261
262
263
264
265
266
267
268
269
270
271
272
273
274
    case 'update':
      if (!$node->revision) {
        db_query('UPDATE {forum} SET tid = %d WHERE vid = %d', $node->tid, $node->vid);
        break;
      }
      // Deliberate no break -- for new revisions we need an insert.
    case 'insert':
      db_query('INSERT INTO {forum} (tid, vid, nid) VALUES (%d, %d, %d)', $node->tid, $node->vid, $node->nid);
      break;
    case 'delete':
      db_query('DELETE FROM {forum} WHERE nid = %d', $node->nid);
      break;
    case 'load':
      return db_fetch_object(db_query('SELECT tid AS forum_tid FROM {forum} WHERE vid = %d', $node->vid));
275
276
277
  }

  return;
278
279
}

280
/**
281
 * Implementation of hook_node_info().
282
 */
283
function forum_node_info() {
284
285
  return array(
    'forum' => array(
286
      'name' => t('Forum topic'),
287
      'module' => 'forum',
288
      'description' => t('A <em>forum topic</em> is the initial post to a new discussion thread within a forum.'),
289
290
291
      'title_label' => t('Subject'),
    )
  );
Dries's avatar
   
Dries committed
292
293
}

294
295
296
/**
 * Implementation of hook_access().
 */
297
function forum_access($op, $node, $account) {
Dries's avatar
   
Dries committed
298
  if ($op == 'create') {
299
    return user_access('create forum topics', $account);
Dries's avatar
   
Dries committed
300
  }
Dries's avatar
   
Dries committed
301
302

  if ($op == 'update' || $op == 'delete') {
303
    if (user_access('edit own forum topics', $account) && ($account->uid == $node->uid)) {
Dries's avatar
   
Dries committed
304
305
306
      return TRUE;
    }
  }
Dries's avatar
   
Dries committed
307
308
}

309
310
311
/**
 * Implementation of hook_perm().
 */
Dries's avatar
   
Dries committed
312
function forum_perm() {
313
  return array('create forum topics', 'edit own forum topics', 'administer forums');
Dries's avatar
   
Dries committed
314
}
Dries's avatar
   
Dries committed
315

316
317
318
/**
 * Implementation of hook_taxonomy().
 */
319
function forum_taxonomy($op, $type, $term = NULL) {
320
  if ($op == 'delete' && $term['vid'] == variable_get('forum_nav_vocabulary', '')) {
321
322
    switch ($type) {
      case 'term':
323
        $results = db_query('SELECT tn.nid FROM {term_node} tn WHERE tn.tid = %d', $term['tid']);
324
325
326
327
328
329
330
        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());
331
332
        $key = array_search($term['tid'], $containers);
        if ($key !== FALSE) {
333
334
335
336
337
338
          unset($containers[$key]);
        }
        variable_set('forum_containers', $containers);
        break;
      case 'vocabulary':
        variable_del('forum_nav_vocabulary');
Dries's avatar
   
Dries committed
339
    }
340
341
  }
}
Dries's avatar
   
Dries committed
342

343
344
345
/**
 * Implementation of hook_form_alter().
 */
346
function forum_form_alter(&$form, $form_state, $form_id) {
347
  // Hide critical options from forum vocabulary
348
  if ($form_id == 'taxonomy_form_vocabulary') {
349
350
    $vid = variable_get('forum_nav_vocabulary', '');
    if (isset($form['vid']) && $form['vid']['#value'] == $vid) {
351
352
353
354
      $form['help_forum_vocab'] = array(
        '#value' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
        '#weight' => -1,
      );
355
      $form['nodes']['#required'] = TRUE;
356
357
358
359
      $form['hierarchy'] = array('#type' => 'value', '#value' => 1);
      unset($form['relations']);
      unset($form['tags']);
      unset($form['multiple']);
360
      unset($form['delete']);
361
362
363
364
365
      $form['required'] = array('#type' => 'value', '#value' => 1);
    }
  }
}

366
367
368
/**
 * Implementation of hook_load().
 */
Dries's avatar
   
Dries committed
369
function forum_load($node) {
370
  $forum = db_fetch_object(db_query('SELECT * FROM {term_node} WHERE vid = %d', $node->vid));
Dries's avatar
   
Dries committed
371
372
373
374

  return $forum;
}

375
376
377
378
379
380
/**
 * Implementation of hook_block().
 *
 * Generates a block containing the currently active forum topics and the
 * most recently added forum topics.
 */
381
382
383
function forum_block($op = 'list', $delta = 0, $edit = array()) {
  switch ($op) {
    case 'list':
Dries's avatar
   
Dries committed
384
385
      $blocks[0]['info'] = t('Active forum topics');
      $blocks[1]['info'] = t('New forum topics');
386
      return $blocks;
Dries's avatar
   
Dries committed
387

388
    case 'configure':
389
      $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)));
390
      return $form;
Dries's avatar
   
Dries committed
391

392
    case 'save':
393
      variable_set('forum_block_num_'. $delta, $edit['forum_block_num_'. $delta]);
394
      break;
Dries's avatar
   
Dries committed
395

396
397
    case 'view':
      if (user_access('access content')) {
Dries's avatar
   
Dries committed
398
399
        switch ($delta) {
          case 0:
400
            $title = t('Active forum topics');
401
402
            $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'));
403
            $content = node_title_list($result);
404
            break;
Dries's avatar
   
Dries committed
405
406

          case 1:
407
            $title = t('New forum topics');
408
409
            $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'));
410
            $content = node_title_list($result);
411
            break;
Dries's avatar
   
Dries committed
412
        }
Dries's avatar
   
Dries committed
413

414
415
        if (!empty($content)) {
          $block['subject'] = $title;
416
          $block['content'] = $content . theme('more_link', url('forum'), t('Read the latest forum topics.'));
417
          return $block;
418
419
420
        }
      }
  }
Dries's avatar
   
Dries committed
421
422
}

423
424
425
/**
 * Implementation of hook_form().
 */
426
function forum_form(&$node, $form_state) {
427
  $type = node_get_types('type', $node);
428
  $form['title'] = array('#type' => 'textfield', '#title' => check_plain($type->title_label), '#default_value' => !empty($node->title) ? $node->title : '', '#required' => TRUE, '#weight' => -5);
429

430
  if (!empty($node->nid)) {
431
432
    $vid = variable_get('forum_nav_vocabulary', '');
    $forum_terms = taxonomy_node_get_terms_by_vocabulary($node, $vid);
Dries's avatar
Dries committed
433
    // if editing, give option to leave shadows
434
    $shadow = (count($forum_terms) > 1);
435
    $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
436
  }
Dries's avatar
   
Dries committed
437

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

440
441
442
  $form['#submit'][] = 'forum_submit';
  // Assign the forum topic submit handler.

443
  return $form;
Dries's avatar
   
Dries committed
444
445
}

446
function forum_link_alter(&$links, $node) {
447
  foreach ($links as $module => $link) {
448
    if (strstr($module, 'taxonomy_term')) {
449
450
      // 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.
451
      $tid = str_replace('taxonomy/term/', '', $link['href']);
452
      $vid = variable_get('forum_nav_vocabulary', '');
453
      $term = taxonomy_get_term($tid);
454
      if ($term->vid == $vid) {
455
        $links[$module]['href'] = str_replace('taxonomy/term', 'forum', $link['href']);
456
      }
457
458
    }
  }
459
460
}

Dries's avatar
   
Dries committed
461
462
463
/**
 * Returns a list of all forums for a given taxonomy id
 *
464
 * Forum objects contain the following fields
Dries's avatar
   
Dries committed
465
466
467
468
469
470
471
472
473
 * -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
474
function forum_get_forums($tid = 0) {
475

Dries's avatar
   
Dries committed
476
  $forums = array();
477
478
  $vid = variable_get('forum_nav_vocabulary', '');
  $_forums = taxonomy_get_tree($vid, $tid);
Dries's avatar
   
Dries committed
479

Dries's avatar
   
Dries committed
480
  if (count($_forums)) {
Dries's avatar
   
Dries committed
481

Dries's avatar
   
Dries committed
482
483
    $counts = array();

484
    $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";
485
    $sql = db_rewrite_sql($sql);
486
    $_counts = db_query($sql);
Dries's avatar
   
Dries committed
487
488
489
    while ($count = db_fetch_object($_counts)) {
      $counts[$count->tid] = $count;
    }
Dries's avatar
   
Dries committed
490
  }
Dries's avatar
   
Dries committed
491

Dries's avatar
   
Dries committed
492
493
494
495
  foreach ($_forums as $forum) {
    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
      $forum->container = 1;
    }
Dries's avatar
   
Dries committed
496

497
    if (!empty($counts[$forum->tid])) {
Dries's avatar
   
Dries committed
498
499
500
501
502
503
504
505
506
507
      $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
508
    // used to join node_comment_statistics to users.
509
    $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";
510
    $sql = db_rewrite_sql($sql);
Dries's avatar
   
Dries committed
511
    $topic = db_fetch_object(db_query_range($sql, $forum->tid, 0, 1));
512

513
    $last_post = new stdClass();
514
515
516
517
518
    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
519
    $forum->last_post = $last_post;
Dries's avatar
   
Dries committed
520

Dries's avatar
   
Dries committed
521
522
523
524
    $forums[$forum->tid] = $forum;
  }

  return $forums;
Dries's avatar
   
Dries committed
525
526
}

527
528
529
530
531
/**
 * Calculate the number of nodes the user has not yet read and are newer
 * than NODE_NEW_LIMIT.
 */
function _forum_topics_unread($term, $uid) {
532
  $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";
533
  $sql = db_rewrite_sql($sql);
534
  return db_result(db_query($sql, $term, $uid, NODE_NEW_LIMIT));
Dries's avatar
   
Dries committed
535
536
}

Dries's avatar
Dries committed
537
function forum_get_topics($tid, $sortby, $forum_per_page) {
Dries's avatar
   
Dries committed
538
  global $user, $forum_topic_list_header;
Dries's avatar
   
Dries committed
539

Dries's avatar
   
Dries committed
540
  $forum_topic_list_header = array(
541
    array('data' => '&nbsp;', 'field' => NULL),
Dries's avatar
   
Dries committed
542
    array('data' => t('Topic'), 'field' => 'n.title'),
Dries's avatar
   
Dries committed
543
    array('data' => t('Replies'), 'field' => 'l.comment_count'),
Dries's avatar
   
Dries committed
544
    array('data' => t('Created'), 'field' => 'n.created'),
Dries's avatar
   
Dries committed
545
    array('data' => t('Last reply'), 'field' => 'l.last_comment_timestamp'),
Dries's avatar
   
Dries committed
546
  );
Dries's avatar
   
Dries committed
547

Dries's avatar
   
Dries committed
548
  $order = _forum_get_topic_order($sortby);
Dries's avatar
   
Dries committed
549
  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
Dries's avatar
   
Dries committed
550
551
    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
      $forum_topic_list_header[$i]['sort'] = $order['sort'];
Dries's avatar
   
Dries committed
552
553
554
555
    }
  }

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

557
  $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
558
  $sql .= tablesort_sql($forum_topic_list_header, 'n.sticky DESC,');
559
  $sql .= ', n.created DESC';  // Always add a secondary sort order so that the news forum topics are on top.
Dries's avatar
Dries committed
560

561
  $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
562

Steven Wittens's avatar
Steven Wittens committed
563
  $result = pager_query($sql, $forum_per_page, 0, $sql_count, $tid);
564
  $topics = array();
Dries's avatar
   
Dries committed
565
  while ($topic = db_fetch_object($result)) {
Dries's avatar
Dries committed
566
567
    if ($user->uid) {
      // folder is new if topic is new or there are new comments since last visit
Dries's avatar
   
Dries committed
568
      if ($topic->tid != $tid) {
Dries's avatar
Dries committed
569
        $topic->new = 0;
Dries's avatar
   
Dries committed
570
571
      }
      else {
Dries's avatar
   
Dries committed
572
        $history = _forum_user_last_visit($topic->nid);
Dries's avatar
   
Dries committed
573
        $topic->new_replies = comment_num_new($topic->nid, $history);
574
        $topic->new = $topic->new_replies || ($topic->timestamp > $history);
Dries's avatar
   
Dries committed
575
      }
576
577
    }
    else {
578
      // Do not track "new replies" status for topics if the user is anonymous.
Dries's avatar
Dries committed
579
580
      $topic->new_replies = 0;
      $topic->new = 0;
581
    }
Dries's avatar
   
Dries committed
582

Dries's avatar
   
Dries committed
583
    if ($topic->num_comments > 0) {
584
      $last_reply = new stdClass();
Dries's avatar
   
Dries committed
585
586
587
588
589
      $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
590
591
592
    $topics[] = $topic;
  }

Dries's avatar
Dries committed
593
  return $topics;
Dries's avatar
   
Dries committed
594
595
}

Dries's avatar
   
Dries committed
596
597
598
/**
 * Finds the first unread node for a given forum.
 */
Dries's avatar
Dries committed
599
function _forum_new($tid) {
Dries's avatar
   
Dries committed
600
601
  global $user;

602
  $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";
603
  $sql = db_rewrite_sql($sql);
Dries's avatar
   
Dries committed
604
  $nid = db_result(db_query_range($sql, $user->uid, $tid, NODE_NEW_LIMIT, 0, 1));
Dries's avatar
   
Dries committed
605
606
607
608

  return $nid ? $nid : 0;
}

Dries's avatar
   
Dries committed
609
/**
610
 * Process variables for forums.tpl.php
611
612
613
614
615
616
617
618
 *
 * The $variables array contains the following arguments:
 * - $forums
 * - $topics
 * - $parents
 * - $tid
 * - $sortby
 * - $forum_per_page
Dries's avatar
   
Dries committed
619
 *
620
 * @see forums.tpl.php
Dries's avatar
   
Dries committed
621
 */
622
function template_preprocess_forums(&$variables) {
Dries's avatar
   
Dries committed
623
  global $user;
Dries's avatar
   
Dries committed
624

625
626
  $vid = variable_get('forum_nav_vocabulary', '');
  $vocabulary = taxonomy_vocabulary_load($vid);
627
  $title = !empty($vocabulary->name) ? $vocabulary->name : '';
Dries's avatar
   
Dries committed
628

Dries's avatar
   
Dries committed
629
  // Breadcrumb navigation:
630
  $breadcrumb[] = l(t('Home'), NULL);
631
  if ($variables['tid']) {
632
    $breadcrumb[] = l($vocabulary->name, 'forum');
Dries's avatar
   
Dries committed
633
  }
634
635
636
637
  if ($variables['parents']) {
    $variables['parents'] = array_reverse($variables['parents']);
    foreach ($variables['parents'] as $p) {
      if ($p->tid == $variables['tid']) {
638
        $title = $p->name;
Dries's avatar
   
Dries committed
639
640
      }
      else {
641
        $breadcrumb[] = l($p->name, 'forum/'. $p->tid);
Dries's avatar
   
Dries committed
642
643
644
      }
    }
  }
645
  drupal_set_breadcrumb($breadcrumb);
Dries's avatar
Dries committed
646
  drupal_set_title(check_plain($title));
647

648
  if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
649
650
651
652
653
654
655
656
657
    // 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.
658
        $forum_types[$type] = array('title' => t('Post new @node_type', array('@node_type' => node_get_types('name', $type))), 'href' => "node/add/$type/$variables[tid]");
659
      }
Dries's avatar
   
Dries committed
660
    }
661

662
    if (empty($forum_types)) {
663
664
      // The user is logged-in; but denied access to create any new forum content type.
      if ($user->uid) {
665
        $forum_types['disallowed'] = array('title' => t('You are not allowed to post new content in forum.'));
666
667
668
      }
      // The user is not logged-in; and denied access to create any new forum content type.
      else {
669
        $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);
670
      }
Dries's avatar
   
Dries committed
671
    }
672
    $variables['links'] = $forum_types;
Dries's avatar
   
Dries committed
673

674
675
676
677
678
679
    if (!empty($variables['forums'])) {
      $variables['forums'] = theme('forum_list', $variables['forums'], $variables['parents'], $variables['tid']);
    }
    else {
      $variables['forums'] = '';
    }
Dries's avatar
   
Dries committed
680

681
682
683
    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
684
    }
685
686
687
688
689
    else {
      $variables['topics'] = '';
    }

    // Provide separate template suggestions based on what's being output. Topic id is also accounted for.
690
    // Check both variables to be safe then the inverse. Forums with topic ID's take precedence.
691
    if ($variables['forums'] && !$variables['topics']) {
692
      $variables['template_files'][] = 'forums-containers';
693
      $variables['template_files'][] = 'forums-'. $variables['tid'];
694
      $variables['template_files'][] = 'forums-containers-'. $variables['tid'];
695
    }
696
    elseif (!$variables['forums'] && $variables['topics']) {
697
      $variables['template_files'][] = 'forums-topics';
698
      $variables['template_files'][] = 'forums-'. $variables['tid'];
699
      $variables['template_files'][] = 'forums-topics-'. $variables['tid'];
700
    }
701
702
703
    else {
      $variables['template_files'][] = 'forums-'. $variables['tid'];
    }
704

Dries's avatar
   
Dries committed
705
706
  }
  else {
707
    drupal_set_title(t('No forums defined'));
708
709
710
    $variables['links'] = array();
    $variables['forums'] = '';
    $variables['topics'] = '';
Dries's avatar
   
Dries committed
711
712
713
  }
}

Dries's avatar
   
Dries committed
714
/**
715
 * Process variables to format a forum listing.
Dries's avatar
   
Dries committed
716
 *
717
718
719
720
721
722
723
 * $variables contains the following information:
 * - $forums
 * - $parents
 * - $tid
 *
 * @see forum-list.tpl.php
 * @see theme_forum_list()
Dries's avatar
   
Dries committed
724
 */
725
function template_preprocess_forum_list(&$variables) {
Dries's avatar
   
Dries committed
726
  global $user;
727
  $row = 0;
728
729
730
731
732
733
  // 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);
734
735
736
737
738
739
740
    $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;
741
742
743
    if ($user->uid) {
      $variables['forums'][$id]->new_topics = _forum_topics_unread($forum->tid, $user->uid);
      if ($variables['forums'][$id]->new_topics) {
744
        $variables['forums'][$id]->new_text = t('!count new', array('!count' => $variables['forums'][$id]->new_topics));
745
        $variables['forums'][$id]->new_url = url("forum/$forum->tid", array('fragment' => 'new'));
Dries's avatar
   
Dries committed
746
      }
747
      $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
Dries's avatar
   
Dries committed
748
    }
749
    $variables['forums'][$id]->last_reply = theme('forum_submitted', $forum->last_post);
Dries's avatar
   
Dries committed
750
  }
751
752
753
  // 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
754
755
}

Dries's avatar
   
Dries committed
756
/**
757
758
759
760
761
762
763
 * Preprocess variables to format the topic listing.
 *
 * $variables contains the following data:
 * - $tid
 * - $topics
 * - $sortby
 * - $forum_per_page
Dries's avatar
   
Dries committed
764
 *
765
766
 * @see forum-topic-list.tpl.php
 * @see theme_forum_topic_list()
Dries's avatar
   
Dries committed
767
 */
768
function template_preprocess_forum_topic_list(&$variables) {
Dries's avatar
   
Dries committed
769
  global $forum_topic_list_header;
Dries's avatar
   
Dries committed
770

771
772
773
774
775
776
777
778
779
780
  // 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'])) {
781
    $row = 0;
782
783
    foreach ($variables['topics'] as $id => $topic) {
      $variables['topics'][$id]->icon = theme('forum_icon', $topic->new, $topic->num_comments, $topic->comment_mode, $topic->sticky);
784
785
786
      $variables['topics'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
      $row++;

787
788
789
790
791
792
793
794
795
796
797
798
799
      // 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 = '';
      }
800
801
802
803
804
805
806
      $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));
807
        $variables['topics'][$id]->new_url = url("node/$topic->nid", array('query' => comment_new_page_count($topic->num_comments, $topic->new_replies, $topic), 'fragment' => 'new'));
808
      }
809

Dries's avatar
   
Dries committed
810
    }
Dries's avatar
   
Dries committed
811
  }
812
813
814
815
  else {
    // Make this safe for the template
    $variables['topics'] = array();
  }
816
817
818
  // 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
819

820
  $variables['pager'] = theme('pager', NULL, $variables['forum_per_page'], 0);
Dries's avatar
   
Dries committed
821
822
}

823
/**
824
 * Process variables to format the icon for each individual topic.
825
 *
826
827
828
829
830
831
832
833
 * $variables contains the following data:
 * - $new_posts
 * - $num_posts = 0
 * - $comment_mode = 0
 * - $sticky = 0
 *
 * @see forum-icon.tpl.php
 * @see theme_forum_icon()
834
 */
835
836
837
838
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
839
840
  }
  else {
841
    $variables['icon'] = $variables['new_posts'] ? 'new' : 'default';
Dries's avatar
   
Dries committed
842
  }
Dries's avatar
   
Dries committed
843

844
845
  if ($variables['comment_mode'] == COMMENT_NODE_READ_ONLY || $variables['comment_mode'] == COMMENT_NODE_DISABLED) {
    $variables['icon'] = 'closed';
846
847
  }

848
849
  if ($variables['sticky'] == 1) {
    $variables['icon'] = 'sticky';
850
  }
851
852
853
}

/**
854
855
856
 * Preprocess variables to format the next/previous forum topic navigation links.
 *
 * $variables contains $node.
857
 *
858
859
 * @see forum-topic-navigation.tpl.php
 * @see theme_forum_topic_navigation
860
 */
861
function template_preprocess_forum_topic_navigation(&$variables) {
862
863
864
  $output = '';

  // get previous and next topic
865
  $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));
866
867
868
  $result = db_query(db_rewrite_sql($sql), isset($variables['node']->tid) ? $variables['node']->tid : 0);

  $stop = $variables['prev'] = $variables['next'] = 0;
869
870
871

  while ($topic = db_fetch_object($result)) {
    if ($stop == 1) {
872
873
874
      $variables['next'] = $topic->nid;
      $variables['next_title'] = check_plain($topic->title);
      $variables['next_url'] = url("node/$topic->nid");
875
876
      break;
    }
877
    if ($topic->nid == $variables['node']->nid) {
878
879
880
      $stop = 1;
    }
    else {
881
882
883
      $variables['prev'] = $topic->nid;
      $variables['prev_title'] = check_plain($topic->title);
      $variables['prev_url'] = url("node/$topic->nid");
884
885
    }
  }
886
}
887

888
/**
889
 * Process variables to format submission info for display in the forum list and topic list.
890
891
892
 *
 * $variables will contain: $topic
 *
893
894
 * @see forum-submitted.tpl.php
 * @see theme_forum_submitted
895
 */
896
897
898
function template_preprocess_forum_submitted(&$variables) {
  $variables['author'] = isset($variables['topic']->uid) ? theme('username', $variables['topic']) : '';
  $variables['time'] = isset($variables['topic']->timestamp) ? format_interval(time() - $variables['topic']->timestamp) : '';
Dries's avatar
   
Dries committed
899
900
901
902
}

function _forum_user_last_visit($nid) {
  global $user;
903
904
905
  static $history = array();

  if (empty($history)) {
Dries's avatar
   
Dries committed
906
    $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = %d', $user->uid);
Dries's avatar
   
Dries committed
907
    while ($t = db_fetch_object($result)) {
908
      $history[$t->nid] = $t->timestamp > NODE_NEW_LIMIT ? $t->timestamp : NODE_NEW_LIMIT;
Dries's avatar
   
Dries committed
909
910
    }
  }
911
  return isset($history[$nid]) ? $history[$nid] : NODE_NEW_LIMIT;
Dries's avatar
   
Dries committed
912
913
914
915
916
}

function _forum_get_topic_order($sortby) {
  switch ($sortby) {
    case 1:
Dries's avatar
   
Dries committed
917
      return array('field' => 'l.last_comment_timestamp', 'sort' => 'desc');
Dries's avatar
   
Dries committed
918
919
      break;
    case 2:
Dries's avatar
   
Dries committed
920
      return array('field' => 'l.last_comment_timestamp', 'sort' => 'asc');
Dries's avatar
   
Dries committed
921
922
      break;
    case 3:
Dries's avatar
   
Dries committed
923
      return array('field' => 'l.comment_count', 'sort' => 'desc');
Dries's avatar
   
Dries committed
924
925
      break;
    case 4:
Dries's avatar
   
Dries committed
926
      return array('field' => 'l.comment_count', 'sort' => 'asc');
Dries's avatar
   
Dries committed
927
928
929
930
      break;
  }
}

Dries's avatar
   
Dries committed
931
932
933
934
function _forum_get_topic_order_sql($sortby) {
  $order = _forum_get_topic_order($sortby);
  return $order['field'] .' '. $order['sort'];
}