/[drupal]/drupal/modules/forum/forum.module
ViewVC logotype

Contents of /drupal/modules/forum/forum.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.529 - (show annotations) (download) (as text)
Sun Nov 1 12:11:10 2009 UTC (3 weeks, 3 days ago) by dries
Branch: MAIN
Changes since 1.528: +10 -10 lines
File MIME type: text/x-php
- Patch #595084 by c960657: use type hinting for .
1 <?php
2 // $Id: forum.module,v 1.528 2009/10/24 05:13:44 webchick Exp $
3
4 /**
5 * @file
6 * Provides discussion forums.
7 */
8
9 /**
10 * Implement hook_help().
11 */
12 function forum_help($path, $arg) {
13 switch ($path) {
14 case 'admin/help#forum':
15 $output = '<p>' . t('The forum module lets you create threaded discussion forums with functionality similar to other message board systems. Forums are useful because they allow community members to discuss topics with one another while ensuring those conversations are archived for later reference. The <a href="@create-topic">forum topic</a> menu item (under <em>Add new content</em> on the Navigation menu) creates the initial post of a new threaded discussion, or thread.', array('@create-topic' => url('node/add/forum'))) . '</p>';
16 $output .= '<p>' . t('A threaded discussion occurs as people leave comments on a forum topic (or on other comments within that topic). A forum topic is contained within a forum, which may hold many similar or related forum topics. Forums are (optionally) nested within a container, which may hold many similar or related forums. Both containers and forums may be nested within other containers and forums, and provide structure for your message board. By carefully planning this structure, you make it easier for users to find and comment on a specific forum topic.') . '</p>';
17 $output .= '<p>' . t('When administering a forum, note that:') . '</p>';
18 $output .= '<ul><li>' . t('a forum topic (and all of its comments) may be moved between forums by selecting a different forum while editing a forum topic.') . '</li>';
19 $output .= '<li>' . t('when moving a forum topic between forums, the <em>Leave shadow copy</em> option creates a link in the original forum pointing to the new location.') . '</li>';
20 $output .= '<li>' . t('selecting <em>Closed</em> under <em>Comment settings</em> while editing a forum topic will lock (prevent new comments) on the thread.') . '</li>';
21 $output .= '<li>' . t('selecting <em>Disabled</em> under <em>Comment settings</em> while editing a forum topic will hide all existing comments on the thread, and prevent new ones.') . '</li></ul>';
22 $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@forum">Forum module</a>.', array('@forum' => 'http://drupal.org/handbook/modules/forum/')) . '</p>';
23 return $output;
24 case 'admin/structure/forum':
25 return '<p>' . t('This page displays a list of existing forums and containers. Containers (optionally) hold forums, and forums hold forum topics (a forum topic is the initial post to a threaded discussion). To provide structure, both containers and forums may be placed inside other containers and forums.') . '</p>';
26 case 'admin/structure/forum/add/container':
27 return '<p>' . t('By grouping related or similar forums, containers help organize forums. For example, a container named "Food" may hold two forums named "Fruit" and "Vegetables", respectively.') . '</p>';
28 case 'admin/structure/forum/add/forum':
29 return '<p>' . t('A forum holds related or similar forum topics (a forum topic is the initial post to a threaded discussion). For example, a forum named "Fruit" may contain forum topics titled "Apples" and "Bananas", respectively.') . '</p>';
30 case 'admin/structure/forum/settings':
31 return '<p>' . t('These settings allow you to adjust the display of your forum topics. The content types available for use within a forum may be selected by editing the <em>Content types</em> on the <a href="@forum-vocabulary">forum vocabulary page</a>.', array('@forum-vocabulary' => url('admin/structure/taxonomy/edit/vocabulary/' . variable_get('forum_nav_vocabulary', 0)))) . '</p>';
32 }
33 }
34
35 /**
36 * Implement hook_theme().
37 */
38 function forum_theme() {
39 return array(
40 'forums' => array(
41 'template' => 'forums',
42 'variables' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
43 ),
44 'forum_list' => array(
45 'template' => 'forum-list',
46 'variables' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
47 ),
48 'forum_topic_list' => array(
49 'template' => 'forum-topic-list',
50 'variables' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
51 ),
52 'forum_icon' => array(
53 'template' => 'forum-icon',
54 'variables' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0),
55 ),
56 'forum_submitted' => array(
57 'template' => 'forum-submitted',
58 'variables' => array('topic' => NULL),
59 ),
60 'forum_form' => array(
61 'render element' => 'form',
62 'file' => 'forum.admin.inc',
63 ),
64 );
65 }
66
67 /**
68 * Implement hook_menu().
69 */
70 function forum_menu() {
71 $items['forum'] = array(
72 'title' => 'Forums',
73 'page callback' => 'forum_page',
74 'access arguments' => array('access content'),
75 'file' => 'forum.pages.inc',
76 );
77 $items['admin/structure/forum'] = array(
78 'title' => 'Forums',
79 'description' => 'Control forums and their hierarchy and change forum settings.',
80 'page callback' => 'drupal_get_form',
81 'page arguments' => array('forum_overview'),
82 'access arguments' => array('administer forums'),
83 'file' => 'forum.admin.inc',
84 );
85 $items['admin/structure/forum/list'] = array(
86 'title' => 'List',
87 'type' => MENU_DEFAULT_LOCAL_TASK,
88 'weight' => -10,
89 );
90 $items['admin/structure/forum/add/container'] = array(
91 'title' => 'Add container',
92 'page callback' => 'forum_form_main',
93 'page arguments' => array('container'),
94 'access arguments' => array('administer forums'),
95 'type' => MENU_LOCAL_ACTION,
96 'parent' => 'admin/structure/forum',
97 'file' => 'forum.admin.inc',
98 );
99 $items['admin/structure/forum/add/forum'] = array(
100 'title' => 'Add forum',
101 'page callback' => 'forum_form_main',
102 'page arguments' => array('forum'),
103 'access arguments' => array('administer forums'),
104 'type' => MENU_LOCAL_ACTION,
105 'parent' => 'admin/structure/forum',
106 'file' => 'forum.admin.inc',
107 );
108 $items['admin/structure/forum/settings'] = array(
109 'title' => 'Settings',
110 'page callback' => 'drupal_get_form',
111 'page arguments' => array('forum_admin_settings'),
112 'access arguments' => array('administer forums'),
113 'weight' => 5,
114 'type' => MENU_LOCAL_TASK,
115 'parent' => 'admin/structure/forum',
116 'file' => 'forum.admin.inc',
117 );
118 $items['admin/structure/forum/edit/container/%taxonomy_term'] = array(
119 'title' => 'Edit container',
120 'page callback' => 'forum_form_main',
121 'page arguments' => array('container', 5),
122 'access arguments' => array('administer forums'),
123 'type' => MENU_CALLBACK,
124 'file' => 'forum.admin.inc',
125 );
126 $items['admin/structure/forum/edit/forum/%taxonomy_term'] = array(
127 'title' => 'Edit forum',
128 'page callback' => 'forum_form_main',
129 'page arguments' => array('forum', 5),
130 'access arguments' => array('administer forums'),
131 'type' => MENU_CALLBACK,
132 'file' => 'forum.admin.inc',
133 );
134 return $items;
135 }
136
137
138 /**
139 * Implement hook_init().
140 */
141 function forum_init() {
142 drupal_add_css(drupal_get_path('module', 'forum') . '/forum.css');
143 }
144
145 /**
146 * Check whether a content type can be used in a forum.
147 *
148 * @param $node
149 * A node object.
150 *
151 * @return
152 * Boolean indicating if the node can be assigned to a forum.
153 */
154 function _forum_node_check_node_type(stdClass $node) {
155 // Fetch information about the forum field.
156 $field = field_info_instance('node', 'taxonomy_forums', $node->type);
157
158 return is_array($field);
159 }
160
161 /**
162 * Implement hook_node_view().
163 */
164 function forum_node_view(stdClass $node, $build_mode) {
165 $vid = variable_get('forum_nav_vocabulary', 0);
166 $vocabulary = taxonomy_vocabulary_load($vid);
167 if (_forum_node_check_node_type($node)) {
168 if ((bool)menu_get_object()) {
169 // Breadcrumb navigation
170 $breadcrumb[] = l(t('Home'), NULL);
171 $breadcrumb[] = l($vocabulary->name, 'forum');
172 if ($parents = taxonomy_get_parents_all($node->forum_tid)) {
173 $parents = array_reverse($parents);
174 foreach ($parents as $parent) {
175 $breadcrumb[] = l($parent->name, 'forum/' . $parent->tid);
176 }
177 }
178 drupal_set_breadcrumb($breadcrumb);
179
180 }
181 }
182 }
183
184 /**
185 * Implement hook_node_prepare().
186 */
187 function forum_node_prepare(stdClass $node) {
188 if (_forum_node_check_node_type($node)) {
189 if (empty($node->nid)) {
190 // New topic
191 $node->taxonomy_forums[0]['value'] = arg(3);
192 }
193 }
194 }
195
196 /**
197 * Implement hook_node_validate().
198 *
199 * Check in particular that only a "leaf" term in the associated taxonomy.
200 */
201 function forum_node_validate(stdClass $node, $form) {
202 if (_forum_node_check_node_type($node)) {
203 $langcode = $form['taxonomy_forums']['#language'];
204 // vocabulary is selected, not a "container" term.
205 if (!empty($node->taxonomy_forums[$langcode])) {
206 // Extract the node's proper topic ID.
207 $containers = variable_get('forum_containers', array());
208 foreach ($node->taxonomy_forums[$langcode] as $tid) {
209 $term = taxonomy_term_load($tid['value']);
210 $used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid',0 , 1, array(
211 ':tid' => $term->tid,
212 ':vid' => $term->vid,
213 ))->fetchField();
214 if ($used && in_array($term->tid, $containers)) {
215 form_set_error('taxonomy_forums', t('The item %forum is only a container for forums. Please select one of the forums below it.', array('%forum' => $term->name)));
216 }
217 }
218 }
219 }
220 }
221
222 /**
223 * Implement hook_node_presave().
224 *
225 * Assign forum taxonomy when adding a topic from within a forum.
226 */
227 function forum_node_presave(stdClass $node) {
228 if (_forum_node_check_node_type($node)) {
229 // Make sure all fields are set properly:
230 $node->icon = !empty($node->icon) ? $node->icon : '';
231 $langcode = array_shift(array_keys($node->taxonomy_forums));
232 if (!empty($node->taxonomy_forums[$langcode])) {
233 $node->forum_tid = $node->taxonomy_forums[$langcode][0]['value'];
234 $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();
235 if ($old_tid && isset($node->forum_tid) && ($node->forum_tid != $old_tid) && !empty($node->shadow)) {
236 // A shadow copy needs to be created. Retain new term and add old term.
237 $node->taxonomy_forums[$langcode][] = array('value' => $old_tid);
238 }
239 }
240 }
241 }
242
243 /**
244 * Implement hook_node_update().
245 */
246 function forum_node_update(stdClass $node) {
247 if (_forum_node_check_node_type($node)) {
248 if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
249 if (!empty($node->forum_tid)) {
250 db_update('forum')
251 ->fields(array('tid' => $node->forum_tid))
252 ->condition('vid', $node->vid)
253 ->execute();
254 }
255 // The node is removed from the forum.
256 else {
257 db_delete('forum')
258 ->condition('nid', $node->nid)
259 ->execute();
260 }
261 }
262 else {
263 if (!empty($node->forum_tid)) {
264 db_insert('forum')
265 ->fields(array(
266 'tid' => $node->forum_tid,
267 'vid' => $node->vid,
268 'nid' => $node->nid,
269 ))
270 ->execute();
271 }
272 }
273 // If the node has a shadow forum topic, update the record for this
274 // revision.
275 if ($node->shadow) {
276 db_delete('forum')
277 ->condition('nid', $node->nid)
278 ->condition('vid', $node->vid)
279 ->execute();
280 db_insert('forum')
281 ->fields(array(
282 'nid' => $node->nid,
283 'vid' => $node->vid,
284 'tid' => $node->forum_tid,
285 ))
286 ->execute();
287 }
288 }
289 }
290
291 /**
292 * Implement hook_node_insert().
293 */
294 function forum_node_insert(stdClass $node) {
295 if (_forum_node_check_node_type($node)) {
296 if (!empty($node->forum_tid)) {
297 $nid = db_insert('forum')
298 ->fields(array(
299 'tid' => $node->forum_tid,
300 'vid' => $node->vid,
301 'nid' => $node->nid,
302 ))
303 ->execute();
304 }
305 }
306 }
307
308 /**
309 * Implement hook_node_delete().
310 */
311 function forum_node_delete(stdClass $node) {
312 if (_forum_node_check_node_type($node)) {
313 db_delete('forum')
314 ->condition('nid', $node->nid)
315 ->execute();
316 db_delete('forum_index')
317 ->condition('nid', $node->nid)
318 ->execute();
319 }
320 }
321
322 /**
323 * Implement hook_node_load().
324 */
325 function forum_node_load($nodes) {
326 $node_vids = array();
327 foreach ($nodes as $node) {
328 if (_forum_node_check_node_type($node)) {
329 $node_vids[] = $node->vid;
330 }
331 }
332 if (!empty($node_vids)) {
333 $query = db_select('forum', 'f');
334 $query
335 ->fields('f', array('nid', 'tid'))
336 ->condition('f.vid', $node_vids);
337 $result = $query->execute();
338 foreach ($result as $record) {
339 $nodes[$record->nid]->forum_tid = $record->tid;
340 }
341 }
342 }
343
344 /**
345 * Implement hook_node_info().
346 */
347 function forum_node_info() {
348 return array(
349 'forum' => array(
350 'name' => t('Forum topic'),
351 'base' => 'forum',
352 'description' => t('A <em>forum topic</em> is the initial post to a new discussion thread within a forum.'),
353 'title_label' => t('Subject'),
354 )
355 );
356 }
357
358 /**
359 * Implement hook_permission().
360 */
361 function forum_permission() {
362 $perms = array(
363 'administer forums' => array(
364 'title' => t('Administer forums'),
365 'description' => t('Manage forums and configure forum administration settings.'),
366 ),
367 );
368 return $perms;
369 }
370
371 /**
372 * Implement hook_taxonomy().
373 */
374 function forum_taxonomy_term_delete($tid) {
375 // For containers, remove the tid from the forum_containers variable.
376 $containers = variable_get('forum_containers', array());
377 $key = array_search($tid, $containers);
378 if ($key !== FALSE) {
379 unset($containers[$key]);
380 }
381 variable_set('forum_containers', $containers);
382 }
383
384 /**
385 * Implement hook_comment_publish().
386 *
387 * This actually handles the insert and update of published nodes since
388 * comment_save() calls hook_comment_publish() for all published comments.
389 */
390 function forum_comment_publish($comment) {
391 _forum_update_forum_index($comment->nid);
392 }
393
394 /**
395 * Implement forum_comment_update().
396 *
397 * Comment module doesn't call hook_comment_unpublish() when saving individual
398 * comments so we need to check for those here.
399 */
400 function forum_comment_update($comment) {
401 // comment_save() calls hook_comment_publish() for all published comments
402 // so we to handle all other values here.
403 if (!$comment->status) {
404 _forum_update_forum_index($comment->nid);
405 }
406 }
407
408 /**
409 * Implements forum_comment_unpublish().
410 */
411 function forum_comment_unpublish($comment) {
412 _forum_update_forum_index($comment->nid);
413 }
414
415 /**
416 * Implement forum_comment_delete().
417 */
418 function forum_comment_delete($comment) {
419 _forum_update_forum_index($comment->nid);
420 }
421
422 /**
423 * Implement hook_field_attach_pre_insert().
424 */
425 function forum_field_attach_pre_insert($obj_type, $object, $skip_fields) {
426 if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {
427 $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
428 foreach ($object->taxonomy_forums as $language) {
429 foreach ($language as $delta) {
430 $query->values(array(
431 'nid' => $object->nid,
432 'title' => $object->title[FIELD_LANGUAGE_NONE][0]['value'],
433 'tid' => $delta['value'],
434 'sticky' => $object->sticky,
435 'created' => $object->created,
436 'comment_count' => 0,
437 'last_comment_timestamp' => $object->created,
438 ));
439 }
440 }
441 $query->execute();
442 }
443 }
444
445 /**
446 * Implement hook_field_attach_pre_update().
447 */
448 function forum_field_attach_pre_update($obj_type, $object, $skip_fields) {
449 $first_call = &drupal_static(__FUNCTION__, array());
450
451 if ($obj_type == 'node' && $object->status && _forum_node_check_node_type($object)) {
452 // We don't maintain data for old revisions, so clear all previous values
453 // from the table. Since this hook runs once per field, per object, make
454 // sure we only wipe values once.
455 if (!isset($first_call[$object->nid])) {
456 $first_call[$object->nid] = FALSE;
457 db_delete('forum_index')->condition('nid', $object->nid)->execute();
458 }
459 // Only save data to the table if the node is published.
460 if ($object->status) {
461 $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
462 foreach ($object->taxonomy_forums as $language) {
463 foreach ($language as $delta) {
464 $query->values(array(
465 'nid' => $object->nid,
466 'title' => $object->title,
467 'tid' => $delta['value'],
468 'sticky' => $object->sticky,
469 'created' => $object->created,
470 'comment_count' => 0,
471 'last_comment_timestamp' => $object->created,
472 ));
473 }
474 }
475 $query->execute();
476 // The logic for determining last_comment_count is fairly complex, so
477 // call _forum_update_forum_index() too.
478 _forum_update_forum_index($object->nid);
479 }
480 }
481 }
482
483 /**
484 * Implement hook_form_alter().
485 */
486 function forum_form_alter(&$form, $form_state, $form_id) {
487 $vid = variable_get('forum_nav_vocabulary', 0);
488 if (isset($form['vid']) && $form['vid']['#value'] == $vid) {
489 // Hide critical options from forum vocabulary.
490 if ($form_id == 'taxonomy_form_vocabulary') {
491 $form['help_forum_vocab'] = array(
492 '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
493 '#weight' => -1,
494 );
495 $form['hierarchy'] = array('#type' => 'value', '#value' => 1);
496 $form['delete']['#access'] = FALSE;
497 }
498 // Hide multiple parents select from forum terms.
499 elseif ($form_id == 'taxonomy_form_term') {
500 $form['advanced']['parent']['#access'] = FALSE;
501 }
502 }
503 if ($form_id == 'forum_node_form') {
504 $langcode = $form['taxonomy_forums']['#language'];
505 // Make the vocabulary required for 'real' forum-nodes.
506 $form['taxonomy_forums'][$langcode]['#required'] = TRUE;
507 $form['taxonomy_forums'][$langcode]['#multiple'] = FALSE;
508 }
509 }
510
511 /**
512 * Implement hook_block_info().
513 */
514 function forum_block_info() {
515 $blocks['active'] = array(
516 'info' => t('Active forum topics'),
517 'cache' => DRUPAL_CACHE_CUSTOM,
518 );
519 $blocks['new'] = array(
520 'info' => t('New forum topics'),
521 'cache' => DRUPAL_CACHE_CUSTOM,
522 );
523 return $blocks;
524 }
525
526 /**
527 * Implement hook_block_configure().
528 */
529 function forum_block_configure($delta = '') {
530 $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)));
531 return $form;
532 }
533
534 /**
535 * Implement hook_block_save().
536 */
537 function forum_block_save($delta = '', $edit = array()) {
538 variable_set('forum_block_num_' . $delta, $edit['forum_block_num_' . $delta]);
539 }
540
541 /**
542 * Implement hook_block_view().
543 *
544 * Generates a block containing the currently active forum topics and the
545 * most recently added forum topics.
546 */
547 function forum_block_view($delta = '') {
548 $query = db_select('forum_index', 'f')
549 ->fields('f')
550 ->addTag('node_access');
551 switch ($delta) {
552 case 'active':
553 $title = t('Active forum topics');
554 $query
555 ->orderBy('f.last_comment_timestamp', 'DESC')
556 ->range(0, variable_get('forum_block_num_active', '5'));
557 break;
558
559 case 'new':
560 $title = t('New forum topics');
561 $query
562 ->orderBy('f.created', 'DESC')
563 ->range(0, variable_get('forum_block_num_new', '5'));
564 break;
565 }
566
567 $cache_keys = array_merge(array('forum', $delta), drupal_render_cid_parts());
568 // Cache based on the altered query. Enables us to cache with node access enabled.
569 $query->preExecute();
570 $cache_keys[] = md5(serialize(array((string) $query, $query->getArguments())));
571
572 $block['subject'] = $title;
573 $block['content'] = array(
574 '#access' => user_access('access content'),
575 '#pre_render' => array('forum_block_view_pre_render'),
576 '#cache' => array(
577 'keys' => $cache_keys,
578 'expire' => CACHE_TEMPORARY,
579 ),
580 '#query' => $query,
581 );
582 return $block;
583 }
584
585 /**
586 * A #pre_render callback. Lists nodes based on the element's #query property.
587 *
588 * @see forum_block_view().
589 *
590 * @return
591 * A renderable array.
592 */
593 function forum_block_view_pre_render($elements) {
594 $result = $elements['#query']->execute();
595 if ($node_title_list = node_title_list($result)) {
596 $elements['forum_list'] = array('#markup' => $node_title_list);
597 $elements['forum_more'] = array('#markup' => theme('more_link', array('url' => url('forum'), 'title' => t('Read the latest forum topics.'))));
598 }
599 return $elements;
600 }
601
602 /**
603 * Implement hook_form().
604 */
605 function forum_form(stdClass $node, $form_state) {
606 $type = node_type_get_type($node);
607
608 if (!empty($node->nid)) {
609 $forum_terms = $node->taxonomy_forums;
610 // If editing, give option to leave shadows
611 $shadow = (count($forum_terms) > 1);
612 $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.'));
613 $form['forum_tid'] = array('#type' => 'value', '#value' => $node->forum_tid);
614 }
615
616 $form['#submit'][] = 'forum_submit';
617 // Assign the forum topic submit handler.
618
619 return $form;
620 }
621
622 /**
623 * Implement hook_url_outbound_alter().
624 */
625 function forum_url_outbound_alter(&$path, &$options, $original_path) {
626 if (preg_match('!^taxonomy/term/(\d+)!', $path, $matches)) {
627 $term = taxonomy_term_load($matches[1]);
628 if ($term && $term->vocabulary_machine_name == 'forums') {
629 $path = 'forum/' . $matches[1];
630 }
631 }
632 }
633
634 /**
635 * Returns a list of all forums for a given taxonomy id
636 *
637 * Forum objects contain the following fields
638 * -num_topics Number of topics in the forum
639 * -num_posts Total number of posts in all topics
640 * -last_post Most recent post for the forum
641 *
642 * @param $tid
643 * Taxonomy ID of the vocabulary that holds the forum list.
644 * @return
645 * Array of object containing the forum information.
646 */
647 function forum_get_forums($tid = 0) {
648
649 $forums = array();
650 $vid = variable_get('forum_nav_vocabulary', 0);
651 $_forums = taxonomy_get_tree($vid, $tid);
652
653 if (count($_forums)) {
654 $query = db_select('node', 'n');
655 $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
656 $query->join('forum', 'f', 'n.vid = f.vid');
657 $query->addExpression('COUNT(n.nid)', 'topic_count');
658 $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
659 $counts = $query
660 ->fields('f', array('tid'))
661 ->condition('status', 1)
662 ->groupBy('tid')
663 ->addTag('node_access')
664 ->execute()
665 ->fetchAllAssoc('tid');
666 }
667
668 foreach ($_forums as $forum) {
669 if (in_array($forum->tid, variable_get('forum_containers', array()))) {
670 $forum->container = 1;
671 }
672
673 if (!empty($counts[$forum->tid])) {
674 $forum->num_topics = $counts[$forum->tid]->topic_count;
675 $forum->num_posts = $counts[$forum->tid]->topic_count + $counts[$forum->tid]->comment_count;
676 }
677 else {
678 $forum->num_topics = 0;
679 $forum->num_posts = 0;
680 }
681
682 $query = db_select('node', 'n');
683 $query->join('users', 'u1', 'n.uid = u1.uid');
684 $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid));
685 $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
686 $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid');
687 $query->addExpression('IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name)', 'last_comment_name');
688
689 $topic = $query
690 ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid'))
691 ->condition('n.status', 1)
692 ->orderBy('last_comment_timestamp', 'DESC')
693 ->range(0, 1)
694 ->addTag('node_access')
695 ->execute()
696 ->fetchObject();
697
698 $last_post = new stdClass();
699 if (!empty($topic->last_comment_timestamp)) {
700 $last_post->created = $topic->last_comment_timestamp;
701 $last_post->name = $topic->last_comment_name;
702 $last_post->uid = $topic->last_comment_uid;
703 }
704 $forum->last_post = $last_post;
705
706 $forums[$forum->tid] = $forum;
707 }
708
709 return $forums;
710 }
711
712 /**
713 * Calculate the number of nodes the user has not yet read and are newer
714 * than NODE_NEW_LIMIT.
715 */
716 function _forum_topics_unread($term, $uid) {
717 $query = db_select('node', 'n');
718 $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term));
719 $query->join('history', 'h', 'n.nid = h.nid AND h.uid = :uid', array(':uid' => $uid));
720 $query->addExpression('COUNT(n.nid)', 'count');
721 return $query
722 ->condition('status', 1)
723 ->condition('n.created', NODE_NEW_LIMIT, '>')
724 ->isNull('h.nid')
725 ->addTag('node_access')
726 ->execute()
727 ->fetchField();
728 }
729
730 function forum_get_topics($tid, $sortby, $forum_per_page) {
731 global $user, $forum_topic_list_header;
732
733 $forum_topic_list_header = array(
734 NULL,
735 array('data' => t('Topic'), 'field' => 'f.title'),
736 array('data' => t('Replies'), 'field' => 'f.comment_count'),
737 array('data' => t('Last reply'), 'field' => 'f.last_comment_timestamp'),
738 );
739
740 $order = _forum_get_topic_order($sortby);
741 for ($i = 0; $i < count($forum_topic_list_header); $i++) {
742 if ($forum_topic_list_header[$i]['field'] == $order['field']) {
743 $forum_topic_list_header[$i]['sort'] = $order['sort'];
744 }
745 }
746
747 $query = db_select('forum_index', 'f')->extend('PagerDefault')->extend('TableSort');
748 $query->fields('f');
749 $query
750 ->condition('f.tid', $tid)
751 ->addTag('node_access')
752 ->orderBy('f.sticky', 'DESC')
753 ->orderByHeader($forum_topic_list_header)
754 ->orderBy('f.last_comment_timestamp', 'DESC')
755 ->limit($forum_per_page);
756
757 $count_query = db_select('forum_index', 'f');
758 $count_query->condition('f.tid', $tid);
759 $count_query->addExpression('COUNT(*)');
760 $count_query->addTag('node_access');
761
762 $query->setCountQuery($count_query);
763 $result = $query->execute();
764 $nids = array();
765 foreach ($result as $record) {
766 $nids[] = $record->nid;
767 }
768 if ($nids) {
769 $result = db_query("SELECT n.title, n.nid, n.sticky, n.created, n.uid, n.comment AS comment_mode, ncs.*, f.tid AS forum_tid, u.name, IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name) AS last_comment_name FROM {node} n INNER JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid INNER JOIN {forum} f ON n.vid = f.vid INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {users} u2 ON ncs.last_comment_uid = u2.uid WHERE n.nid IN (:nids)", array(':nids' => $nids));
770 }
771 else {
772 $result = array();
773 }
774
775 $topics = array();
776 foreach ($result as $topic) {
777 if ($user->uid) {
778 // folder is new if topic is new or there are new comments since last visit
779 if ($topic->forum_tid != $tid) {
780 $topic->new = 0;
781 }
782 else {
783 $history = _forum_user_last_visit($topic->nid);
784 $topic->new_replies = comment_num_new($topic->nid, $history);
785 $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history);
786 }
787 }
788 else {
789 // Do not track "new replies" status for topics if the user is anonymous.
790 $topic->new_replies = 0;
791 $topic->new = 0;
792 }
793
794 if ($topic->comment_count > 0) {
795 $last_reply = new stdClass();
796 $last_reply->created = $topic->last_comment_timestamp;
797 $last_reply->name = $topic->last_comment_name;
798 $last_reply->uid = $topic->last_comment_uid;
799 $topic->last_reply = $last_reply;
800 }
801 $topics[] = $topic;
802 }
803
804 return $topics;
805 }
806
807 /**
808 * Process variables for forums.tpl.php
809 *
810 * The $variables array contains the following arguments:
811 * - $forums
812 * - $topics
813 * - $parents
814 * - $tid
815 * - $sortby
816 * - $forum_per_page
817 *
818 * @see forums.tpl.php
819 */
820 function template_preprocess_forums(&$variables) {
821 global $user;
822
823 $vid = variable_get('forum_nav_vocabulary', 0);
824 $vocabulary = taxonomy_vocabulary_load($vid);
825 $title = !empty($vocabulary->name) ? $vocabulary->name : '';
826
827 // Breadcrumb navigation:
828 $breadcrumb[] = l(t('Home'), NULL);
829 if ($variables['tid']) {
830 $breadcrumb[] = l($vocabulary->name, 'forum');
831 }
832 if ($variables['parents']) {
833 $variables['parents'] = array_reverse($variables['parents']);
834 foreach ($variables['parents'] as $p) {
835 if ($p->tid == $variables['tid']) {
836 $title = $p->name;
837 }
838 else {
839 $breadcrumb[] = l($p->name, 'forum/' . $p->tid);
840 }
841 }
842 }
843 drupal_set_breadcrumb($breadcrumb);
844 drupal_set_title($title);
845
846 if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
847 // Format the "post new content" links listing.
848 $forum_types = array();
849
850 // Loop through all bundles for forum taxonomy vocabulary field.
851 $field = field_info_field('taxonomy_' . $vocabulary->machine_name);
852 foreach ($field['bundles']['node'] as $type) {
853 // Check if the current user has the 'create' permission for this node type.
854 if (node_access('create', $type)) {
855 // Fetch the "General" name of the content type;
856 // Push the link with title and url to the array.
857 $forum_types[$type] = array('title' => t('Post new @node_type', array('@node_type' => node_type_get_name($type))), 'href' => 'node/add/' . str_replace('_', '-', $type) . '/' . $variables['tid']);
858 }
859 }
860
861 if (empty($forum_types)) {
862 // The user is logged-in; but denied access to create any new forum content type.
863 if ($user->uid) {
864 $forum_types['disallowed'] = array('title' => t('You are not allowed to post new content in the forum.'));
865 }
866 // The user is not logged-in; and denied access to create any new forum content type.
867 else {
868 $forum_types['login'] = array('title' => t('<a href="@login">Login</a> to post new content in the forum.', array('@login' => url('user/login', array('query' => drupal_get_destination())))), 'html' => TRUE);
869 }
870 }
871 $variables['links'] = $forum_types;
872
873 if (!empty($variables['forums'])) {
874 $variables['forums'] = theme('forum_list', $variables);
875 }
876 else {
877 $variables['forums'] = '';
878 }
879
880 if ($variables['tid'] && !in_array($variables['tid'], variable_get('forum_containers', array()))) {
881 $variables['topics'] = theme('forum_topic_list', $variables);
882 drupal_add_feed(url('taxonomy/term/' . $variables['tid'] . '/0/feed'), 'RSS - ' . $title);
883 }
884 else {
885 $variables['topics'] = '';
886 }
887
888 // Provide separate template suggestions based on what's being output. Topic id is also accounted for.
889 // Check both variables to be safe then the inverse. Forums with topic ID's take precedence.
890 if ($variables['forums'] && !$variables['topics']) {
891 $variables['template_files'][] = 'forums-containers';
892 $variables['template_files'][] = 'forums-' . $variables['tid'];
893 $variables['template_files'][] = 'forums-containers-' . $variables['tid'];
894 }
895 elseif (!$variables['forums'] && $variables['topics']) {
896 $variables['template_files'][] = 'forums-topics';
897 $variables['template_files'][] = 'forums-' . $variables['tid'];
898 $variables['template_files'][] = 'forums-topics-' . $variables['tid'];
899 }
900 else {
901 $variables['template_files'][] = 'forums-' . $variables['tid'];
902 }
903
904 }
905 else {
906 drupal_set_title(t('No forums defined'), PASS_THROUGH);
907 $variables['links'] = array();
908 $variables['forums'] = '';
909 $variables['topics'] = '';
910 }
911 }
912
913 /**
914 * Process variables to format a forum listing.
915 *
916 * $variables contains the following information:
917 * - $forums
918 * - $parents
919 * - $tid
920 *
921 * @see forum-list.tpl.php
922 * @see theme_forum_list()
923 */
924 function template_preprocess_forum_list(&$variables) {
925 global $user;
926 $row = 0;
927 // Sanitize each forum so that the template can safely print the data.
928 foreach ($variables['forums'] as $id => $forum) {
929 $variables['forums'][$id]->description = !empty($forum->description) ? filter_xss_admin($forum->description) : '';
930 $variables['forums'][$id]->link = url("forum/$forum->tid");
931 $variables['forums'][$id]->name = check_plain($forum->name);
932 $variables['forums'][$id]->is_container = !empty($forum->container);
933 $variables['forums'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
934 $row++;
935
936 $variables['forums'][$id]->new_text = '';
937 $variables['forums'][$id]->new_url = '';
938 $variables['forums'][$id]->new_topics = 0;
939 $variables['forums'][$id]->old_topics = $forum->num_topics;
940 if ($user->uid) {
941 $variables['forums'][$id]->new_topics = _forum_topics_unread($forum->tid, $user->uid);
942 if ($variables['forums'][$id]->new_topics) {
943 $variables['forums'][$id]->new_text = format_plural($variables['forums'][$id]->new_topics, '1 new', '@count new');
944 $variables['forums'][$id]->new_url = url("forum/$forum->tid", array('fragment' => 'new'));
945 }
946 $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
947 }
948 $variables['forums'][$id]->last_reply = theme('forum_submitted', array('topic' => $forum->last_post));
949 }
950 // Give meaning to $tid for themers. $tid actually stands for term id.
951 $variables['forum_id'] = $variables['tid'];
952 unset($variables['tid']);
953 }
954
955 /**
956 * Preprocess variables to format the topic listing.
957 *
958 * $variables contains the following data:
959 * - $tid
960 * - $topics
961 * - $sortby
962 * - $forum_per_page
963 *
964 * @see forum-topic-list.tpl.php
965 * @see theme_forum_topic_list()
966 */
967 function template_preprocess_forum_topic_list(&$variables) {
968 global $forum_topic_list_header;
969
970 // Create the tablesorting header.
971 $ts = tablesort_init($forum_topic_list_header);
972 $header = '';
973 foreach ($forum_topic_list_header as $cell) {
974 $cell = tablesort_header($cell, $forum_topic_list_header, $ts);
975 $header .= _theme_table_cell($cell, TRUE);
976 }
977 $variables['header'] = $header;
978
979 if (!empty($variables['topics'])) {
980 $row = 0;
981 foreach ($variables['topics'] as $id => $topic) {
982 $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));
983 $variables['topics'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
984 $row++;
985
986 // We keep the actual tid in forum table, if it's different from the
987 // current tid then it means the topic appears in two forums, one of
988 // them is a shadow copy.
989 if ($variables['tid'] != $topic->forum_tid) {
990 $variables['topics'][$id]->moved = TRUE;
991 $variables['topics'][$id]->title = check_plain($topic->title);
992 $variables['topics'][$id]->message = l(t('This topic has been moved'), "forum/$topic->forum_tid");
993 }
994 else {
995 $variables['topics'][$id]->moved = FALSE;
996 $variables['topics'][$id]->title = l($topic->title, "node/$topic->nid");
997 $variables['topics'][$id]->message = '';
998 }
999 $topic->uid = $topic->last_comment_uid ? $topic->last_comment_uid : $topic->uid;
1000 $variables['topics'][$id]->created = theme('forum_submitted', array('topic' => $topic));
1001 $variables['topics'][$id]->last_reply = theme('forum_submitted', array('topic' => isset($topic->last_reply) ? $topic->last_reply : NULL));
1002
1003 $variables['topics'][$id]->new_text = '';
1004 $variables['topics'][$id]->new_url = '';
1005 if ($topic->new_replies) {
1006 $variables['topics'][$id]->new_text = format_plural($topic->new_replies, '1 new', '@count new');
1007 $variables['topics'][$id]->new_url = url("node/$topic->nid", array('query' => comment_new_page_count($topic->num_comments, $topic->new_replies, $topic), 'fragment' => 'new'));
1008 }
1009
1010 }
1011 }
1012 else {
1013 // Make this safe for the template
1014 $variables['topics'] = array();
1015 }
1016 // Give meaning to $tid for themers. $tid actually stands for term id.
1017 $variables['topic_id'] = $variables['tid'];
1018 unset($variables['tid']);
1019
1020 $variables['pager'] = theme('pager', array('tags' => NULL));
1021 }
1022
1023 /**
1024 * Process variables to format the icon for each individual topic.
1025 *
1026 * $variables contains the following data:
1027 * - $new_posts
1028 * - $num_posts = 0
1029 * - $comment_mode = 0
1030 * - $sticky = 0
1031 *
1032 * @see forum-icon.tpl.php
1033 * @see theme_forum_icon()
1034 */
1035 function template_preprocess_forum_icon(&$variables) {
1036 $variables['hot_threshold'] = variable_get('forum_hot_topic', 15);
1037 if ($variables['num_posts'] > $variables['hot_threshold']) {
1038 $variables['icon'] = $variables['new_posts'] ? 'hot-new' : 'hot';
1039 }
1040 else {
1041 $variables['icon'] = $variables['new_posts'] ? 'new' : 'default';
1042 }
1043
1044 if ($variables['comment_mode'] == COMMENT_NODE_CLOSED || $variables['comment_mode'] == COMMENT_NODE_HIDDEN) {
1045 $variables['icon'] = 'closed';
1046 }
1047
1048 if ($variables['sticky'] == 1) {
1049 $variables['icon'] = 'sticky';
1050 }
1051 }
1052
1053 /**
1054 * Process variables to format submission info for display in the forum list and topic list.
1055 *
1056 * $variables will contain: $topic
1057 *
1058 * @see forum-submitted.tpl.php
1059 * @see theme_forum_submitted()
1060 */
1061 function template_preprocess_forum_submitted(&$variables) {
1062 $variables['author'] = isset($variables['topic']->uid) ? theme('username', array('account' => $variables['topic'])) : '';
1063 $variables['time'] = isset($variables['topic']->created) ? format_interval(REQUEST_TIME - $variables['topic']->created) : '';
1064 }
1065
1066 function _forum_user_last_visit($nid) {
1067 global $user;
1068 $history = &drupal_static(__FUNCTION__, array());
1069
1070 if (empty($history)) {
1071 $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid', array(':uid' => $user->uid));
1072 foreach ($result as $t) {
1073 $history[$t->nid] = $t->timestamp > NODE_NEW_LIMIT ? $t->timestamp : NODE_NEW_LIMIT;
1074 }
1075 }
1076 return isset($history[$nid]) ? $history[$nid] : NODE_NEW_LIMIT;
1077 }
1078
1079 function _forum_get_topic_order($sortby) {
1080 switch ($sortby) {
1081 case 1:
1082