Project

General

Profile

Paste
Download (47.8 KB) Statistics
| Branch: | Revision:

root / drupal7 / modules / forum / forum.module @ bd0d4c5c

1
<?php
2

    
3
/**
4
 * @file
5
 * Provides discussion forums.
6
 */
7

    
8
/**
9
 * Implements hook_help().
10
 */
11
function forum_help($path, $arg) {
12
  switch ($path) {
13
    case 'admin/help#forum':
14
      $output = '';
15
      $output .= '<h3>' . t('About') . '</h3>';
16
      $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. In a forum, users post topics and threads in nested hierarchies, allowing discussions to be categorized and grouped. The forum hierarchy consists of:') . '</p>';
17
      $output .= '<ul>';
18
      $output .= '<li>' . t('Optional containers (for example, <em>Support</em>), which can hold:') . '</li>';
19
      $output .= '<ul><li>' . t('Forums (for example, <em>Installing Drupal</em>), which can hold:') . '</li>';
20
      $output .= '<ul><li>' . t('Forum topics submitted by users (for example, <em>How to start a Drupal 6 Multisite</em>), which start discussions and are starting points for:') . '</li>';
21
      $output .= '<ul><li>' . t('Threaded comments submitted by users (for example, <em>You have these options...</em>).') . '</li>';
22
      $output .= '</ul>';
23
      $output .= '</ul>';
24
      $output .= '</ul>';
25
      $output .= '</ul>';
26
      $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@forum">Forum module</a>.', array('@forum' => 'http://drupal.org/documentation/modules/forum')) . '</p>';
27
      $output .= '<h3>' . t('Uses') . '</h3>';
28
      $output .= '<dl>';
29
      $output .= '<dt>' . t('Setting up forum structure') . '</dt>';
30
      $output .= '<dd>' . t('Visit the <a href="@forums">Forums page</a> to set up containers and forums to hold your discussion topics.', array('@forums' => url('admin/structure/forum'))) . '</dd>';
31
      $output .= '<dt>' . t('Starting a discussion') . '</dt>';
32
      $output .= '<dd>' . t('The <a href="@create-topic">Forum topic</a> link on the <a href="@content-add">Add new content</a> page creates the first post of a new threaded discussion, or thread.', array('@create-topic' => url('node/add/forum'), '@content-add' => url('node/add'))) . '</dd>';
33
      $output .= '<dt>' . t('Navigation') . '</dt>';
34
      $output .= '<dd>' . t('Enabling the Forum module provides a default <em>Forums</em> menu item in the navigation menu that links to the <a href="@forums">Forums page</a>.', array('@forums' => url('forum'))) . '</dd>';
35
      $output .= '<dt>' . t('Moving forum topics') . '</dt>';
36
      $output .= '<dd>' . t('A forum topic (and all of its comments) may be moved between forums by selecting a different forum while editing a forum topic. 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.') . '</dd>';
37
      $output .= '<dt>' . t('Locking and disabling comments') . '</dt>';
38
      $output .= '<dd>' . t('Selecting <em>Closed</em> under <em>Comment settings</em> while editing a forum topic will lock (prevent new comments on) the thread. Selecting <em>Hidden</em> under <em>Comment settings</em> while editing a forum topic will hide all existing comments on the thread, and prevent new ones.') . '</dd>';
39
      $output .= '</dl>';
40
      return $output;
41
    case 'admin/structure/forum':
42
      $output = '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
43
      $output .= theme('more_help_link', array('url' => 'admin/help/forum'));
44
      return $output;
45
    case 'admin/structure/forum/add/container':
46
      return '<p>' . t('Use containers to group related forums.') . '</p>';
47
    case 'admin/structure/forum/add/forum':
48
      return '<p>' . t('A forum holds related forum topics.') . '</p>';
49
    case 'admin/structure/forum/settings':
50
      return '<p>' . t('Adjust the display of your forum topics. Organize the forums on the <a href="@forum-structure">forum structure page</a>.', array('@forum-structure' => url('admin/structure/forum'))) . '</p>';
51
  }
52
}
53

    
54
/**
55
 * Implements hook_theme().
56
 */
57
function forum_theme() {
58
  return array(
59
    'forums' => array(
60
      'template' => 'forums',
61
      'variables' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
62
    ),
63
    'forum_list' => array(
64
      'template' => 'forum-list',
65
      'variables' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
66
    ),
67
    'forum_topic_list' => array(
68
      'template' => 'forum-topic-list',
69
      'variables' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
70
    ),
71
    'forum_icon' => array(
72
      'template' => 'forum-icon',
73
      'variables' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0, 'first_new' => FALSE),
74
    ),
75
    'forum_submitted' => array(
76
      'template' => 'forum-submitted',
77
      'variables' => array('topic' => NULL),
78
    ),
79
    'forum_form' => array(
80
      'render element' => 'form',
81
      'file' => 'forum.admin.inc',
82
    ),
83
  );
84
}
85

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

    
161
/**
162
 * Implements hook_menu_local_tasks_alter().
163
 */
164
function forum_menu_local_tasks_alter(&$data, $router_item, $root_path) {
165
  global $user;
166

    
167
  // Add action link to 'node/add/forum' on 'forum' sub-pages.
168
  if ($root_path == 'forum' || $root_path == 'forum/%') {
169
    $tid = (isset($router_item['page_arguments'][0]) ? $router_item['page_arguments'][0]->tid : 0);
170
    $forum_term = forum_forum_load($tid);
171
    if ($forum_term) {
172
      $links = array();
173
      // Loop through all bundles for forum taxonomy vocabulary field.
174
      $field = field_info_field('taxonomy_forums');
175
      foreach ($field['bundles']['node'] as $type) {
176
        if (node_access('create', $type)) {
177
          $links[$type] = array(
178
            '#theme' => 'menu_local_action',
179
            '#link' => array(
180
              'title' => t('Add new @node_type', array('@node_type' => node_type_get_name($type))),
181
              'href' => 'node/add/' . str_replace('_', '-', $type) . '/' . $forum_term->tid,
182
            ),
183
          );
184
        }
185
      }
186
      if (empty($links)) {
187
        // Authenticated user does not have access to create new topics.
188
        if ($user->uid) {
189
          $links['disallowed'] = array(
190
            '#theme' => 'menu_local_action',
191
            '#link' => array(
192
              'title' => t('You are not allowed to post new content in the forum.'),
193
            ),
194
          );
195
        }
196
        // Anonymous user does not have access to create new topics.
197
        else {
198
          $links['login'] = array(
199
            '#theme' => 'menu_local_action',
200
            '#link' => array(
201
              'title' => t('<a href="@login">Log in</a> to post new content in the forum.', array(
202
                '@login' => url('user/login', array('query' => drupal_get_destination())),
203
              )),
204
              'localized_options' => array('html' => TRUE),
205
            ),
206
          );
207
        }
208
      }
209
      $data['actions']['output'] = array_merge($data['actions']['output'], $links);
210
    }
211
  }
212
}
213

    
214
/**
215
 * Implements hook_entity_info_alter().
216
 */
217
function forum_entity_info_alter(&$info) {
218
  // Take over URI construction for taxonomy terms that are forums.
219
  if ($vid = variable_get('forum_nav_vocabulary', 0)) {
220
    // Within hook_entity_info(), we can't invoke entity_load() as that would
221
    // cause infinite recursion, so we call taxonomy_vocabulary_get_names()
222
    // instead of taxonomy_vocabulary_load(). All we need is the machine name
223
    // of $vid, so retrieving and iterating all the vocabulary names is somewhat
224
    // inefficient, but entity info is cached across page requests, and an
225
    // iteration of all vocabularies once per cache clearing isn't a big deal,
226
    // and is done as part of taxonomy_entity_info() anyway.
227
    foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
228
      if ($vid == $vocabulary->vid) {
229
        $info['taxonomy_term']['bundles'][$machine_name]['uri callback'] = 'forum_uri';
230
      }
231
    }
232
  }
233
}
234

    
235
/**
236
 * Implements callback_entity_info_uri().
237
 *
238
 * Entity URI callback used in forum_entity_info_alter().
239
 */
240
function forum_uri($forum) {
241
  return array(
242
    'path' => 'forum/' . $forum->tid,
243
  );
244
}
245

    
246
/**
247
 * Checks whether a node can be used in a forum, based on its content type.
248
 *
249
 * @param $node
250
 *   A node object.
251
 *
252
 * @return
253
 *   Boolean indicating if the node can be assigned to a forum.
254
 */
255
function _forum_node_check_node_type($node) {
256
  // Fetch information about the forum field.
257
  $field = field_info_instance('node', 'taxonomy_forums', $node->type);
258

    
259
  return is_array($field);
260
}
261

    
262
/**
263
 * Implements hook_node_view().
264
 */
265
function forum_node_view($node, $view_mode) {
266
  if (_forum_node_check_node_type($node)) {
267
    if ($view_mode == 'full' && node_is_page($node)) {
268
      $vid = variable_get('forum_nav_vocabulary', 0);
269
      $vocabulary = taxonomy_vocabulary_load($vid);
270
      // Breadcrumb navigation
271
      $breadcrumb[] = l(t('Home'), NULL);
272
      $breadcrumb[] = l($vocabulary->name, 'forum');
273
      if ($parents = taxonomy_get_parents_all($node->forum_tid)) {
274
        $parents = array_reverse($parents);
275
        foreach ($parents as $parent) {
276
          $breadcrumb[] = l($parent->name, 'forum/' . $parent->tid);
277
        }
278
      }
279
      drupal_set_breadcrumb($breadcrumb);
280

    
281
    }
282
  }
283
}
284

    
285
/**
286
 * Implements hook_node_validate().
287
 *
288
 * Checks in particular that the node is assigned only a "leaf" term in the
289
 * forum taxonomy.
290
 */
291
function forum_node_validate($node, $form) {
292
  if (_forum_node_check_node_type($node)) {
293
    $langcode = $form['taxonomy_forums']['#language'];
294
    // vocabulary is selected, not a "container" term.
295
    if (!empty($node->taxonomy_forums[$langcode])) {
296
      // Extract the node's proper topic ID.
297
      $containers = variable_get('forum_containers', array());
298
      foreach ($node->taxonomy_forums[$langcode] as $delta => $item) {
299
        // If no term was selected (e.g. when no terms exist yet), remove the
300
        // item.
301
        if (empty($item['tid'])) {
302
          unset($node->taxonomy_forums[$langcode][$delta]);
303
          continue;
304
        }
305
        $term = taxonomy_term_load($item['tid']);
306
        if (!$term) {
307
          form_set_error('taxonomy_forums', t('Select a forum.'));
308
          continue;
309
        }
310
        $used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid',0 , 1, array(
311
          ':tid' => $term->tid,
312
          ':vid' => $term->vid,
313
        ))->fetchField();
314
        if ($used && in_array($term->tid, $containers)) {
315
          form_set_error('taxonomy_forums', t('The item %forum is a forum container, not a forum. Select one of the forums below instead.', array('%forum' => $term->name)));
316
        }
317
      }
318
    }
319
  }
320
}
321

    
322
/**
323
 * Implements hook_node_presave().
324
 *
325
 * Assigns the forum taxonomy when adding a topic from within a forum.
326
 */
327
function forum_node_presave($node) {
328
  if (_forum_node_check_node_type($node)) {
329
    // Make sure all fields are set properly:
330
    $node->icon = !empty($node->icon) ? $node->icon : '';
331
    reset($node->taxonomy_forums);
332
    $langcode = key($node->taxonomy_forums);
333
    if (!empty($node->taxonomy_forums[$langcode])) {
334
      $node->forum_tid = $node->taxonomy_forums[$langcode][0]['tid'];
335
      if (isset($node->nid)) {
336
        $old_tid = db_query_range("SELECT f.tid FROM {forum} f INNER JOIN {node} n ON f.vid = n.vid WHERE n.nid = :nid ORDER BY f.vid DESC", 0, 1, array(':nid' => $node->nid))->fetchField();
337
        if ($old_tid && isset($node->forum_tid) && ($node->forum_tid != $old_tid) && !empty($node->shadow)) {
338
          // A shadow copy needs to be created. Retain new term and add old term.
339
          $node->taxonomy_forums[$langcode][] = array('tid' => $old_tid);
340
        }
341
      }
342
    }
343
  }
344
}
345

    
346
/**
347
 * Implements hook_node_update().
348
 */
349
function forum_node_update($node) {
350
  if (_forum_node_check_node_type($node)) {
351
    if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
352
      if (!empty($node->forum_tid)) {
353
        db_update('forum')
354
          ->fields(array('tid' => $node->forum_tid))
355
          ->condition('vid', $node->vid)
356
          ->execute();
357
      }
358
      // The node is removed from the forum.
359
      else {
360
        db_delete('forum')
361
          ->condition('nid', $node->nid)
362
          ->execute();
363
      }
364
    }
365
    else {
366
      if (!empty($node->forum_tid)) {
367
        db_insert('forum')
368
          ->fields(array(
369
            'tid' => $node->forum_tid,
370
            'vid' => $node->vid,
371
            'nid' => $node->nid,
372
          ))
373
          ->execute();
374
      }
375
    }
376
    // If the node has a shadow forum topic, update the record for this
377
    // revision.
378
    if (!empty($node->shadow)) {
379
      db_delete('forum')
380
        ->condition('nid', $node->nid)
381
        ->condition('vid', $node->vid)
382
        ->execute();
383
      db_insert('forum')
384
        ->fields(array(
385
          'nid' => $node->nid,
386
          'vid' => $node->vid,
387
          'tid' => $node->forum_tid,
388
        ))
389
        ->execute();
390
     }
391
  }
392
}
393

    
394
/**
395
 * Implements hook_node_insert().
396
 */
397
function forum_node_insert($node) {
398
  if (_forum_node_check_node_type($node)) {
399
    if (!empty($node->forum_tid)) {
400
      $nid = db_insert('forum')
401
        ->fields(array(
402
          'tid' => $node->forum_tid,
403
          'vid' => $node->vid,
404
          'nid' => $node->nid,
405
        ))
406
        ->execute();
407
    }
408
  }
409
}
410

    
411
/**
412
 * Implements hook_node_delete().
413
 */
414
function forum_node_delete($node) {
415
  if (_forum_node_check_node_type($node)) {
416
    db_delete('forum')
417
      ->condition('nid', $node->nid)
418
      ->execute();
419
    db_delete('forum_index')
420
      ->condition('nid', $node->nid)
421
      ->execute();
422
  }
423
}
424

    
425
/**
426
 * Implements hook_node_load().
427
 */
428
function forum_node_load($nodes) {
429
  $node_vids = array();
430
  foreach ($nodes as $node) {
431
    if (_forum_node_check_node_type($node)) {
432
      $node_vids[] = $node->vid;
433
    }
434
  }
435
  if (!empty($node_vids)) {
436
    $query = db_select('forum', 'f');
437
    $query
438
      ->fields('f', array('nid', 'tid'))
439
      ->condition('f.vid', $node_vids);
440
    $result = $query->execute();
441
    foreach ($result as $record) {
442
      $nodes[$record->nid]->forum_tid = $record->tid;
443
    }
444
  }
445
}
446

    
447
/**
448
 * Implements hook_node_info().
449
 */
450
function forum_node_info() {
451
  return array(
452
    'forum' => array(
453
      'name' => t('Forum topic'),
454
      'base' => 'forum',
455
      'description' => t('A <em>forum topic</em> starts a new discussion thread within a forum.'),
456
      'title_label' => t('Subject'),
457
    )
458
  );
459
}
460

    
461
/**
462
 * Implements hook_permission().
463
 */
464
function forum_permission() {
465
  $perms = array(
466
    'administer forums' => array(
467
      'title' => t('Administer forums'),
468
    ),
469
  );
470
  return $perms;
471
}
472

    
473
/**
474
 * Implements hook_taxonomy_term_delete().
475
 */
476
function forum_taxonomy_term_delete($term) {
477
  // For containers, remove the tid from the forum_containers variable.
478
  $containers = variable_get('forum_containers', array());
479
  $key = array_search($term->tid, $containers);
480
  if ($key !== FALSE) {
481
    unset($containers[$key]);
482
  }
483
  variable_set('forum_containers', $containers);
484
}
485

    
486
/**
487
 * Implements hook_comment_publish().
488
 *
489
 * This actually handles the insertion and update of published nodes since
490
 * comment_save() calls hook_comment_publish() for all published comments.
491
 */
492
function forum_comment_publish($comment) {
493
  _forum_update_forum_index($comment->nid);
494
}
495

    
496
/**
497
 * Implements hook_comment_update().
498
 *
499
 * The Comment module doesn't call hook_comment_unpublish() when saving
500
 * individual comments, so we need to check for those here.
501
 */
502
function forum_comment_update($comment) {
503
  // comment_save() calls hook_comment_publish() for all published comments,
504
  // so we need to handle all other values here.
505
  if (!$comment->status) {
506
    _forum_update_forum_index($comment->nid);
507
  }
508
}
509

    
510
/**
511
 * Implements hook_comment_unpublish().
512
 */
513
function forum_comment_unpublish($comment) {
514
  _forum_update_forum_index($comment->nid);
515
}
516

    
517
/**
518
 * Implements hook_comment_delete().
519
 */
520
function forum_comment_delete($comment) {
521
  _forum_update_forum_index($comment->nid);
522
}
523

    
524
/**
525
 * Implements hook_field_storage_pre_insert().
526
 */
527
function forum_field_storage_pre_insert($entity_type, $entity, &$skip_fields) {
528
  if ($entity_type == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
529
    $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
530
    foreach ($entity->taxonomy_forums as $language) {
531
      foreach ($language as $item) {
532
        $query->values(array(
533
          'nid' => $entity->nid,
534
          'title' => $entity->title,
535
          'tid' => $item['tid'],
536
          'sticky' => $entity->sticky,
537
          'created' => $entity->created,
538
          'comment_count' => 0,
539
          'last_comment_timestamp' => $entity->created,
540
        ));
541
      }
542
    }
543
    $query->execute();
544
  }
545
}
546

    
547
/**
548
 * Implements hook_field_storage_pre_update().
549
 */
550
function forum_field_storage_pre_update($entity_type, $entity, &$skip_fields) {
551
  $first_call = &drupal_static(__FUNCTION__, array());
552

    
553
  if ($entity_type == 'node' && _forum_node_check_node_type($entity)) {
554

    
555
    // If the node is published, update the forum index.
556
    if ($entity->status) {
557

    
558
      // We don't maintain data for old revisions, so clear all previous values
559
      // from the table. Since this hook runs once per field, per object, make
560
      // sure we only wipe values once.
561
      if (!isset($first_call[$entity->nid])) {
562
        $first_call[$entity->nid] = FALSE;
563
        db_delete('forum_index')->condition('nid', $entity->nid)->execute();
564
      }
565
      $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
566
      foreach ($entity->taxonomy_forums as $language) {
567
        foreach ($language as $item) {
568
          $query->values(array(
569
            'nid' => $entity->nid,
570
            'title' => $entity->title,
571
            'tid' => $item['tid'],
572
            'sticky' => $entity->sticky,
573
            'created' => $entity->created,
574
            'comment_count' => 0,
575
            'last_comment_timestamp' => $entity->created,
576
          ));
577
        }
578
      }
579
      $query->execute();
580
      // The logic for determining last_comment_count is fairly complex, so
581
      // call _forum_update_forum_index() too.
582
      _forum_update_forum_index($entity->nid);
583
    }
584

    
585
    // When a forum node is unpublished, remove it from the forum_index table.
586
    else {
587
      db_delete('forum_index')->condition('nid', $entity->nid)->execute();
588
    }
589

    
590
  }
591
}
592

    
593
/**
594
 * Implements hook_form_FORM_ID_alter() for taxonomy_form_vocabulary().
595
 */
596
function forum_form_taxonomy_form_vocabulary_alter(&$form, &$form_state, $form_id) {
597
  $vid = variable_get('forum_nav_vocabulary', 0);
598
  if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {
599
    $form['help_forum_vocab'] = array(
600
      '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
601
      '#weight' => -1,
602
    );
603
    // Forum's vocabulary always has single hierarchy. Forums and containers
604
    // have only one parent or no parent for root items. By default this value
605
    // is 0.
606
    $form['hierarchy']['#value'] = 1;
607
    // Do not allow to delete forum's vocabulary.
608
    $form['actions']['delete']['#access'] = FALSE;
609
  }
610
}
611

    
612
/**
613
 * Implements hook_form_FORM_ID_alter() for taxonomy_form_term().
614
 */
615
function forum_form_taxonomy_form_term_alter(&$form, &$form_state, $form_id) {
616
   $vid = variable_get('forum_nav_vocabulary', 0);
617
   if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {
618
    // Hide multiple parents select from forum terms.
619
    $form['relations']['parent']['#access'] = FALSE;
620
  }
621
}
622

    
623
/**
624
 * Implements hook_form_BASE_FORM_ID_alter() for node_form().
625
 */
626
function forum_form_node_form_alter(&$form, &$form_state, $form_id) {
627
  if (isset($form['taxonomy_forums'])) {
628
    $langcode = $form['taxonomy_forums']['#language'];
629
    // Make the vocabulary required for 'real' forum-nodes.
630
    $form['taxonomy_forums'][$langcode]['#required'] = TRUE;
631
    $form['taxonomy_forums'][$langcode]['#multiple'] = FALSE;
632
    if (empty($form['taxonomy_forums'][$langcode]['#default_value'])) {
633
      // If there is no default forum already selected, try to get the forum
634
      // ID from the URL (e.g., if we are on a page like node/add/forum/2, we
635
      // expect "2" to be the ID of the forum that was requested).
636
      $requested_forum_id = arg(3);
637
      $form['taxonomy_forums'][$langcode]['#default_value'] = is_numeric($requested_forum_id) ? $requested_forum_id : '';
638
    }
639
  }
640
}
641

    
642
/**
643
 * Implements hook_block_info().
644
 */
645
function forum_block_info() {
646
  $blocks['active'] = array(
647
    'info' => t('Active forum topics'),
648
    'cache' => DRUPAL_CACHE_CUSTOM,
649
    'properties' => array('administrative' => TRUE),
650
  );
651
  $blocks['new'] = array(
652
    'info' => t('New forum topics'),
653
    'cache' => DRUPAL_CACHE_CUSTOM,
654
    'properties' => array('administrative' => TRUE),
655
  );
656
  return $blocks;
657
}
658

    
659
/**
660
 * Implements hook_block_configure().
661
 */
662
function forum_block_configure($delta = '') {
663
  $form['forum_block_num_' . $delta] = array(
664
    '#type' => 'select',
665
    '#title' => t('Number of topics'),
666
    '#default_value' => variable_get('forum_block_num_' . $delta, '5'),
667
    '#options' => drupal_map_assoc(range(2, 20))
668
  );
669
  return $form;
670
}
671

    
672
/**
673
 * Implements hook_block_save().
674
 */
675
function forum_block_save($delta = '', $edit = array()) {
676
  variable_set('forum_block_num_' . $delta, $edit['forum_block_num_' . $delta]);
677
}
678

    
679
/**
680
 * Implements hook_block_view().
681
 *
682
 * Generates a block containing the currently active forum topics and the most
683
 * recently added forum topics.
684
 */
685
function forum_block_view($delta = '') {
686
  $query = db_select('forum_index', 'f')
687
    ->fields('f')
688
    ->addTag('node_access');
689
  switch ($delta) {
690
    case 'active':
691
      $title = t('Active forum topics');
692
      $query
693
        ->orderBy('f.last_comment_timestamp', 'DESC')
694
        ->range(0, variable_get('forum_block_num_active', '5'));
695
      break;
696

    
697
    case 'new':
698
      $title = t('New forum topics');
699
      $query
700
        ->orderBy('f.created', 'DESC')
701
        ->range(0, variable_get('forum_block_num_new', '5'));
702
      break;
703
  }
704

    
705
  $block['subject'] = $title;
706
  // Cache based on the altered query. Enables us to cache with node access enabled.
707
  $block['content'] = drupal_render_cache_by_query($query, 'forum_block_view');
708
  $block['content']['#access'] = user_access('access content');
709
  return $block;
710
}
711

    
712
/**
713
 * Render API callback: Lists nodes based on the element's #query property.
714
 *
715
 * This function can be used as a #pre_render callback.
716
 *
717
 * @see forum_block_view()
718
 */
719
function forum_block_view_pre_render($elements) {
720
  $result = $elements['#query']->execute();
721
  if ($node_title_list = node_title_list($result)) {
722
    $elements['forum_list'] = $node_title_list;
723
    $elements['forum_more'] = array('#theme' => 'more_link', '#url' => 'forum', '#title' => t('Read the latest forum topics.'));
724
  }
725
  return $elements;
726
}
727

    
728
/**
729
 * Implements hook_form().
730
 */
731
function forum_form($node, $form_state) {
732
  $type = node_type_get_type($node);
733
  $form['title'] = array(
734
    '#type' => 'textfield',
735
    '#title' => check_plain($type->title_label),
736
    '#default_value' => !empty($node->title) ? $node->title : '',
737
    '#required' => TRUE, '#weight' => -5
738
  );
739

    
740
  if (!empty($node->nid)) {
741
    $forum_terms = $node->taxonomy_forums;
742
    // If editing, give option to leave shadows.
743
    $shadow = (count($forum_terms) > 1);
744
    $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.'));
745
    $form['forum_tid'] = array('#type' => 'value', '#value' => $node->forum_tid);
746
  }
747

    
748
  return $form;
749
}
750

    
751
/**
752
 * Returns a tree of all forums for a given taxonomy term ID.
753
 *
754
 * @param $tid
755
 *   (optional) Taxonomy term ID of the forum. If not given all forums will be
756
 *   returned.
757
 *
758
 * @return
759
 *   A tree of taxonomy objects, with the following additional properties:
760
 *   - num_topics: Number of topics in the forum.
761
 *   - num_posts: Total number of posts in all topics.
762
 *   - last_post: Most recent post for the forum.
763
 *   - forums: An array of child forums.
764
 */
765
function forum_forum_load($tid = NULL) {
766
  $cache = &drupal_static(__FUNCTION__, array());
767

    
768
  // Return a cached forum tree if available.
769
  if (!isset($tid)) {
770
    $tid = 0;
771
  }
772
  if (isset($cache[$tid])) {
773
    return $cache[$tid];
774
  }
775

    
776
  $vid = variable_get('forum_nav_vocabulary', 0);
777

    
778
  // Load and validate the parent term.
779
  if ($tid) {
780
    $forum_term = taxonomy_term_load($tid);
781
    if (!$forum_term || ($forum_term->vid != $vid)) {
782
      return $cache[$tid] = FALSE;
783
    }
784
  }
785
  // If $tid is 0, create an empty object to hold the child terms.
786
  elseif ($tid === 0) {
787
    $forum_term = (object) array(
788
      'tid' => 0,
789
    );
790
  }
791

    
792
  // Determine if the requested term is a container.
793
  if (!$forum_term->tid || in_array($forum_term->tid, variable_get('forum_containers', array()))) {
794
    $forum_term->container = 1;
795
  }
796

    
797
  // Load parent terms.
798
  $forum_term->parents = taxonomy_get_parents_all($forum_term->tid);
799

    
800
  // Load the tree below.
801
  $forums = array();
802
  $_forums = taxonomy_get_tree($vid, $tid);
803

    
804
  if (count($_forums)) {
805
    $query = db_select('node', 'n');
806
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
807
    $query->join('forum', 'f', 'n.vid = f.vid');
808
    $query->addExpression('COUNT(n.nid)', 'topic_count');
809
    $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
810
    $counts = $query
811
      ->fields('f', array('tid'))
812
      ->condition('n.status', 1)
813
      ->groupBy('tid')
814
      ->addTag('node_access')
815
      ->execute()
816
      ->fetchAllAssoc('tid');
817
  }
818

    
819
  foreach ($_forums as $forum) {
820
    // Determine if the child term is a container.
821
    if (in_array($forum->tid, variable_get('forum_containers', array()))) {
822
      $forum->container = 1;
823
    }
824

    
825
    // Merge in the topic and post counters.
826
    if (!empty($counts[$forum->tid])) {
827
      $forum->num_topics = $counts[$forum->tid]->topic_count;
828
      $forum->num_posts = $counts[$forum->tid]->topic_count + $counts[$forum->tid]->comment_count;
829
    }
830
    else {
831
      $forum->num_topics = 0;
832
      $forum->num_posts = 0;
833
    }
834

    
835
    // Query "Last Post" information for this forum.
836
    $query = db_select('node', 'n');
837
    $query->join('users', 'u1', 'n.uid = u1.uid');
838
    $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid));
839
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
840
    $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid');
841
    $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u2.name END', 'last_comment_name');
842

    
843
    $topic = $query
844
      ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid'))
845
      ->condition('n.status', 1)
846
      ->orderBy('last_comment_timestamp', 'DESC')
847
      ->range(0, 1)
848
      ->addTag('node_access')
849
      ->execute()
850
      ->fetchObject();
851

    
852
    // Merge in the "Last Post" information.
853
    $last_post = new stdClass();
854
    if (!empty($topic->last_comment_timestamp)) {
855
      $last_post->created = $topic->last_comment_timestamp;
856
      $last_post->name = $topic->last_comment_name;
857
      $last_post->uid = $topic->last_comment_uid;
858
    }
859
    $forum->last_post = $last_post;
860

    
861
    $forums[$forum->tid] = $forum;
862
  }
863

    
864
  // Cache the result, and return the tree.
865
  $forum_term->forums = $forums;
866
  $cache[$tid] = $forum_term;
867
  return $forum_term;
868
}
869

    
870
/**
871
 * Calculates the number of new posts in a forum that the user has not yet read.
872
 *
873
 * Nodes are new if they are newer than NODE_NEW_LIMIT.
874
 *
875
 * @param $term
876
 *   The term ID of the forum.
877
 * @param $uid
878
 *   The user ID.
879
 *
880
 * @return
881
 *   The number of new posts in the forum that have not been read by the user.
882
 */
883
function _forum_topics_unread($term, $uid) {
884
  $query = db_select('node', 'n');
885
  $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term));
886
  $query->leftJoin('history', 'h', 'n.nid = h.nid AND h.uid = :uid', array(':uid' => $uid));
887
  $query->addExpression('COUNT(n.nid)', 'count');
888
  return $query
889
    ->condition('status', 1)
890
    ->condition('n.created', NODE_NEW_LIMIT, '>')
891
    ->isNull('h.nid')
892
    ->addTag('node_access')
893
    ->execute()
894
    ->fetchField();
895
}
896

    
897
/**
898
 * Gets all the topics in a forum.
899
 *
900
 * @param $tid
901
 *   The term ID of the forum.
902
 * @param $sortby
903
 *   One of the following integers indicating the sort criteria:
904
 *   - 1: Date - newest first.
905
 *   - 2: Date - oldest first.
906
 *   - 3: Posts with the most comments first.
907
 *   - 4: Posts with the least comments first.
908
 * @param $forum_per_page
909
 *   The maximum number of topics to display per page.
910
 *
911
 * @return
912
 *   A list of all the topics in a forum.
913
 */
914
function forum_get_topics($tid, $sortby, $forum_per_page) {
915
  global $user, $forum_topic_list_header;
916

    
917
  $forum_topic_list_header = array(
918
    NULL,
919
    array('data' => t('Topic'), 'field' => 'f.title'),
920
    array('data' => t('Replies'), 'field' => 'f.comment_count'),
921
    array('data' => t('Last reply'), 'field' => 'f.last_comment_timestamp'),
922
  );
923

    
924
  $order = _forum_get_topic_order($sortby);
925
  for ($i = 0; $i < count($forum_topic_list_header); $i++) {
926
    if ($forum_topic_list_header[$i]['field'] == $order['field']) {
927
      $forum_topic_list_header[$i]['sort'] = $order['sort'];
928
    }
929
  }
930

    
931
  $query = db_select('forum_index', 'f')->extend('PagerDefault')->extend('TableSort');
932
  $query->fields('f');
933
  $query
934
    ->condition('f.tid', $tid)
935
    ->addTag('node_access')
936
    ->orderBy('f.sticky', 'DESC')
937
    ->orderByHeader($forum_topic_list_header)
938
    ->limit($forum_per_page);
939

    
940
  $count_query = db_select('forum_index', 'f');
941
  $count_query->condition('f.tid', $tid);
942
  $count_query->addExpression('COUNT(*)');
943
  $count_query->addTag('node_access');
944

    
945
  $query->setCountQuery($count_query);
946
  $result = $query->execute();
947
  $nids = array();
948
  foreach ($result as $record) {
949
    $nids[] = $record->nid;
950
  }
951
  if ($nids) {
952
    $query = db_select('node', 'n')->extend('TableSort');
953
    $query->fields('n', array('title', 'nid', 'type', 'sticky', 'created', 'uid'));
954
    $query->addField('n', 'comment', 'comment_mode');
955

    
956
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
957
    $query->fields('ncs', array('cid', 'last_comment_uid', 'last_comment_timestamp', 'comment_count'));
958

    
959
    $query->join('forum_index', 'f', 'f.nid = ncs.nid');
960
    $query->addField('f', 'tid', 'forum_tid');
961

    
962
    $query->join('users', 'u', 'n.uid = u.uid');
963
    $query->addField('u', 'name');
964

    
965
    $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid');
966

    
967
    $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u2.name END', 'last_comment_name');
968

    
969
    $query
970
      ->orderBy('f.sticky', 'DESC')
971
      ->orderByHeader($forum_topic_list_header)
972
      ->condition('n.nid', $nids);
973

    
974
    $result = $query->execute();
975
  }
976
  else {
977
    $result = array();
978
  }
979

    
980
  $topics = array();
981
  $first_new_found = FALSE;
982
  foreach ($result as $topic) {
983
    if ($user->uid) {
984
      // A forum is new if the topic is new, or if there are new comments since
985
      // the user's last visit.
986
      if ($topic->forum_tid != $tid) {
987
        $topic->new = 0;
988
      }
989
      else {
990
        $history = _forum_user_last_visit($topic->nid);
991
        $topic->new_replies = comment_num_new($topic->nid, $history);
992
        $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history);
993
      }
994
    }
995
    else {
996
      // Do not track "new replies" status for topics if the user is anonymous.
997
      $topic->new_replies = 0;
998
      $topic->new = 0;
999
    }
1000

    
1001
    // Make sure only one topic is indicated as the first new topic.
1002
    $topic->first_new = FALSE;
1003
    if ($topic->new != 0 && !$first_new_found) {
1004
      $topic->first_new = TRUE;
1005
      $first_new_found = TRUE;
1006
    }
1007

    
1008
    if ($topic->comment_count > 0) {
1009
      $last_reply = new stdClass();
1010
      $last_reply->created = $topic->last_comment_timestamp;
1011
      $last_reply->name = $topic->last_comment_name;
1012
      $last_reply->uid = $topic->last_comment_uid;
1013
      $topic->last_reply = $last_reply;
1014
    }
1015
    $topics[] = $topic;
1016
  }
1017

    
1018
  return $topics;
1019
}
1020

    
1021
/**
1022
 * Preprocesses variables for forums.tpl.php.
1023
 *
1024
 * @param $variables
1025
 *   An array containing the following elements:
1026
 *   - forums: An array of all forum objects to display for the given taxonomy
1027
 *     term ID. If tid = 0 then all the top-level forums are displayed.
1028
 *   - topics: An array of all the topics in the current forum.
1029
 *   - parents: An array of taxonomy term objects that are ancestors of the
1030
 *     current term ID.
1031
 *   - tid: Taxonomy term ID of the current forum.
1032
 *   - sortby: One of the following integers indicating the sort criteria:
1033
 *     - 1: Date - newest first.
1034
 *     - 2: Date - oldest first.
1035
 *     - 3: Posts with the most comments first.
1036
 *     - 4: Posts with the least comments first.
1037
 *   - forum_per_page: The maximum number of topics to display per page.
1038
 *
1039
 * @see forums.tpl.php
1040
 */
1041
function template_preprocess_forums(&$variables) {
1042
  global $user;
1043

    
1044
  $vid = variable_get('forum_nav_vocabulary', 0);
1045
  $vocabulary = taxonomy_vocabulary_load($vid);
1046
  $title = !empty($vocabulary->name) ? $vocabulary->name : '';
1047

    
1048
  // Breadcrumb navigation:
1049
  $breadcrumb[] = l(t('Home'), NULL);
1050
  if ($variables['tid']) {
1051
    $breadcrumb[] = l($vocabulary->name, 'forum');
1052
  }
1053
  if ($variables['parents']) {
1054
    $variables['parents'] = array_reverse($variables['parents']);
1055
    foreach ($variables['parents'] as $p) {
1056
      if ($p->tid == $variables['tid']) {
1057
        $title = $p->name;
1058
      }
1059
      else {
1060
        $breadcrumb[] = l($p->name, 'forum/' . $p->tid);
1061
      }
1062
    }
1063
  }
1064
  drupal_set_breadcrumb($breadcrumb);
1065
  drupal_set_title($title);
1066

    
1067
  if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
1068
    if (!empty($variables['forums'])) {
1069
      $variables['forums'] = theme('forum_list', $variables);
1070
    }
1071
    else {
1072
      $variables['forums'] = '';
1073
    }
1074

    
1075
    if ($variables['tid'] && !in_array($variables['tid'], variable_get('forum_containers', array()))) {
1076
      $variables['topics'] = theme('forum_topic_list', $variables);
1077
      drupal_add_feed('taxonomy/term/' . $variables['tid'] . '/feed', 'RSS - ' . $title);
1078
    }
1079
    else {
1080
      $variables['topics'] = '';
1081
    }
1082

    
1083
    // Provide separate template suggestions based on what's being output. Topic id is also accounted for.
1084
    // Check both variables to be safe then the inverse. Forums with topic ID's take precedence.
1085
    if ($variables['forums'] && !$variables['topics']) {
1086
      $variables['theme_hook_suggestions'][] = 'forums__containers';
1087
      $variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
1088
      $variables['theme_hook_suggestions'][] = 'forums__containers__' . $variables['tid'];
1089
    }
1090
    elseif (!$variables['forums'] && $variables['topics']) {
1091
      $variables['theme_hook_suggestions'][] = 'forums__topics';
1092
      $variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
1093
      $variables['theme_hook_suggestions'][] = 'forums__topics__' . $variables['tid'];
1094
    }
1095
    else {
1096
      $variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
1097
    }
1098

    
1099
  }
1100
  else {
1101
    drupal_set_title(t('No forums defined'));
1102
    $variables['forums'] = '';
1103
    $variables['topics'] = '';
1104
  }
1105
}
1106

    
1107
/**
1108
 * Preprocesses variables for forum-list.tpl.php.
1109
 *
1110
 * @param $variables
1111
 *   An array containing the following elements:
1112
 *   - forums: An array of all forum objects to display for the given taxonomy
1113
 *     term ID. If tid = 0 then all the top-level forums are displayed.
1114
 *   - parents: An array of taxonomy term objects that are ancestors of the
1115
 *     current term ID.
1116
 *   - tid: Taxonomy term ID of the current forum.
1117
 *
1118
 * @see forum-list.tpl.php
1119
 * @see theme_forum_list()
1120
 */
1121
function template_preprocess_forum_list(&$variables) {
1122
  global $user;
1123
  $row = 0;
1124
  // Sanitize each forum so that the template can safely print the data.
1125
  foreach ($variables['forums'] as $id => $forum) {
1126
    $variables['forums'][$id]->description = !empty($forum->description) ? filter_xss_admin($forum->description) : '';
1127
    $variables['forums'][$id]->link = url("forum/$forum->tid");
1128
    $variables['forums'][$id]->name = check_plain($forum->name);
1129
    $variables['forums'][$id]->is_container = !empty($forum->container);
1130
    $variables['forums'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
1131
    $row++;
1132

    
1133
    $variables['forums'][$id]->new_text = '';
1134
    $variables['forums'][$id]->new_url = '';
1135
    $variables['forums'][$id]->new_topics = 0;
1136
    $variables['forums'][$id]->old_topics = $forum->num_topics;
1137
    $variables['forums'][$id]->icon_class = 'default';
1138
    $variables['forums'][$id]->icon_title = t('No new posts');
1139
    if ($user->uid) {
1140
      $variables['forums'][$id]->new_topics = _forum_topics_unread($forum->tid, $user->uid);
1141
      if ($variables['forums'][$id]->new_topics) {
1142
        $variables['forums'][$id]->new_text = format_plural($variables['forums'][$id]->new_topics, '1 new', '@count new');
1143
        $variables['forums'][$id]->new_url = url("forum/$forum->tid", array('fragment' => 'new'));
1144
        $variables['forums'][$id]->icon_class = 'new';
1145
        $variables['forums'][$id]->icon_title = t('New posts');
1146
      }
1147
      $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
1148
    }
1149
    $variables['forums'][$id]->last_reply = theme('forum_submitted', array('topic' => $forum->last_post));
1150
  }
1151
  // Give meaning to $tid for themers. $tid actually stands for term id.
1152
  $variables['forum_id'] = $variables['tid'];
1153
  unset($variables['tid']);
1154
}
1155

    
1156
/**
1157
 * Preprocesses variables for forum-topic-list.tpl.php.
1158
 *
1159
 * @param $variables
1160
 *   An array containing the following elements:
1161
 *   - tid: Taxonomy term ID of the current forum.
1162
 *   - topics: An array of all the topics in the current forum.
1163
 *   - forum_per_page: The maximum number of topics to display per page.
1164
 *
1165
 * @see forum-topic-list.tpl.php
1166
 * @see theme_forum_topic_list()
1167
 */
1168
function template_preprocess_forum_topic_list(&$variables) {
1169
  global $forum_topic_list_header;
1170

    
1171
  // Create the tablesorting header.
1172
  $ts = tablesort_init($forum_topic_list_header);
1173
  $header = '';
1174
  foreach ($forum_topic_list_header as $cell) {
1175
    $cell = tablesort_header($cell, $forum_topic_list_header, $ts);
1176
    $header .= _theme_table_cell($cell, TRUE);
1177
  }
1178
  $variables['header'] = $header;
1179

    
1180
  if (!empty($variables['topics'])) {
1181
    $row = 0;
1182
    foreach ($variables['topics'] as $id => $topic) {
1183
      $variables['topics'][$id]->icon = theme('forum_icon', array('new_posts' => $topic->new, 'num_posts' => $topic->comment_count, 'comment_mode' => $topic->comment_mode, 'sticky' => $topic->sticky, 'first_new' => $topic->first_new));
1184
      $variables['topics'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
1185
      $row++;
1186

    
1187
      // We keep the actual tid in forum table, if it's different from the
1188
      // current tid then it means the topic appears in two forums, one of
1189
      // them is a shadow copy.
1190
      if ($variables['tid'] != $topic->forum_tid) {
1191
        $variables['topics'][$id]->moved = TRUE;
1192
        $variables['topics'][$id]->title = check_plain($topic->title);
1193
        $variables['topics'][$id]->message = l(t('This topic has been moved'), "forum/$topic->forum_tid");
1194
      }
1195
      else {
1196
        $variables['topics'][$id]->moved = FALSE;
1197
        $variables['topics'][$id]->title = l($topic->title, "node/$topic->nid");
1198
        $variables['topics'][$id]->message = '';
1199
      }
1200
      $variables['topics'][$id]->created = theme('forum_submitted', array('topic' => $topic));
1201
      $variables['topics'][$id]->last_reply = theme('forum_submitted', array('topic' => isset($topic->last_reply) ? $topic->last_reply : NULL));
1202

    
1203
      $variables['topics'][$id]->new_text = '';
1204
      $variables['topics'][$id]->new_url = '';
1205
      if ($topic->new_replies) {
1206
        $variables['topics'][$id]->new_text = format_plural($topic->new_replies, '1 new', '@count new');
1207
        $variables['topics'][$id]->new_url = url("node/$topic->nid", array('query' => comment_new_page_count($topic->comment_count, $topic->new_replies, $topic), 'fragment' => 'new'));
1208
      }
1209

    
1210
    }
1211
  }
1212
  else {
1213
    // Make this safe for the template.
1214
    $variables['topics'] = array();
1215
  }
1216
  // Give meaning to $tid for themers. $tid actually stands for term id.
1217
  $variables['topic_id'] = $variables['tid'];
1218
  unset($variables['tid']);
1219

    
1220
  $variables['pager'] = theme('pager');
1221
}
1222

    
1223
/**
1224
 * Preprocesses variables for forum-icon.tpl.php.
1225
 *
1226
 * @param $variables
1227
 *   An array containing the following elements:
1228
 *   - new_posts: Indicates whether or not the topic contains new posts.
1229
 *   - num_posts: The total number of posts in all topics.
1230
 *   - comment_mode: An integer indicating whether comments are open, closed,
1231
 *     or hidden.
1232
 *   - sticky: Indicates whether the topic is sticky.
1233
 *   - first_new: Indicates whether this is the first topic with new posts.
1234
 *
1235
 * @see forum-icon.tpl.php
1236
 * @see theme_forum_icon()
1237
 */
1238
function template_preprocess_forum_icon(&$variables) {
1239
  $variables['hot_threshold'] = variable_get('forum_hot_topic', 15);
1240
  if ($variables['num_posts'] > $variables['hot_threshold']) {
1241
    $variables['icon_class'] = $variables['new_posts'] ? 'hot-new' : 'hot';
1242
    $variables['icon_title'] = $variables['new_posts'] ? t('Hot topic, new comments') : t('Hot topic');
1243
  }
1244
  else {
1245
    $variables['icon_class'] = $variables['new_posts'] ? 'new' : 'default';
1246
    $variables['icon_title'] = $variables['new_posts'] ? t('New comments') : t('Normal topic');
1247
  }
1248

    
1249
  if ($variables['comment_mode'] == COMMENT_NODE_CLOSED || $variables['comment_mode'] == COMMENT_NODE_HIDDEN) {
1250
    $variables['icon_class'] = 'closed';
1251
    $variables['icon_title'] = t('Closed topic');
1252
  }
1253

    
1254
  if ($variables['sticky'] == 1) {
1255
    $variables['icon_class'] = 'sticky';
1256
    $variables['icon_title'] = t('Sticky topic');
1257
  }
1258
}
1259

    
1260
/**
1261
 * Preprocesses variables for forum-submitted.tpl.php.
1262
 *
1263
 * The submission information will be displayed in the forum list and topic
1264
 * list.
1265
 *
1266
 * @param $variables
1267
 *   An array containing the following elements:
1268
 *   - topic: The topic object.
1269
 *
1270
 * @see forum-submitted.tpl.php
1271
 * @see theme_forum_submitted()
1272
 */
1273
function template_preprocess_forum_submitted(&$variables) {
1274
  $variables['author'] = isset($variables['topic']->uid) ? theme('username', array('account' => $variables['topic'])) : '';
1275
  $variables['time'] = isset($variables['topic']->created) ? format_interval(REQUEST_TIME - $variables['topic']->created) : '';
1276
}
1277

    
1278
/**
1279
 * Gets the last time the user viewed a node.
1280
 *
1281
 * @param $nid
1282
 *   The node ID.
1283
 *
1284
 * @return
1285
 *   The timestamp when the user last viewed this node, if the user has
1286
 *   previously viewed the node; otherwise NODE_NEW_LIMIT.
1287
 */
1288
function _forum_user_last_visit($nid) {
1289
  global $user;
1290
  $history = &drupal_static(__FUNCTION__, array());
1291

    
1292
  if (empty($history)) {
1293
    $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid', array(':uid' => $user->uid));
1294
    foreach ($result as $t) {
1295
      $history[$t->nid] = $t->timestamp > NODE_NEW_LIMIT ? $t->timestamp : NODE_NEW_LIMIT;
1296
    }
1297
  }
1298
  return isset($history[$nid]) ? $history[$nid] : NODE_NEW_LIMIT;
1299
}
1300

    
1301
/**
1302
 * Gets topic sorting information based on an integer code.
1303
 *
1304
 * @param $sortby
1305
 *   One of the following integers indicating the sort criteria:
1306
 *   - 1: Date - newest first.
1307
 *   - 2: Date - oldest first.
1308
 *   - 3: Posts with the most comments first.
1309
 *   - 4: Posts with the least comments first.
1310
 *
1311
 * @return
1312
 *   An array with the following values:
1313
 *   - field: A field for an SQL query.
1314
 *   - sort: 'asc' or 'desc'.
1315
 */
1316
function _forum_get_topic_order($sortby) {
1317
  switch ($sortby) {
1318
    case 1:
1319
      return array('field' => 'f.last_comment_timestamp', 'sort' => 'desc');
1320
      break;
1321
    case 2:
1322
      return array('field' => 'f.last_comment_timestamp', 'sort' => 'asc');
1323
      break;
1324
    case 3:
1325
      return array('field' => 'f.comment_count', 'sort' => 'desc');
1326
      break;
1327
    case 4:
1328
      return array('field' => 'f.comment_count', 'sort' => 'asc');
1329
      break;
1330
  }
1331
}
1332

    
1333
/**
1334
 * Updates the taxonomy index for a given node.
1335
 *
1336
 * @param $nid
1337
 *   The ID of the node to update.
1338
 */
1339
function _forum_update_forum_index($nid) {
1340
  $count = db_query('SELECT COUNT(cid) FROM {comment} c INNER JOIN {forum_index} i ON c.nid = i.nid WHERE c.nid = :nid AND c.status = :status', array(
1341
    ':nid' => $nid,
1342
    ':status' => COMMENT_PUBLISHED,
1343
  ))->fetchField();
1344

    
1345
  if ($count > 0) {
1346
    // Comments exist.
1347
    $last_reply = db_query_range('SELECT cid, name, created, uid FROM {comment} WHERE nid = :nid AND status = :status ORDER BY cid DESC', 0, 1, array(
1348
      ':nid' => $nid,
1349
      ':status' => COMMENT_PUBLISHED,
1350
    ))->fetchObject();
1351
    db_update('forum_index')
1352
      ->fields( array(
1353
        'comment_count' => $count,
1354
        'last_comment_timestamp' => $last_reply->created,
1355
      ))
1356
      ->condition('nid', $nid)
1357
      ->execute();
1358
  }
1359
  else {
1360
    // Comments do not exist.
1361
    $node = db_query('SELECT uid, created FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
1362
    db_update('forum_index')
1363
      ->fields( array(
1364
        'comment_count' => 0,
1365
        'last_comment_timestamp' => $node->created,
1366
      ))
1367
      ->condition('nid', $nid)
1368
      ->execute();
1369
  }
1370
}
1371

    
1372
/**
1373
 * Implements hook_rdf_mapping().
1374
 */
1375
function forum_rdf_mapping() {
1376
  return array(
1377
    array(
1378
      'type' => 'node',
1379
      'bundle' => 'forum',
1380
      'mapping' => array(
1381
        'rdftype' => array('sioc:Post', 'sioct:BoardPost'),
1382
        'taxonomy_forums' => array(
1383
          'predicates' => array('sioc:has_container'),
1384
          'type' => 'rel',
1385
        ),
1386
      ),
1387
    ),
1388
    array(
1389
      'type' => 'taxonomy_term',
1390
      'bundle' => 'forums',
1391
      'mapping' => array(
1392
        'rdftype' => array('sioc:Container', 'sioc:Forum'),
1393
      ),
1394
    ),
1395
  );
1396
}