summaryrefslogtreecommitdiff
path: root/kolab.org/www/drupal-7.26/modules/forum
diff options
context:
space:
mode:
Diffstat (limited to 'kolab.org/www/drupal-7.26/modules/forum')
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum-icon.tpl.php26
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum-list.tpl.php77
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum-rtl.css24
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum-submitted.tpl.php30
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum-topic-list.tpl.php72
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum.admin.inc352
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum.css54
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum.info16
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum.install467
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum.module1396
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum.pages.inc37
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forum.test687
-rw-r--r--kolab.org/www/drupal-7.26/modules/forum/forums.tpl.php24
13 files changed, 3262 insertions, 0 deletions
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum-icon.tpl.php b/kolab.org/www/drupal-7.26/modules/forum/forum-icon.tpl.php
new file mode 100644
index 0000000..fd1cd13
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum-icon.tpl.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Displays an appropriate icon for a forum post.
+ *
+ * Available variables:
+ * - $new_posts: Indicates whether or not the topic contains new posts.
+ * - $icon_class: The icon to display. May be one of 'hot', 'hot-new', 'new',
+ * 'default', 'closed', or 'sticky'.
+ * - $first_new: Indicates whether this is the first topic with new posts.
+ *
+ * @see template_preprocess_forum_icon()
+ * @see theme_forum_icon()
+ *
+ * @ingroup themeable
+ */
+?>
+<div class="topic-status-<?php print $icon_class ?>" title="<?php print $icon_title ?>">
+<?php if ($first_new): ?>
+ <a id="new"></a>
+<?php endif; ?>
+
+ <span class="element-invisible"><?php print $icon_title ?></span>
+
+</div>
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum-list.tpl.php b/kolab.org/www/drupal-7.26/modules/forum/forum-list.tpl.php
new file mode 100644
index 0000000..01c74a3
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum-list.tpl.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Displays a list of forums and containers.
+ *
+ * Available variables:
+ * - $forums: An array of forums and containers to display. It is keyed to the
+ * numeric IDs of all child forums and containers. Each $forum in $forums
+ * contains:
+ * - $forum->is_container: TRUE if the forum can contain other forums. FALSE
+ * if the forum can contain only topics.
+ * - $forum->depth: How deep the forum is in the current hierarchy.
+ * - $forum->zebra: 'even' or 'odd' string used for row class.
+ * - $forum->icon_class: 'default' or 'new' string used for forum icon class.
+ * - $forum->icon_title: Text alternative for the forum icon.
+ * - $forum->name: The name of the forum.
+ * - $forum->link: The URL to link to this forum.
+ * - $forum->description: The description of this forum.
+ * - $forum->new_topics: TRUE if the forum contains unread posts.
+ * - $forum->new_url: A URL to the forum's unread posts.
+ * - $forum->new_text: Text for the above URL, which tells how many new posts.
+ * - $forum->old_topics: A count of posts that have already been read.
+ * - $forum->num_posts: The total number of posts in the forum.
+ * - $forum->last_reply: Text representing the last time a forum was posted or
+ * commented in.
+ * - $forum_id: Forum ID for the current forum. Parent to all items within the
+ * $forums array.
+ *
+ * @see template_preprocess_forum_list()
+ * @see theme_forum_list()
+ *
+ * @ingroup themeable
+ */
+?>
+<table id="forum-<?php print $forum_id; ?>">
+ <thead>
+ <tr>
+ <th><?php print t('Forum'); ?></th>
+ <th><?php print t('Topics');?></th>
+ <th><?php print t('Posts'); ?></th>
+ <th><?php print t('Last post'); ?></th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($forums as $child_id => $forum): ?>
+ <tr id="forum-list-<?php print $child_id; ?>" class="<?php print $forum->zebra; ?>">
+ <td <?php print $forum->is_container ? 'colspan="4" class="container"' : 'class="forum"'; ?>>
+ <?php /* Enclose the contents of this cell with X divs, where X is the
+ * depth this forum resides at. This will allow us to use CSS
+ * left-margin for indenting.
+ */ ?>
+ <?php print str_repeat('<div class="indent">', $forum->depth); ?>
+ <div class="icon forum-status-<?php print $forum->icon_class; ?>" title="<?php print $forum->icon_title; ?>">
+ <span class="element-invisible"><?php print $forum->icon_title; ?></span>
+ </div>
+ <div class="name"><a href="<?php print $forum->link; ?>"><?php print $forum->name; ?></a></div>
+ <?php if ($forum->description): ?>
+ <div class="description"><?php print $forum->description; ?></div>
+ <?php endif; ?>
+ <?php print str_repeat('</div>', $forum->depth); ?>
+ </td>
+ <?php if (!$forum->is_container): ?>
+ <td class="topics">
+ <?php print $forum->num_topics ?>
+ <?php if ($forum->new_topics): ?>
+ <br />
+ <a href="<?php print $forum->new_url; ?>"><?php print $forum->new_text; ?></a>
+ <?php endif; ?>
+ </td>
+ <td class="posts"><?php print $forum->num_posts ?></td>
+ <td class="last-reply"><?php print $forum->last_reply ?></td>
+ <?php endif; ?>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+</table>
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum-rtl.css b/kolab.org/www/drupal-7.26/modules/forum/forum-rtl.css
new file mode 100644
index 0000000..3f2a88b
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum-rtl.css
@@ -0,0 +1,24 @@
+/**
+ * @file
+ * Right-to-left styling for the Forum module.
+ */
+
+#forum td.forum .icon {
+ float: right;
+ margin: 0 0 0 9px;
+}
+#forum div.indent {
+ margin-left: 0;
+ margin-right: 20px;
+}
+.forum-topic-navigation {
+ padding: 1em 3em 0 0;
+}
+.forum-topic-navigation .topic-previous {
+ text-align: left;
+ float: right;
+}
+.forum-topic-navigation .topic-next {
+ text-align: right;
+ float: left;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum-submitted.tpl.php b/kolab.org/www/drupal-7.26/modules/forum/forum-submitted.tpl.php
new file mode 100644
index 0000000..18fea8f
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum-submitted.tpl.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Formats a forum post submission string.
+ *
+ * The submission string indicates when and by whom a topic was submitted.
+ *
+ * Available variables:
+ * - $author: The author of the post.
+ * - $time: How long ago the post was created.
+ * - $topic: An object with the raw data of the post. Potentially unsafe. Be
+ * sure to clean this data before printing.
+ *
+ * @see template_preprocess_forum_submitted()
+ * @see theme_forum_submitted()
+ *
+ * @ingroup themeable
+ */
+?>
+<?php if ($time): ?>
+ <span class="submitted">
+ <?php print t('By !author @time ago', array(
+ '@time' => $time,
+ '!author' => $author,
+ )); ?>
+ </span>
+<?php else: ?>
+ <?php print t('n/a'); ?>
+<?php endif; ?>
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum-topic-list.tpl.php b/kolab.org/www/drupal-7.26/modules/forum/forum-topic-list.tpl.php
new file mode 100644
index 0000000..6427814
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum-topic-list.tpl.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Displays a list of forum topics.
+ *
+ * Available variables:
+ * - $header: The table header. This is pre-generated with click-sorting
+ * information. If you need to change this, see
+ * template_preprocess_forum_topic_list().
+ * - $pager: The pager to display beneath the table.
+ * - $topics: An array of topics to be displayed. Each $topic in $topics
+ * contains:
+ * - $topic->icon: The icon to display.
+ * - $topic->moved: A flag to indicate whether the topic has been moved to
+ * another forum.
+ * - $topic->title: The title of the topic. Safe to output.
+ * - $topic->message: If the topic has been moved, this contains an
+ * explanation and a link.
+ * - $topic->zebra: 'even' or 'odd' string used for row class.
+ * - $topic->comment_count: The number of replies on this topic.
+ * - $topic->new_replies: A flag to indicate whether there are unread
+ * comments.
+ * - $topic->new_url: If there are unread replies, this is a link to them.
+ * - $topic->new_text: Text containing the translated, properly pluralized
+ * count.
+ * - $topic->created: A string representing when the topic was posted. Safe
+ * to output.
+ * - $topic->last_reply: An outputtable string representing when the topic was
+ * last replied to.
+ * - $topic->timestamp: The raw timestamp this topic was posted.
+ * - $topic_id: Numeric ID for the current forum topic.
+ *
+ * @see template_preprocess_forum_topic_list()
+ * @see theme_forum_topic_list()
+ *
+ * @ingroup themeable
+ */
+?>
+<table id="forum-topic-<?php print $topic_id; ?>">
+ <thead>
+ <tr><?php print $header; ?></tr>
+ </thead>
+ <tbody>
+ <?php foreach ($topics as $topic): ?>
+ <tr class="<?php print $topic->zebra;?>">
+ <td class="icon"><?php print $topic->icon; ?></td>
+ <td class="title">
+ <div>
+ <?php print $topic->title; ?>
+ </div>
+ <div>
+ <?php print $topic->created; ?>
+ </div>
+ </td>
+ <?php if ($topic->moved): ?>
+ <td colspan="3"><?php print $topic->message; ?></td>
+ <?php else: ?>
+ <td class="replies">
+ <?php print $topic->comment_count; ?>
+ <?php if ($topic->new_replies): ?>
+ <br />
+ <a href="<?php print $topic->new_url; ?>"><?php print $topic->new_text; ?></a>
+ <?php endif; ?>
+ </td>
+ <td class="last-reply"><?php print $topic->last_reply; ?></td>
+ <?php endif; ?>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+</table>
+<?php print $pager; ?>
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum.admin.inc b/kolab.org/www/drupal-7.26/modules/forum/forum.admin.inc
new file mode 100644
index 0000000..712cf54
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum.admin.inc
@@ -0,0 +1,352 @@
+<?php
+
+/**
+ * @file
+ * Administrative page callbacks for the Forum module.
+ */
+
+/**
+ * Page callback: Returns a form for creating a new forum or container.
+ *
+ * @param $type
+ * What is being added. Possible values are 'forum' and 'container'.
+ * @param $edit
+ * (optional) Associative array containing a forum term to be edited.
+ * Defaults to an empty array.
+ *
+ * @return
+ * A form for creating a new forum or container.
+ *
+ * @see forum_menu()
+ */
+function forum_form_main($type, $edit = array()) {
+ $edit = (array) $edit;
+ if ((isset($_POST['op']) && $_POST['op'] == t('Delete')) || !empty($_POST['confirm'])) {
+ return drupal_get_form('forum_confirm_delete', $edit['tid']);
+ }
+ switch ($type) {
+ case 'forum':
+ return drupal_get_form('forum_form_forum', $edit);
+ break;
+ case 'container':
+ return drupal_get_form('forum_form_container', $edit);
+ break;
+ }
+}
+
+/**
+ * Form constructor for adding and editing a forum.
+ *
+ * @param $edit
+ * (optional) Associative array containing a forum term to be added or edited.
+ * Defaults to an empty array.
+ *
+ * @see forum_form_submit()
+ * @ingroup forms
+ */
+function forum_form_forum($form, &$form_state, $edit = array()) {
+ $edit += array(
+ 'name' => '',
+ 'description' => '',
+ 'tid' => NULL,
+ 'weight' => 0,
+ );
+ $form['name'] = array('#type' => 'textfield',
+ '#title' => t('Forum name'),
+ '#default_value' => $edit['name'],
+ '#maxlength' => 255,
+ '#description' => t('Short but meaningful name for this collection of threaded discussions.'),
+ '#required' => TRUE,
+ );
+ $form['description'] = array('#type' => 'textarea',
+ '#title' => t('Description'),
+ '#default_value' => $edit['description'],
+ '#description' => t('Description and guidelines for discussions within this forum.'),
+ );
+ $form['parent']['#tree'] = TRUE;
+ $form['parent'][0] = _forum_parent_select($edit['tid'], t('Parent'), 'forum');
+ $form['weight'] = array('#type' => 'weight',
+ '#title' => t('Weight'),
+ '#default_value' => $edit['weight'],
+ '#description' => t('Forums are displayed in ascending order by weight (forums with equal weights are displayed alphabetically).'),
+ );
+
+ $form['vid'] = array('#type' => 'hidden', '#value' => variable_get('forum_nav_vocabulary', ''));
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save'));
+ if ($edit['tid']) {
+ $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+ $form['tid'] = array('#type' => 'hidden', '#value' => $edit['tid']);
+ }
+ $form['#submit'][] = 'forum_form_submit';
+ $form['#theme'] = 'forum_form';
+
+ return $form;
+}
+
+/**
+ * Form submission handler for forum_form_forum() and forum_form_container().
+ */
+function forum_form_submit($form, &$form_state) {
+ if ($form['form_id']['#value'] == 'forum_form_container') {
+ $container = TRUE;
+ $type = t('forum container');
+ }
+ else {
+ $container = FALSE;
+ $type = t('forum');
+ }
+
+ $term = (object) $form_state['values'];
+ $status = taxonomy_term_save($term);
+ switch ($status) {
+ case SAVED_NEW:
+ if ($container) {
+ $containers = variable_get('forum_containers', array());
+ $containers[] = $term->tid;
+ variable_set('forum_containers', $containers);
+ }
+ $form_state['values']['tid'] = $term->tid;
+ drupal_set_message(t('Created new @type %term.', array('%term' => $form_state['values']['name'], '@type' => $type)));
+ break;
+ case SAVED_UPDATED:
+ drupal_set_message(t('The @type %term has been updated.', array('%term' => $form_state['values']['name'], '@type' => $type)));
+ // Clear the page and block caches to avoid stale data.
+ cache_clear_all();
+ break;
+ }
+ $form_state['redirect'] = 'admin/structure/forum';
+ return;
+}
+
+/**
+ * Returns HTML for a forum form.
+ *
+ * By default this does not alter the appearance of a form at all, but is
+ * provided as a convenience for themers.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - form: A render element representing the form.
+ *
+ * @ingroup themeable
+ */
+function theme_forum_form($variables) {
+ return drupal_render_children($variables['form']);
+}
+
+/**
+ * Form constructor for adding and editing forum containers.
+ *
+ * @param $edit
+ * (optional) Associative array containing a container term to be added or edited.
+ * Defaults to an empty array.
+ *
+ * @see forum_form_submit()
+ * @ingroup forms
+ */
+function forum_form_container($form, &$form_state, $edit = array()) {
+ $edit += array(
+ 'name' => '',
+ 'description' => '',
+ 'tid' => NULL,
+ 'weight' => 0,
+ );
+ // Handle a delete operation.
+ $form['name'] = array(
+ '#title' => t('Container name'),
+ '#type' => 'textfield',
+ '#default_value' => $edit['name'],
+ '#maxlength' => 255,
+ '#description' => t('Short but meaningful name for this collection of related forums.'),
+ '#required' => TRUE
+ );
+
+ $form['description'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Description'),
+ '#default_value' => $edit['description'],
+ '#description' => t('Description and guidelines for forums within this container.')
+ );
+ $form['parent']['#tree'] = TRUE;
+ $form['parent'][0] = _forum_parent_select($edit['tid'], t('Parent'), 'container');
+ $form['weight'] = array(
+ '#type' => 'weight',
+ '#title' => t('Weight'),
+ '#default_value' => $edit['weight'],
+ '#description' => t('Containers are displayed in ascending order by weight (containers with equal weights are displayed alphabetically).')
+ );
+
+ $form['vid'] = array(
+ '#type' => 'hidden',
+ '#value' => variable_get('forum_nav_vocabulary', ''),
+ );
+ $form['actions'] = array('#type' => 'actions');
+ $form['actions']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save')
+ );
+ if ($edit['tid']) {
+ $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
+ $form['tid'] = array('#type' => 'value', '#value' => $edit['tid']);
+ }
+ $form['#submit'][] = 'forum_form_submit';
+ $form['#theme'] = 'forum_form';
+
+ return $form;
+}
+
+/**
+ * Form constructor for confirming deletion of a forum taxonomy term.
+ *
+ * @param $tid
+ * ID of the term to be deleted.
+ *
+ * @see forum_confirm_delete_submit()
+ * @ingroup forms
+ */
+function forum_confirm_delete($form, &$form_state, $tid) {
+ $term = taxonomy_term_load($tid);
+
+ $form['tid'] = array('#type' => 'value', '#value' => $tid);
+ $form['name'] = array('#type' => 'value', '#value' => $term->name);
+
+ return confirm_form($form, t('Are you sure you want to delete the forum %name?', array('%name' => $term->name)), 'admin/structure/forum', t('Deleting a forum or container will also delete its sub-forums, if any. To delete posts in this forum, visit <a href="@content">content administration</a> first. This action cannot be undone.', array('@content' => url('admin/content'))), t('Delete'), t('Cancel'));
+}
+
+/**
+ * Form submission handler for forum_confirm_delete().
+ */
+function forum_confirm_delete_submit($form, &$form_state) {
+ taxonomy_term_delete($form_state['values']['tid']);
+ drupal_set_message(t('The forum %term and all sub-forums have been deleted.', array('%term' => $form_state['values']['name'])));
+ watchdog('content', 'forum: deleted %term and all its sub-forums.', array('%term' => $form_state['values']['name']));
+
+ $form_state['redirect'] = 'admin/structure/forum';
+ return;
+}
+
+/**
+ * Form constructor for the forum settings page.
+ *
+ * @see forum_menu()
+ * @see system_settings_form()
+ * @ingroup forms
+ */
+function forum_admin_settings($form) {
+ $number = drupal_map_assoc(array(5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 80, 100, 150, 200, 250, 300, 350, 400, 500));
+ $form['forum_hot_topic'] = array('#type' => 'select',
+ '#title' => t('Hot topic threshold'),
+ '#default_value' => variable_get('forum_hot_topic', 15),
+ '#options' => $number,
+ '#description' => t('The number of replies a topic must have to be considered "hot".'),
+ );
+ $number = drupal_map_assoc(array(10, 25, 50, 75, 100));
+ $form['forum_per_page'] = array('#type' => 'select',
+ '#title' => t('Topics per page'),
+ '#default_value' => variable_get('forum_per_page', 25),
+ '#options' => $number,
+ '#description' => t('Default number of forum topics displayed per page.'),
+ );
+ $forder = array(1 => t('Date - newest first'), 2 => t('Date - oldest first'), 3 => t('Posts - most active first'), 4 => t('Posts - least active first'));
+ $form['forum_order'] = array('#type' => 'radios',
+ '#title' => t('Default order'),
+ '#default_value' => variable_get('forum_order', 1),
+ '#options' => $forder,
+ '#description' => t('Default display order for topics.'),
+ );
+ return system_settings_form($form);
+}
+
+/**
+ * Form constructor for the forum overview form.
+ *
+ * Returns a form for controlling the hierarchy of existing forums and
+ * containers.
+ *
+ * @see forum_menu()
+ * @ingroup forms
+ */
+function forum_overview($form, &$form_state) {
+ module_load_include('inc', 'taxonomy', 'taxonomy.admin');
+
+ $vid = variable_get('forum_nav_vocabulary', '');
+ $vocabulary = taxonomy_vocabulary_load($vid);
+ $form = taxonomy_overview_terms($form, $form_state, $vocabulary);
+
+ foreach (element_children($form) as $key) {
+ if (isset($form[$key]['#term'])) {
+ $term = $form[$key]['#term'];
+ $form[$key]['view']['#href'] = 'forum/' . $term['tid'];
+ if (in_array($form[$key]['#term']['tid'], variable_get('forum_containers', array()))) {
+ $form[$key]['edit']['#title'] = t('edit container');
+ $form[$key]['edit']['#href'] = 'admin/structure/forum/edit/container/' . $term['tid'];
+ }
+ else {
+ $form[$key]['edit']['#title'] = t('edit forum');
+ $form[$key]['edit']['#href'] = 'admin/structure/forum/edit/forum/' . $term['tid'];
+ }
+ }
+ }
+
+ // Remove the alphabetical reset.
+ unset($form['actions']['reset_alphabetical']);
+
+ // The form needs to have submit and validate handlers set explicitly.
+ $form['#theme'] = 'taxonomy_overview_terms';
+ $form['#submit'] = array('taxonomy_overview_terms_submit'); // Use the existing taxonomy overview submit handler.
+ $form['#empty_text'] = t('No containers or forums available. <a href="@container">Add container</a> or <a href="@forum">Add forum</a>.', array('@container' => url('admin/structure/forum/add/container'), '@forum' => url('admin/structure/forum/add/forum')));
+ return $form;
+}
+
+/**
+ * Returns a select box for available parent terms.
+ *
+ * @param $tid
+ * ID of the term that is being added or edited.
+ * @param $title
+ * Title for the select box.
+ * @param $child_type
+ * Whether the child is a forum or a container.
+ *
+ * @return
+ * A select form element.
+ */
+function _forum_parent_select($tid, $title, $child_type) {
+
+ $parents = taxonomy_get_parents($tid);
+ if ($parents) {
+ $parent = array_shift($parents);
+ $parent = $parent->tid;
+ }
+ else {
+ $parent = 0;
+ }
+
+ $vid = variable_get('forum_nav_vocabulary', '');
+ $children = taxonomy_get_tree($vid, $tid);
+
+ // A term can't be the child of itself, nor of its children.
+ foreach ($children as $child) {
+ $exclude[] = $child->tid;
+ }
+ $exclude[] = $tid;
+
+ $tree = taxonomy_get_tree($vid);
+ $options[0] = '<' . t('root') . '>';
+ if ($tree) {
+ foreach ($tree as $term) {
+ if (!in_array($term->tid, $exclude)) {
+ $options[$term->tid] = str_repeat(' -- ', $term->depth) . $term->name;
+ }
+ }
+ }
+ if ($child_type == 'container') {
+ $description = t('Containers are usually placed at the top (root) level, but may also be placed inside another container or forum.');
+ }
+ elseif ($child_type == 'forum') {
+ $description = t('Forums may be placed at the top (root) level, or inside another container or forum.');
+ }
+
+ return array('#type' => 'select', '#title' => $title, '#default_value' => $parent, '#options' => $options, '#description' => $description, '#required' => TRUE);
+}
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum.css b/kolab.org/www/drupal-7.26/modules/forum/forum.css
new file mode 100644
index 0000000..480e07b
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum.css
@@ -0,0 +1,54 @@
+/**
+ * @file
+ * Styling for the Forum module.
+ */
+
+#forum .description {
+ font-size: 0.9em;
+ margin: 0.5em;
+}
+#forum td.created,
+#forum td.posts,
+#forum td.topics,
+#forum td.last-reply,
+#forum td.replies,
+#forum td.pager {
+ white-space: nowrap;
+}
+
+#forum td.forum .icon {
+ background-image: url(../../misc/forum-icons.png);
+ background-repeat: no-repeat;
+ float: left; /* LTR */
+ height: 24px;
+ margin: 0 9px 0 0; /* LTR */
+ width: 24px;
+}
+#forum td.forum .forum-status-new {
+ background-position: -24px 0;
+}
+
+#forum div.indent {
+ margin-left: 20px; /* LTR */
+}
+#forum .icon div {
+ background-image: url(../../misc/forum-icons.png);
+ background-repeat: no-repeat;
+ width: 24px;
+ height: 24px;
+}
+#forum .icon .topic-status-new {
+ background-position: -24px 0;
+}
+#forum .icon .topic-status-hot {
+ background-position: -48px 0;
+}
+#forum .icon .topic-status-hot-new {
+ background-position: -72px 0;
+}
+#forum .icon .topic-status-sticky {
+ background-position: -96px 0;
+}
+#forum .icon .topic-status-closed {
+ background-position: -120px 0;
+}
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum.info b/kolab.org/www/drupal-7.26/modules/forum/forum.info
new file mode 100644
index 0000000..610bd7b
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum.info
@@ -0,0 +1,16 @@
+name = Forum
+description = Provides discussion forums.
+dependencies[] = taxonomy
+dependencies[] = comment
+package = Core
+version = VERSION
+core = 7.x
+files[] = forum.test
+configure = admin/structure/forum
+stylesheets[all][] = forum.css
+
+; Information added by Drupal.org packaging script on 2014-01-15
+version = "7.26"
+project = "drupal"
+datestamp = "1389815930"
+
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum.install b/kolab.org/www/drupal-7.26/modules/forum/forum.install
new file mode 100644
index 0000000..57e116b
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum.install
@@ -0,0 +1,467 @@
+<?php
+
+/**
+ * @file
+ * Install, update, and uninstall functions for the Forum module.
+ */
+
+/**
+ * Implements hook_install().
+ */
+function forum_install() {
+ // Set the weight of the forum.module to 1 so it is loaded after the taxonomy.module.
+ db_update('system')
+ ->fields(array('weight' => 1))
+ ->condition('name', 'forum')
+ ->execute();
+ // Forum topics are published by default, but do not have any other default
+ // options set (for example, they are not promoted to the front page).
+ variable_set('node_options_forum', array('status'));
+}
+
+/**
+ * Implements hook_enable().
+ */
+function forum_enable() {
+ // If we enable forum at the same time as taxonomy we need to call
+ // field_associate_fields() as otherwise the field won't be enabled until
+ // hook modules_enabled is called which takes place after hook_enable events.
+ field_associate_fields('taxonomy');
+ // Create the forum vocabulary if it does not exist.
+ $vocabulary = taxonomy_vocabulary_load(variable_get('forum_nav_vocabulary', 0));
+ if (!$vocabulary) {
+ $edit = array(
+ 'name' => t('Forums'),
+ 'machine_name' => 'forums',
+ 'description' => t('Forum navigation vocabulary'),
+ 'hierarchy' => 1,
+ 'module' => 'forum',
+ 'weight' => -10,
+ );
+ $vocabulary = (object) $edit;
+ taxonomy_vocabulary_save($vocabulary);
+ variable_set('forum_nav_vocabulary', $vocabulary->vid);
+ }
+
+ // Create the 'taxonomy_forums' field if it doesn't already exist.
+ if (!field_info_field('taxonomy_forums')) {
+ $field = array(
+ 'field_name' => 'taxonomy_forums',
+ 'type' => 'taxonomy_term_reference',
+ 'settings' => array(
+ 'allowed_values' => array(
+ array(
+ 'vocabulary' => $vocabulary->machine_name,
+ 'parent' => 0,
+ ),
+ ),
+ ),
+ );
+ field_create_field($field);
+
+ // Create a default forum so forum posts can be created.
+ $edit = array(
+ 'name' => t('General discussion'),
+ 'description' => '',
+ 'parent' => array(0),
+ 'vid' => $vocabulary->vid,
+ );
+ $term = (object) $edit;
+ taxonomy_term_save($term);
+
+ // Create the instance on the bundle.
+ $instance = array(
+ 'field_name' => 'taxonomy_forums',
+ 'entity_type' => 'node',
+ 'label' => $vocabulary->name,
+ 'bundle' => 'forum',
+ 'required' => TRUE,
+ 'widget' => array(
+ 'type' => 'options_select',
+ ),
+ 'display' => array(
+ 'default' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ 'teaser' => array(
+ 'type' => 'taxonomy_term_reference_link',
+ 'weight' => 10,
+ ),
+ ),
+ );
+ field_create_instance($instance);
+ }
+
+ // Ensure the forum node type is available.
+ node_types_rebuild();
+ $types = node_type_get_types();
+ node_add_body_field($types['forum']);
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function forum_uninstall() {
+ // Load the dependent Taxonomy module, in case it has been disabled.
+ drupal_load('module', 'taxonomy');
+
+ variable_del('forum_containers');
+ variable_del('forum_hot_topic');
+ variable_del('forum_per_page');
+ variable_del('forum_order');
+ variable_del('forum_block_num_active');
+ variable_del('forum_block_num_new');
+ variable_del('node_options_forum');
+
+ field_delete_field('taxonomy_forums');
+ // Purge field data now to allow taxonomy module to be uninstalled
+ // if this is the only field remaining.
+ field_purge_batch(10);
+}
+
+/**
+ * Implements hook_schema().
+ */
+function forum_schema() {
+ $schema['forum'] = array(
+ 'description' => 'Stores the relationship of nodes to forum terms.',
+ 'fields' => array(
+ 'nid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The {node}.nid of the node.',
+ ),
+ 'vid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'Primary Key: The {node}.vid of the node.',
+ ),
+ 'tid' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The {taxonomy_term_data}.tid of the forum term assigned to the node.',
+ ),
+ ),
+ 'indexes' => array(
+ 'forum_topic' => array('nid', 'tid'),
+ 'tid' => array('tid'),
+ ),
+ 'primary key' => array('vid'),
+ 'foreign keys' => array(
+ 'forum_node' => array(
+ 'table' => 'node',
+ 'columns' => array(
+ 'nid' => 'nid',
+ 'vid' => 'vid',
+ ),
+ ),
+ ),
+ );
+
+ $schema['forum_index'] = array(
+ 'description' => 'Maintains denormalized information about node/term relationships.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The {node}.nid this record tracks.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'title' => array(
+ 'description' => 'The title of this node, always treated as non-markup plain text.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'tid' => array(
+ 'description' => 'The term ID.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sticky' => array(
+ 'description' => 'Boolean indicating whether the node is sticky.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'created' => array(
+ 'description' => 'The Unix timestamp when the node was created.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default'=> 0,
+ ),
+ 'last_comment_timestamp' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The Unix timestamp of the last comment that was posted within this node, from {comment}.timestamp.',
+ ),
+ 'comment_count' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The total number of comments on this node.',
+ ),
+ ),
+ 'indexes' => array(
+ 'forum_topics' => array('nid', 'tid', 'sticky', 'last_comment_timestamp'),
+ 'created' => array('created'),
+ 'last_comment_timestamp' => array('last_comment_timestamp'),
+ ),
+ 'foreign keys' => array(
+ 'tracked_node' => array(
+ 'table' => 'node',
+ 'columns' => array('nid' => 'nid'),
+ ),
+ 'term' => array(
+ 'table' => 'taxonomy_term_data',
+ 'columns' => array(
+ 'tid' => 'tid',
+ ),
+ ),
+ ),
+ );
+
+
+ return $schema;
+}
+
+/**
+ * Implements hook_update_dependencies().
+ */
+function forum_update_dependencies() {
+ $dependencies['forum'][7003] = array(
+ // Forum update 7003 uses field API update functions, so must run after
+ // Field API has been enabled.
+ 'system' => 7020,
+ // Forum update 7003 relies on updated taxonomy module schema. Ensure it
+ // runs after all taxonomy updates.
+ 'taxonomy' => 7010,
+ );
+ return $dependencies;
+}
+
+/**
+ * Add new index to forum table.
+ */
+function forum_update_7000() {
+ db_drop_index('forum', 'nid');
+ db_add_index('forum', 'forum_topic', array('nid', 'tid'));
+}
+
+/**
+ * Create new {forum_index} table.
+ */
+function forum_update_7001() {
+ $forum_index = array(
+ 'description' => 'Maintains denormalized information about node/term relationships.',
+ 'fields' => array(
+ 'nid' => array(
+ 'description' => 'The {node}.nid this record tracks.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'title' => array(
+ 'description' => 'The title of this node, always treated as non-markup plain text.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'tid' => array(
+ 'description' => 'The term ID.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'sticky' => array(
+ 'description' => 'Boolean indicating whether the node is sticky.',
+ 'type' => 'int',
+ 'not null' => FALSE,
+ 'default' => 0,
+ 'size' => 'tiny',
+ ),
+ 'created' => array(
+ 'description' => 'The Unix timestamp when the node was created.',
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default'=> 0,
+ ),
+ 'last_comment_timestamp' => array(
+ 'type' => 'int',
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The Unix timestamp of the last comment that was posted within this node, from {comment}.timestamp.',
+ ),
+ 'comment_count' => array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ 'description' => 'The total number of comments on this node.',
+ ),
+ ),
+ 'indexes' => array(
+ 'forum_topics' => array('tid', 'sticky', 'last_comment_timestamp'),
+ ),
+ 'foreign keys' => array(
+ 'tracked_node' => array(
+ 'table' => 'node',
+ 'columns' => array('nid' => 'nid'),
+ ),
+ 'term' => array(
+ 'table' => 'taxonomy_term_data',
+ 'columns' => array(
+ 'tid' => 'tid',
+ ),
+ ),
+ ),
+ );
+ db_create_table('forum_index', $forum_index);
+
+ $select = db_select('node', 'n');
+ $forum_alias = $select->join('forum', 'f', 'n.vid = f.vid');
+ $ncs_alias = $select->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
+ $select
+ ->fields('n', array('nid', 'title', 'sticky', 'created'))
+ ->fields($forum_alias, array('tid'))
+ ->fields($ncs_alias, array('last_comment_timestamp', 'comment_count'));
+
+ db_insert('forum_index')
+ ->fields(array('nid', 'title', 'sticky', 'created', 'tid', 'last_comment_timestamp', 'comment_count'))
+ ->from($select)
+ ->execute();
+}
+
+/**
+ * @addtogroup updates-7.x-extra
+ * @{
+ */
+
+/**
+ * Add new index to forum_index table.
+ */
+function forum_update_7002() {
+ db_drop_index('forum_index', 'forum_topics');
+ db_add_index('forum_index', 'forum_topics', array('nid', 'tid', 'sticky', 'last_comment_timestamp'));
+}
+
+/**
+ * Rename field to 'taxonomy_forums'.
+ */
+function forum_update_7003() {
+ $messages = array();
+
+ $new_field_name = 'taxonomy_forums';
+
+ // Test to see if the taxonomy_forums field exists.
+ $fields = _update_7000_field_read_fields(array('field_name' => $new_field_name));
+ if ($fields) {
+ // Since the field exists, we're done.
+ return;
+ }
+
+ // Calculate the old field name.
+ $vid = variable_get('forum_nav_vocabulary', 0);
+ $vocabulary_machine_name = db_select('taxonomy_vocabulary', 'tv')
+ ->fields('tv', array('machine_name'))
+ ->condition('vid', $vid)
+ ->execute()
+ ->fetchField();
+ $old_field_name = 'taxonomy_' . $vocabulary_machine_name;
+
+ // Read the old fields.
+ $old_fields = _update_7000_field_read_fields(array('field_name' => $old_field_name));
+ foreach ($old_fields as $old_field) {
+ if ($old_field['storage']['type'] != 'field_sql_storage') {
+ $messages[] = t('Cannot rename field %id (%old_field_name) to %new_field_name because it does not use the field_sql_storage storage type.', array(
+ '%id' => $old_field['id'],
+ '%old_field_name' => $old_field_name,
+ '%new_field_name' => $new_field_name,
+ ));
+ continue;
+ }
+
+ // Update {field_config}.
+ db_update('field_config')
+ ->fields(array('field_name' => $new_field_name))
+ ->condition('id', $old_field['id'])
+ ->execute();
+
+ // Update {field_config_instance}.
+ db_update('field_config_instance')
+ ->fields(array('field_name' => $new_field_name))
+ ->condition('field_id', $old_field['id'])
+ ->execute();
+
+ // The tables that need updating in the form 'old_name' => 'new_name'.
+ $tables = array(
+ 'field_data_' . $old_field_name => 'field_data_' . $new_field_name,
+ 'field_revision_' . $old_field_name => 'field_revision_' . $new_field_name,
+ );
+ foreach ($tables as $old_table => $new_table) {
+ $old_column_name = $old_field_name . '_tid';
+ $new_column_name = $new_field_name . '_tid';
+
+ // Rename the column.
+ db_drop_index($old_table, $old_column_name);
+ db_change_field($old_table, $old_column_name, $new_column_name, array(
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => FALSE,
+ ));
+ db_drop_index($old_table, $new_column_name);
+ db_add_index($old_table, $new_column_name, array($new_column_name));
+
+ // Rename the table.
+ db_rename_table($old_table, $new_table);
+ }
+ }
+
+ cache_clear_all('*', 'cache_field', TRUE);
+
+ return $messages;
+}
+
+/**
+ * Update {forum_index} so that only published nodes are indexed.
+ */
+function forum_update_7011() {
+ $select = db_select('node', 'n')
+ ->fields('n', array('nid'))
+ ->condition('status', 0 );
+
+ db_delete('forum_index')
+ ->condition('nid', $select, 'IN')
+ ->execute();
+}
+
+/**
+ * Add 'created' and 'last_comment_timestamp' indexes.
+ */
+function forum_update_7012() {
+ db_add_index('forum_index', 'created', array('created'));
+ db_add_index('forum_index', 'last_comment_timestamp', array('last_comment_timestamp'));
+}
+
+/**
+ * @} End of "addtogroup updates-7.x-extra".
+ */
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum.module b/kolab.org/www/drupal-7.26/modules/forum/forum.module
new file mode 100644
index 0000000..575de36
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum.module
@@ -0,0 +1,1396 @@
+<?php
+
+/**
+ * @file
+ * Provides discussion forums.
+ */
+
+/**
+ * Implements hook_help().
+ */
+function forum_help($path, $arg) {
+ switch ($path) {
+ case 'admin/help#forum':
+ $output = '';
+ $output .= '<h3>' . t('About') . '</h3>';
+ $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>';
+ $output .= '<ul>';
+ $output .= '<li>' . t('Optional containers (for example, <em>Support</em>), which can hold:') . '</li>';
+ $output .= '<ul><li>' . t('Forums (for example, <em>Installing Drupal</em>), which can hold:') . '</li>';
+ $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>';
+ $output .= '<ul><li>' . t('Threaded comments submitted by users (for example, <em>You have these options...</em>).') . '</li>';
+ $output .= '</ul>';
+ $output .= '</ul>';
+ $output .= '</ul>';
+ $output .= '</ul>';
+ $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>';
+ $output .= '<h3>' . t('Uses') . '</h3>';
+ $output .= '<dl>';
+ $output .= '<dt>' . t('Setting up forum structure') . '</dt>';
+ $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>';
+ $output .= '<dt>' . t('Starting a discussion') . '</dt>';
+ $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>';
+ $output .= '<dt>' . t('Navigation') . '</dt>';
+ $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>';
+ $output .= '<dt>' . t('Moving forum topics') . '</dt>';
+ $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>';
+ $output .= '<dt>' . t('Locking and disabling comments') . '</dt>';
+ $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>';
+ $output .= '</dl>';
+ return $output;
+ case 'admin/structure/forum':
+ $output = '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
+ $output .= theme('more_help_link', array('url' => 'admin/help/forum'));
+ return $output;
+ case 'admin/structure/forum/add/container':
+ return '<p>' . t('Use containers to group related forums.') . '</p>';
+ case 'admin/structure/forum/add/forum':
+ return '<p>' . t('A forum holds related forum topics.') . '</p>';
+ case 'admin/structure/forum/settings':
+ 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>';
+ }
+}
+
+/**
+ * Implements hook_theme().
+ */
+function forum_theme() {
+ return array(
+ 'forums' => array(
+ 'template' => 'forums',
+ 'variables' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
+ ),
+ 'forum_list' => array(
+ 'template' => 'forum-list',
+ 'variables' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
+ ),
+ 'forum_topic_list' => array(
+ 'template' => 'forum-topic-list',
+ 'variables' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
+ ),
+ 'forum_icon' => array(
+ 'template' => 'forum-icon',
+ 'variables' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0, 'first_new' => FALSE),
+ ),
+ 'forum_submitted' => array(
+ 'template' => 'forum-submitted',
+ 'variables' => array('topic' => NULL),
+ ),
+ 'forum_form' => array(
+ 'render element' => 'form',
+ 'file' => 'forum.admin.inc',
+ ),
+ );
+}
+
+/**
+ * Implements hook_menu().
+ */
+function forum_menu() {
+ $items['forum'] = array(
+ 'title' => 'Forums',
+ 'page callback' => 'forum_page',
+ 'access arguments' => array('access content'),
+ 'file' => 'forum.pages.inc',
+ );
+ $items['forum/%forum_forum'] = array(
+ 'title' => 'Forums',
+ 'page callback' => 'forum_page',
+ 'page arguments' => array(1),
+ 'access arguments' => array('access content'),
+ 'file' => 'forum.pages.inc',
+ );
+ $items['admin/structure/forum'] = array(
+ 'title' => 'Forums',
+ 'description' => 'Control forum hierarchy settings.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('forum_overview'),
+ 'access arguments' => array('administer forums'),
+ 'file' => 'forum.admin.inc',
+ );
+ $items['admin/structure/forum/list'] = array(
+ 'title' => 'List',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+ $items['admin/structure/forum/add/container'] = array(
+ 'title' => 'Add container',
+ 'page callback' => 'forum_form_main',
+ 'page arguments' => array('container'),
+ 'access arguments' => array('administer forums'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'parent' => 'admin/structure/forum',
+ 'file' => 'forum.admin.inc',
+ );
+ $items['admin/structure/forum/add/forum'] = array(
+ 'title' => 'Add forum',
+ 'page callback' => 'forum_form_main',
+ 'page arguments' => array('forum'),
+ 'access arguments' => array('administer forums'),
+ 'type' => MENU_LOCAL_ACTION,
+ 'parent' => 'admin/structure/forum',
+ 'file' => 'forum.admin.inc',
+ );
+ $items['admin/structure/forum/settings'] = array(
+ 'title' => 'Settings',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('forum_admin_settings'),
+ 'access arguments' => array('administer forums'),
+ 'weight' => 5,
+ 'type' => MENU_LOCAL_TASK,
+ 'parent' => 'admin/structure/forum',
+ 'file' => 'forum.admin.inc',
+ );
+ $items['admin/structure/forum/edit/container/%taxonomy_term'] = array(
+ 'title' => 'Edit container',
+ 'page callback' => 'forum_form_main',
+ 'page arguments' => array('container', 5),
+ 'access arguments' => array('administer forums'),
+ 'file' => 'forum.admin.inc',
+ );
+ $items['admin/structure/forum/edit/forum/%taxonomy_term'] = array(
+ 'title' => 'Edit forum',
+ 'page callback' => 'forum_form_main',
+ 'page arguments' => array('forum', 5),
+ 'access arguments' => array('administer forums'),
+ 'file' => 'forum.admin.inc',
+ );
+ return $items;
+}
+
+/**
+ * Implements hook_menu_local_tasks_alter().
+ */
+function forum_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+ global $user;
+
+ // Add action link to 'node/add/forum' on 'forum' sub-pages.
+ if ($root_path == 'forum' || $root_path == 'forum/%') {
+ $tid = (isset($router_item['page_arguments'][0]) ? $router_item['page_arguments'][0]->tid : 0);
+ $forum_term = forum_forum_load($tid);
+ if ($forum_term) {
+ $links = array();
+ // Loop through all bundles for forum taxonomy vocabulary field.
+ $field = field_info_field('taxonomy_forums');
+ foreach ($field['bundles']['node'] as $type) {
+ if (node_access('create', $type)) {
+ $links[$type] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => array(
+ 'title' => t('Add new @node_type', array('@node_type' => node_type_get_name($type))),
+ 'href' => 'node/add/' . str_replace('_', '-', $type) . '/' . $forum_term->tid,
+ ),
+ );
+ }
+ }
+ if (empty($links)) {
+ // Authenticated user does not have access to create new topics.
+ if ($user->uid) {
+ $links['disallowed'] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => array(
+ 'title' => t('You are not allowed to post new content in the forum.'),
+ ),
+ );
+ }
+ // Anonymous user does not have access to create new topics.
+ else {
+ $links['login'] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => array(
+ 'title' => t('<a href="@login">Log in</a> to post new content in the forum.', array(
+ '@login' => url('user/login', array('query' => drupal_get_destination())),
+ )),
+ 'localized_options' => array('html' => TRUE),
+ ),
+ );
+ }
+ }
+ $data['actions']['output'] = array_merge($data['actions']['output'], $links);
+ }
+ }
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function forum_entity_info_alter(&$info) {
+ // Take over URI construction for taxonomy terms that are forums.
+ if ($vid = variable_get('forum_nav_vocabulary', 0)) {
+ // Within hook_entity_info(), we can't invoke entity_load() as that would
+ // cause infinite recursion, so we call taxonomy_vocabulary_get_names()
+ // instead of taxonomy_vocabulary_load(). All we need is the machine name
+ // of $vid, so retrieving and iterating all the vocabulary names is somewhat
+ // inefficient, but entity info is cached across page requests, and an
+ // iteration of all vocabularies once per cache clearing isn't a big deal,
+ // and is done as part of taxonomy_entity_info() anyway.
+ foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
+ if ($vid == $vocabulary->vid) {
+ $info['taxonomy_term']['bundles'][$machine_name]['uri callback'] = 'forum_uri';
+ }
+ }
+ }
+}
+
+/**
+ * Implements callback_entity_info_uri().
+ *
+ * Entity URI callback used in forum_entity_info_alter().
+ */
+function forum_uri($forum) {
+ return array(
+ 'path' => 'forum/' . $forum->tid,
+ );
+}
+
+/**
+ * Checks whether a node can be used in a forum, based on its content type.
+ *
+ * @param $node
+ * A node object.
+ *
+ * @return
+ * Boolean indicating if the node can be assigned to a forum.
+ */
+function _forum_node_check_node_type($node) {
+ // Fetch information about the forum field.
+ $field = field_info_instance('node', 'taxonomy_forums', $node->type);
+
+ return is_array($field);
+}
+
+/**
+ * Implements hook_node_view().
+ */
+function forum_node_view($node, $view_mode) {
+ $vid = variable_get('forum_nav_vocabulary', 0);
+ $vocabulary = taxonomy_vocabulary_load($vid);
+ if (_forum_node_check_node_type($node)) {
+ if ($view_mode == 'full' && node_is_page($node)) {
+ // Breadcrumb navigation
+ $breadcrumb[] = l(t('Home'), NULL);
+ $breadcrumb[] = l($vocabulary->name, 'forum');
+ if ($parents = taxonomy_get_parents_all($node->forum_tid)) {
+ $parents = array_reverse($parents);
+ foreach ($parents as $parent) {
+ $breadcrumb[] = l($parent->name, 'forum/' . $parent->tid);
+ }
+ }
+ drupal_set_breadcrumb($breadcrumb);
+
+ }
+ }
+}
+
+/**
+ * Implements hook_node_validate().
+ *
+ * Checks in particular that the node is assigned only a "leaf" term in the
+ * forum taxonomy.
+ */
+function forum_node_validate($node, $form) {
+ if (_forum_node_check_node_type($node)) {
+ $langcode = $form['taxonomy_forums']['#language'];
+ // vocabulary is selected, not a "container" term.
+ if (!empty($node->taxonomy_forums[$langcode])) {
+ // Extract the node's proper topic ID.
+ $containers = variable_get('forum_containers', array());
+ foreach ($node->taxonomy_forums[$langcode] as $delta => $item) {
+ // If no term was selected (e.g. when no terms exist yet), remove the
+ // item.
+ if (empty($item['tid'])) {
+ unset($node->taxonomy_forums[$langcode][$delta]);
+ continue;
+ }
+ $term = taxonomy_term_load($item['tid']);
+ if (!$term) {
+ form_set_error('taxonomy_forums', t('Select a forum.'));
+ continue;
+ }
+ $used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid',0 , 1, array(
+ ':tid' => $term->tid,
+ ':vid' => $term->vid,
+ ))->fetchField();
+ if ($used && in_array($term->tid, $containers)) {
+ 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)));
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_node_presave().
+ *
+ * Assigns the forum taxonomy when adding a topic from within a forum.
+ */
+function forum_node_presave($node) {
+ if (_forum_node_check_node_type($node)) {
+ // Make sure all fields are set properly:
+ $node->icon = !empty($node->icon) ? $node->icon : '';
+ reset($node->taxonomy_forums);
+ $langcode = key($node->taxonomy_forums);
+ if (!empty($node->taxonomy_forums[$langcode])) {
+ $node->forum_tid = $node->taxonomy_forums[$langcode][0]['tid'];
+ if (isset($node->nid)) {
+ $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();
+ if ($old_tid && isset($node->forum_tid) && ($node->forum_tid != $old_tid) && !empty($node->shadow)) {
+ // A shadow copy needs to be created. Retain new term and add old term.
+ $node->taxonomy_forums[$langcode][] = array('tid' => $old_tid);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * Implements hook_node_update().
+ */
+function forum_node_update($node) {
+ if (_forum_node_check_node_type($node)) {
+ if (empty($node->revision) && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
+ if (!empty($node->forum_tid)) {
+ db_update('forum')
+ ->fields(array('tid' => $node->forum_tid))
+ ->condition('vid', $node->vid)
+ ->execute();
+ }
+ // The node is removed from the forum.
+ else {
+ db_delete('forum')
+ ->condition('nid', $node->nid)
+ ->execute();
+ }
+ }
+ else {
+ if (!empty($node->forum_tid)) {
+ db_insert('forum')
+ ->fields(array(
+ 'tid' => $node->forum_tid,
+ 'vid' => $node->vid,
+ 'nid' => $node->nid,
+ ))
+ ->execute();
+ }
+ }
+ // If the node has a shadow forum topic, update the record for this
+ // revision.
+ if (!empty($node->shadow)) {
+ db_delete('forum')
+ ->condition('nid', $node->nid)
+ ->condition('vid', $node->vid)
+ ->execute();
+ db_insert('forum')
+ ->fields(array(
+ 'nid' => $node->nid,
+ 'vid' => $node->vid,
+ 'tid' => $node->forum_tid,
+ ))
+ ->execute();
+ }
+ }
+}
+
+/**
+ * Implements hook_node_insert().
+ */
+function forum_node_insert($node) {
+ if (_forum_node_check_node_type($node)) {
+ if (!empty($node->forum_tid)) {
+ $nid = db_insert('forum')
+ ->fields(array(
+ 'tid' => $node->forum_tid,
+ 'vid' => $node->vid,
+ 'nid' => $node->nid,
+ ))
+ ->execute();
+ }
+ }
+}
+
+/**
+ * Implements hook_node_delete().
+ */
+function forum_node_delete($node) {
+ if (_forum_node_check_node_type($node)) {
+ db_delete('forum')
+ ->condition('nid', $node->nid)
+ ->execute();
+ db_delete('forum_index')
+ ->condition('nid', $node->nid)
+ ->execute();
+ }
+}
+
+/**
+ * Implements hook_node_load().
+ */
+function forum_node_load($nodes) {
+ $node_vids = array();
+ foreach ($nodes as $node) {
+ if (_forum_node_check_node_type($node)) {
+ $node_vids[] = $node->vid;
+ }
+ }
+ if (!empty($node_vids)) {
+ $query = db_select('forum', 'f');
+ $query
+ ->fields('f', array('nid', 'tid'))
+ ->condition('f.vid', $node_vids);
+ $result = $query->execute();
+ foreach ($result as $record) {
+ $nodes[$record->nid]->forum_tid = $record->tid;
+ }
+ }
+}
+
+/**
+ * Implements hook_node_info().
+ */
+function forum_node_info() {
+ return array(
+ 'forum' => array(
+ 'name' => t('Forum topic'),
+ 'base' => 'forum',
+ 'description' => t('A <em>forum topic</em> starts a new discussion thread within a forum.'),
+ 'title_label' => t('Subject'),
+ )
+ );
+}
+
+/**
+ * Implements hook_permission().
+ */
+function forum_permission() {
+ $perms = array(
+ 'administer forums' => array(
+ 'title' => t('Administer forums'),
+ ),
+ );
+ return $perms;
+}
+
+/**
+ * Implements hook_taxonomy_term_delete().
+ */
+function forum_taxonomy_term_delete($term) {
+ // For containers, remove the tid from the forum_containers variable.
+ $containers = variable_get('forum_containers', array());
+ $key = array_search($term->tid, $containers);
+ if ($key !== FALSE) {
+ unset($containers[$key]);
+ }
+ variable_set('forum_containers', $containers);
+}
+
+/**
+ * Implements hook_comment_publish().
+ *
+ * This actually handles the insertion and update of published nodes since
+ * comment_save() calls hook_comment_publish() for all published comments.
+ */
+function forum_comment_publish($comment) {
+ _forum_update_forum_index($comment->nid);
+}
+
+/**
+ * Implements hook_comment_update().
+ *
+ * The Comment module doesn't call hook_comment_unpublish() when saving
+ * individual comments, so we need to check for those here.
+ */
+function forum_comment_update($comment) {
+ // comment_save() calls hook_comment_publish() for all published comments,
+ // so we need to handle all other values here.
+ if (!$comment->status) {
+ _forum_update_forum_index($comment->nid);
+ }
+}
+
+/**
+ * Implements hook_comment_unpublish().
+ */
+function forum_comment_unpublish($comment) {
+ _forum_update_forum_index($comment->nid);
+}
+
+/**
+ * Implements hook_comment_delete().
+ */
+function forum_comment_delete($comment) {
+ _forum_update_forum_index($comment->nid);
+}
+
+/**
+ * Implements hook_field_storage_pre_insert().
+ */
+function forum_field_storage_pre_insert($entity_type, $entity, &$skip_fields) {
+ if ($entity_type == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
+ $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
+ foreach ($entity->taxonomy_forums as $language) {
+ foreach ($language as $item) {
+ $query->values(array(
+ 'nid' => $entity->nid,
+ 'title' => $entity->title,
+ 'tid' => $item['tid'],
+ 'sticky' => $entity->sticky,
+ 'created' => $entity->created,
+ 'comment_count' => 0,
+ 'last_comment_timestamp' => $entity->created,
+ ));
+ }
+ }
+ $query->execute();
+ }
+}
+
+/**
+ * Implements hook_field_storage_pre_update().
+ */
+function forum_field_storage_pre_update($entity_type, $entity, &$skip_fields) {
+ $first_call = &drupal_static(__FUNCTION__, array());
+
+ if ($entity_type == 'node' && _forum_node_check_node_type($entity)) {
+
+ // If the node is published, update the forum index.
+ if ($entity->status) {
+
+ // We don't maintain data for old revisions, so clear all previous values
+ // from the table. Since this hook runs once per field, per object, make
+ // sure we only wipe values once.
+ if (!isset($first_call[$entity->nid])) {
+ $first_call[$entity->nid] = FALSE;
+ db_delete('forum_index')->condition('nid', $entity->nid)->execute();
+ }
+ $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
+ foreach ($entity->taxonomy_forums as $language) {
+ foreach ($language as $item) {
+ $query->values(array(
+ 'nid' => $entity->nid,
+ 'title' => $entity->title,
+ 'tid' => $item['tid'],
+ 'sticky' => $entity->sticky,
+ 'created' => $entity->created,
+ 'comment_count' => 0,
+ 'last_comment_timestamp' => $entity->created,
+ ));
+ }
+ }
+ $query->execute();
+ // The logic for determining last_comment_count is fairly complex, so
+ // call _forum_update_forum_index() too.
+ _forum_update_forum_index($entity->nid);
+ }
+
+ // When a forum node is unpublished, remove it from the forum_index table.
+ else {
+ db_delete('forum_index')->condition('nid', $entity->nid)->execute();
+ }
+
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for taxonomy_form_vocabulary().
+ */
+function forum_form_taxonomy_form_vocabulary_alter(&$form, &$form_state, $form_id) {
+ $vid = variable_get('forum_nav_vocabulary', 0);
+ if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {
+ $form['help_forum_vocab'] = array(
+ '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
+ '#weight' => -1,
+ );
+ // Forum's vocabulary always has single hierarchy. Forums and containers
+ // have only one parent or no parent for root items. By default this value
+ // is 0.
+ $form['hierarchy']['#value'] = 1;
+ // Do not allow to delete forum's vocabulary.
+ $form['actions']['delete']['#access'] = FALSE;
+ }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for taxonomy_form_term().
+ */
+function forum_form_taxonomy_form_term_alter(&$form, &$form_state, $form_id) {
+ $vid = variable_get('forum_nav_vocabulary', 0);
+ if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {
+ // Hide multiple parents select from forum terms.
+ $form['relations']['parent']['#access'] = FALSE;
+ }
+}
+
+/**
+ * Implements hook_form_BASE_FORM_ID_alter() for node_form().
+ */
+function forum_form_node_form_alter(&$form, &$form_state, $form_id) {
+ if (isset($form['taxonomy_forums'])) {
+ $langcode = $form['taxonomy_forums']['#language'];
+ // Make the vocabulary required for 'real' forum-nodes.
+ $form['taxonomy_forums'][$langcode]['#required'] = TRUE;
+ $form['taxonomy_forums'][$langcode]['#multiple'] = FALSE;
+ if (empty($form['taxonomy_forums'][$langcode]['#default_value'])) {
+ // If there is no default forum already selected, try to get the forum
+ // ID from the URL (e.g., if we are on a page like node/add/forum/2, we
+ // expect "2" to be the ID of the forum that was requested).
+ $requested_forum_id = arg(3);
+ $form['taxonomy_forums'][$langcode]['#default_value'] = is_numeric($requested_forum_id) ? $requested_forum_id : '';
+ }
+ }
+}
+
+/**
+ * Implements hook_block_info().
+ */
+function forum_block_info() {
+ $blocks['active'] = array(
+ 'info' => t('Active forum topics'),
+ 'cache' => DRUPAL_CACHE_CUSTOM,
+ 'properties' => array('administrative' => TRUE),
+ );
+ $blocks['new'] = array(
+ 'info' => t('New forum topics'),
+ 'cache' => DRUPAL_CACHE_CUSTOM,
+ 'properties' => array('administrative' => TRUE),
+ );
+ return $blocks;
+}
+
+/**
+ * Implements hook_block_configure().
+ */
+function forum_block_configure($delta = '') {
+ $form['forum_block_num_' . $delta] = array(
+ '#type' => 'select',
+ '#title' => t('Number of topics'),
+ '#default_value' => variable_get('forum_block_num_' . $delta, '5'),
+ '#options' => drupal_map_assoc(range(2, 20))
+ );
+ return $form;
+}
+
+/**
+ * Implements hook_block_save().
+ */
+function forum_block_save($delta = '', $edit = array()) {
+ variable_set('forum_block_num_' . $delta, $edit['forum_block_num_' . $delta]);
+}
+
+/**
+ * Implements hook_block_view().
+ *
+ * Generates a block containing the currently active forum topics and the most
+ * recently added forum topics.
+ */
+function forum_block_view($delta = '') {
+ $query = db_select('forum_index', 'f')
+ ->fields('f')
+ ->addTag('node_access');
+ switch ($delta) {
+ case 'active':
+ $title = t('Active forum topics');
+ $query
+ ->orderBy('f.last_comment_timestamp', 'DESC')
+ ->range(0, variable_get('forum_block_num_active', '5'));
+ break;
+
+ case 'new':
+ $title = t('New forum topics');
+ $query
+ ->orderBy('f.created', 'DESC')
+ ->range(0, variable_get('forum_block_num_new', '5'));
+ break;
+ }
+
+ $block['subject'] = $title;
+ // Cache based on the altered query. Enables us to cache with node access enabled.
+ $block['content'] = drupal_render_cache_by_query($query, 'forum_block_view');
+ $block['content']['#access'] = user_access('access content');
+ return $block;
+}
+
+/**
+ * Render API callback: Lists nodes based on the element's #query property.
+ *
+ * This function can be used as a #pre_render callback.
+ *
+ * @see forum_block_view()
+ */
+function forum_block_view_pre_render($elements) {
+ $result = $elements['#query']->execute();
+ if ($node_title_list = node_title_list($result)) {
+ $elements['forum_list'] = $node_title_list;
+ $elements['forum_more'] = array('#theme' => 'more_link', '#url' => 'forum', '#title' => t('Read the latest forum topics.'));
+ }
+ return $elements;
+}
+
+/**
+ * Implements hook_form().
+ */
+function forum_form($node, $form_state) {
+ $type = node_type_get_type($node);
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => check_plain($type->title_label),
+ '#default_value' => !empty($node->title) ? $node->title : '',
+ '#required' => TRUE, '#weight' => -5
+ );
+
+ if (!empty($node->nid)) {
+ $forum_terms = $node->taxonomy_forums;
+ // If editing, give option to leave shadows.
+ $shadow = (count($forum_terms) > 1);
+ $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.'));
+ $form['forum_tid'] = array('#type' => 'value', '#value' => $node->forum_tid);
+ }
+
+ return $form;
+}
+
+/**
+ * Returns a tree of all forums for a given taxonomy term ID.
+ *
+ * @param $tid
+ * (optional) Taxonomy term ID of the forum. If not given all forums will be
+ * returned.
+ *
+ * @return
+ * A tree of taxonomy objects, with the following additional properties:
+ * - num_topics: Number of topics in the forum.
+ * - num_posts: Total number of posts in all topics.
+ * - last_post: Most recent post for the forum.
+ * - forums: An array of child forums.
+ */
+function forum_forum_load($tid = NULL) {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ // Return a cached forum tree if available.
+ if (!isset($tid)) {
+ $tid = 0;
+ }
+ if (isset($cache[$tid])) {
+ return $cache[$tid];
+ }
+
+ $vid = variable_get('forum_nav_vocabulary', 0);
+
+ // Load and validate the parent term.
+ if ($tid) {
+ $forum_term = taxonomy_term_load($tid);
+ if (!$forum_term || ($forum_term->vid != $vid)) {
+ return $cache[$tid] = FALSE;
+ }
+ }
+ // If $tid is 0, create an empty object to hold the child terms.
+ elseif ($tid === 0) {
+ $forum_term = (object) array(
+ 'tid' => 0,
+ );
+ }
+
+ // Determine if the requested term is a container.
+ if (!$forum_term->tid || in_array($forum_term->tid, variable_get('forum_containers', array()))) {
+ $forum_term->container = 1;
+ }
+
+ // Load parent terms.
+ $forum_term->parents = taxonomy_get_parents_all($forum_term->tid);
+
+ // Load the tree below.
+ $forums = array();
+ $_forums = taxonomy_get_tree($vid, $tid);
+
+ if (count($_forums)) {
+ $query = db_select('node', 'n');
+ $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
+ $query->join('forum', 'f', 'n.vid = f.vid');
+ $query->addExpression('COUNT(n.nid)', 'topic_count');
+ $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
+ $counts = $query
+ ->fields('f', array('tid'))
+ ->condition('n.status', 1)
+ ->groupBy('tid')
+ ->addTag('node_access')
+ ->execute()
+ ->fetchAllAssoc('tid');
+ }
+
+ foreach ($_forums as $forum) {
+ // Determine if the child term is a container.
+ if (in_array($forum->tid, variable_get('forum_containers', array()))) {
+ $forum->container = 1;
+ }
+
+ // Merge in the topic and post counters.
+ if (!empty($counts[$forum->tid])) {
+ $forum->num_topics = $counts[$forum->tid]->topic_count;
+ $forum->num_posts = $counts[$forum->tid]->topic_count + $counts[$forum->tid]->comment_count;
+ }
+ else {
+ $forum->num_topics = 0;
+ $forum->num_posts = 0;
+ }
+
+ // Query "Last Post" information for this forum.
+ $query = db_select('node', 'n');
+ $query->join('users', 'u1', 'n.uid = u1.uid');
+ $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid));
+ $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
+ $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid');
+ $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u2.name END', 'last_comment_name');
+
+ $topic = $query
+ ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid'))
+ ->condition('n.status', 1)
+ ->orderBy('last_comment_timestamp', 'DESC')
+ ->range(0, 1)
+ ->addTag('node_access')
+ ->execute()
+ ->fetchObject();
+
+ // Merge in the "Last Post" information.
+ $last_post = new stdClass();
+ if (!empty($topic->last_comment_timestamp)) {
+ $last_post->created = $topic->last_comment_timestamp;
+ $last_post->name = $topic->last_comment_name;
+ $last_post->uid = $topic->last_comment_uid;
+ }
+ $forum->last_post = $last_post;
+
+ $forums[$forum->tid] = $forum;
+ }
+
+ // Cache the result, and return the tree.
+ $forum_term->forums = $forums;
+ $cache[$tid] = $forum_term;
+ return $forum_term;
+}
+
+/**
+ * Calculates the number of new posts in a forum that the user has not yet read.
+ *
+ * Nodes are new if they are newer than NODE_NEW_LIMIT.
+ *
+ * @param $term
+ * The term ID of the forum.
+ * @param $uid
+ * The user ID.
+ *
+ * @return
+ * The number of new posts in the forum that have not been read by the user.
+ */
+function _forum_topics_unread($term, $uid) {
+ $query = db_select('node', 'n');
+ $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $term));
+ $query->leftJoin('history', 'h', 'n.nid = h.nid AND h.uid = :uid', array(':uid' => $uid));
+ $query->addExpression('COUNT(n.nid)', 'count');
+ return $query
+ ->condition('status', 1)
+ ->condition('n.created', NODE_NEW_LIMIT, '>')
+ ->isNull('h.nid')
+ ->addTag('node_access')
+ ->execute()
+ ->fetchField();
+}
+
+/**
+ * Gets all the topics in a forum.
+ *
+ * @param $tid
+ * The term ID of the forum.
+ * @param $sortby
+ * One of the following integers indicating the sort criteria:
+ * - 1: Date - newest first.
+ * - 2: Date - oldest first.
+ * - 3: Posts with the most comments first.
+ * - 4: Posts with the least comments first.
+ * @param $forum_per_page
+ * The maximum number of topics to display per page.
+ *
+ * @return
+ * A list of all the topics in a forum.
+ */
+function forum_get_topics($tid, $sortby, $forum_per_page) {
+ global $user, $forum_topic_list_header;
+
+ $forum_topic_list_header = array(
+ NULL,
+ array('data' => t('Topic'), 'field' => 'f.title'),
+ array('data' => t('Replies'), 'field' => 'f.comment_count'),
+ array('data' => t('Last reply'), 'field' => 'f.last_comment_timestamp'),
+ );
+
+ $order = _forum_get_topic_order($sortby);
+ for ($i = 0; $i < count($forum_topic_list_header); $i++) {
+ if ($forum_topic_list_header[$i]['field'] == $order['field']) {
+ $forum_topic_list_header[$i]['sort'] = $order['sort'];
+ }
+ }
+
+ $query = db_select('forum_index', 'f')->extend('PagerDefault')->extend('TableSort');
+ $query->fields('f');
+ $query
+ ->condition('f.tid', $tid)
+ ->addTag('node_access')
+ ->orderBy('f.sticky', 'DESC')
+ ->orderByHeader($forum_topic_list_header)
+ ->limit($forum_per_page);
+
+ $count_query = db_select('forum_index', 'f');
+ $count_query->condition('f.tid', $tid);
+ $count_query->addExpression('COUNT(*)');
+ $count_query->addTag('node_access');
+
+ $query->setCountQuery($count_query);
+ $result = $query->execute();
+ $nids = array();
+ foreach ($result as $record) {
+ $nids[] = $record->nid;
+ }
+ if ($nids) {
+ $query = db_select('node', 'n')->extend('TableSort');
+ $query->fields('n', array('title', 'nid', 'type', 'sticky', 'created', 'uid'));
+ $query->addField('n', 'comment', 'comment_mode');
+
+ $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
+ $query->fields('ncs', array('cid', 'last_comment_uid', 'last_comment_timestamp', 'comment_count'));
+
+ $query->join('forum_index', 'f', 'f.nid = ncs.nid');
+ $query->addField('f', 'tid', 'forum_tid');
+
+ $query->join('users', 'u', 'n.uid = u.uid');
+ $query->addField('u', 'name');
+
+ $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid');
+
+ $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u2.name END', 'last_comment_name');
+
+ $query
+ ->orderBy('f.sticky', 'DESC')
+ ->orderByHeader($forum_topic_list_header)
+ ->condition('n.nid', $nids);
+
+ $result = $query->execute();
+ }
+ else {
+ $result = array();
+ }
+
+ $topics = array();
+ $first_new_found = FALSE;
+ foreach ($result as $topic) {
+ if ($user->uid) {
+ // A forum is new if the topic is new, or if there are new comments since
+ // the user's last visit.
+ if ($topic->forum_tid != $tid) {
+ $topic->new = 0;
+ }
+ else {
+ $history = _forum_user_last_visit($topic->nid);
+ $topic->new_replies = comment_num_new($topic->nid, $history);
+ $topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history);
+ }
+ }
+ else {
+ // Do not track "new replies" status for topics if the user is anonymous.
+ $topic->new_replies = 0;
+ $topic->new = 0;
+ }
+
+ // Make sure only one topic is indicated as the first new topic.
+ $topic->first_new = FALSE;
+ if ($topic->new != 0 && !$first_new_found) {
+ $topic->first_new = TRUE;
+ $first_new_found = TRUE;
+ }
+
+ if ($topic->comment_count > 0) {
+ $last_reply = new stdClass();
+ $last_reply->created = $topic->last_comment_timestamp;
+ $last_reply->name = $topic->last_comment_name;
+ $last_reply->uid = $topic->last_comment_uid;
+ $topic->last_reply = $last_reply;
+ }
+ $topics[] = $topic;
+ }
+
+ return $topics;
+}
+
+/**
+ * Preprocesses variables for forums.tpl.php.
+ *
+ * @param $variables
+ * An array containing the following elements:
+ * - forums: An array of all forum objects to display for the given taxonomy
+ * term ID. If tid = 0 then all the top-level forums are displayed.
+ * - topics: An array of all the topics in the current forum.
+ * - parents: An array of taxonomy term objects that are ancestors of the
+ * current term ID.
+ * - tid: Taxonomy term ID of the current forum.
+ * - sortby: One of the following integers indicating the sort criteria:
+ * - 1: Date - newest first.
+ * - 2: Date - oldest first.
+ * - 3: Posts with the most comments first.
+ * - 4: Posts with the least comments first.
+ * - forum_per_page: The maximum number of topics to display per page.
+ *
+ * @see forums.tpl.php
+ */
+function template_preprocess_forums(&$variables) {
+ global $user;
+
+ $vid = variable_get('forum_nav_vocabulary', 0);
+ $vocabulary = taxonomy_vocabulary_load($vid);
+ $title = !empty($vocabulary->name) ? $vocabulary->name : '';
+
+ // Breadcrumb navigation:
+ $breadcrumb[] = l(t('Home'), NULL);
+ if ($variables['tid']) {
+ $breadcrumb[] = l($vocabulary->name, 'forum');
+ }
+ if ($variables['parents']) {
+ $variables['parents'] = array_reverse($variables['parents']);
+ foreach ($variables['parents'] as $p) {
+ if ($p->tid == $variables['tid']) {
+ $title = $p->name;
+ }
+ else {
+ $breadcrumb[] = l($p->name, 'forum/' . $p->tid);
+ }
+ }
+ }
+ drupal_set_breadcrumb($breadcrumb);
+ drupal_set_title($title);
+
+ if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
+ if (!empty($variables['forums'])) {
+ $variables['forums'] = theme('forum_list', $variables);
+ }
+ else {
+ $variables['forums'] = '';
+ }
+
+ if ($variables['tid'] && !in_array($variables['tid'], variable_get('forum_containers', array()))) {
+ $variables['topics'] = theme('forum_topic_list', $variables);
+ drupal_add_feed('taxonomy/term/' . $variables['tid'] . '/feed', 'RSS - ' . $title);
+ }
+ else {
+ $variables['topics'] = '';
+ }
+
+ // Provide separate template suggestions based on what's being output. Topic id is also accounted for.
+ // Check both variables to be safe then the inverse. Forums with topic ID's take precedence.
+ if ($variables['forums'] && !$variables['topics']) {
+ $variables['theme_hook_suggestions'][] = 'forums__containers';
+ $variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
+ $variables['theme_hook_suggestions'][] = 'forums__containers__' . $variables['tid'];
+ }
+ elseif (!$variables['forums'] && $variables['topics']) {
+ $variables['theme_hook_suggestions'][] = 'forums__topics';
+ $variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
+ $variables['theme_hook_suggestions'][] = 'forums__topics__' . $variables['tid'];
+ }
+ else {
+ $variables['theme_hook_suggestions'][] = 'forums__' . $variables['tid'];
+ }
+
+ }
+ else {
+ drupal_set_title(t('No forums defined'));
+ $variables['forums'] = '';
+ $variables['topics'] = '';
+ }
+}
+
+/**
+ * Preprocesses variables for forum-list.tpl.php.
+ *
+ * @param $variables
+ * An array containing the following elements:
+ * - forums: An array of all forum objects to display for the given taxonomy
+ * term ID. If tid = 0 then all the top-level forums are displayed.
+ * - parents: An array of taxonomy term objects that are ancestors of the
+ * current term ID.
+ * - tid: Taxonomy term ID of the current forum.
+ *
+ * @see forum-list.tpl.php
+ * @see theme_forum_list()
+ */
+function template_preprocess_forum_list(&$variables) {
+ global $user;
+ $row = 0;
+ // Sanitize each forum so that the template can safely print the data.
+ foreach ($variables['forums'] as $id => $forum) {
+ $variables['forums'][$id]->description = !empty($forum->description) ? filter_xss_admin($forum->description) : '';
+ $variables['forums'][$id]->link = url("forum/$forum->tid");
+ $variables['forums'][$id]->name = check_plain($forum->name);
+ $variables['forums'][$id]->is_container = !empty($forum->container);
+ $variables['forums'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
+ $row++;
+
+ $variables['forums'][$id]->new_text = '';
+ $variables['forums'][$id]->new_url = '';
+ $variables['forums'][$id]->new_topics = 0;
+ $variables['forums'][$id]->old_topics = $forum->num_topics;
+ $variables['forums'][$id]->icon_class = 'default';
+ $variables['forums'][$id]->icon_title = t('No new posts');
+ if ($user->uid) {
+ $variables['forums'][$id]->new_topics = _forum_topics_unread($forum->tid, $user->uid);
+ if ($variables['forums'][$id]->new_topics) {
+ $variables['forums'][$id]->new_text = format_plural($variables['forums'][$id]->new_topics, '1 new', '@count new');
+ $variables['forums'][$id]->new_url = url("forum/$forum->tid", array('fragment' => 'new'));
+ $variables['forums'][$id]->icon_class = 'new';
+ $variables['forums'][$id]->icon_title = t('New posts');
+ }
+ $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
+ }
+ $variables['forums'][$id]->last_reply = theme('forum_submitted', array('topic' => $forum->last_post));
+ }
+ // Give meaning to $tid for themers. $tid actually stands for term id.
+ $variables['forum_id'] = $variables['tid'];
+ unset($variables['tid']);
+}
+
+/**
+ * Preprocesses variables for forum-topic-list.tpl.php.
+ *
+ * @param $variables
+ * An array containing the following elements:
+ * - tid: Taxonomy term ID of the current forum.
+ * - topics: An array of all the topics in the current forum.
+ * - forum_per_page: The maximum number of topics to display per page.
+ *
+ * @see forum-topic-list.tpl.php
+ * @see theme_forum_topic_list()
+ */
+function template_preprocess_forum_topic_list(&$variables) {
+ global $forum_topic_list_header;
+
+ // Create the tablesorting header.
+ $ts = tablesort_init($forum_topic_list_header);
+ $header = '';
+ foreach ($forum_topic_list_header as $cell) {
+ $cell = tablesort_header($cell, $forum_topic_list_header, $ts);
+ $header .= _theme_table_cell($cell, TRUE);
+ }
+ $variables['header'] = $header;
+
+ if (!empty($variables['topics'])) {
+ $row = 0;
+ foreach ($variables['topics'] as $id => $topic) {
+ $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));
+ $variables['topics'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
+ $row++;
+
+ // We keep the actual tid in forum table, if it's different from the
+ // current tid then it means the topic appears in two forums, one of
+ // them is a shadow copy.
+ if ($variables['tid'] != $topic->forum_tid) {
+ $variables['topics'][$id]->moved = TRUE;
+ $variables['topics'][$id]->title = check_plain($topic->title);
+ $variables['topics'][$id]->message = l(t('This topic has been moved'), "forum/$topic->forum_tid");
+ }
+ else {
+ $variables['topics'][$id]->moved = FALSE;
+ $variables['topics'][$id]->title = l($topic->title, "node/$topic->nid");
+ $variables['topics'][$id]->message = '';
+ }
+ $variables['topics'][$id]->created = theme('forum_submitted', array('topic' => $topic));
+ $variables['topics'][$id]->last_reply = theme('forum_submitted', array('topic' => isset($topic->last_reply) ? $topic->last_reply : NULL));
+
+ $variables['topics'][$id]->new_text = '';
+ $variables['topics'][$id]->new_url = '';
+ if ($topic->new_replies) {
+ $variables['topics'][$id]->new_text = format_plural($topic->new_replies, '1 new', '@count new');
+ $variables['topics'][$id]->new_url = url("node/$topic->nid", array('query' => comment_new_page_count($topic->comment_count, $topic->new_replies, $topic), 'fragment' => 'new'));
+ }
+
+ }
+ }
+ else {
+ // Make this safe for the template.
+ $variables['topics'] = array();
+ }
+ // Give meaning to $tid for themers. $tid actually stands for term id.
+ $variables['topic_id'] = $variables['tid'];
+ unset($variables['tid']);
+
+ $variables['pager'] = theme('pager');
+}
+
+/**
+ * Preprocesses variables for forum-icon.tpl.php.
+ *
+ * @param $variables
+ * An array containing the following elements:
+ * - new_posts: Indicates whether or not the topic contains new posts.
+ * - num_posts: The total number of posts in all topics.
+ * - comment_mode: An integer indicating whether comments are open, closed,
+ * or hidden.
+ * - sticky: Indicates whether the topic is sticky.
+ * - first_new: Indicates whether this is the first topic with new posts.
+ *
+ * @see forum-icon.tpl.php
+ * @see theme_forum_icon()
+ */
+function template_preprocess_forum_icon(&$variables) {
+ $variables['hot_threshold'] = variable_get('forum_hot_topic', 15);
+ if ($variables['num_posts'] > $variables['hot_threshold']) {
+ $variables['icon_class'] = $variables['new_posts'] ? 'hot-new' : 'hot';
+ $variables['icon_title'] = $variables['new_posts'] ? t('Hot topic, new comments') : t('Hot topic');
+ }
+ else {
+ $variables['icon_class'] = $variables['new_posts'] ? 'new' : 'default';
+ $variables['icon_title'] = $variables['new_posts'] ? t('New comments') : t('Normal topic');
+ }
+
+ if ($variables['comment_mode'] == COMMENT_NODE_CLOSED || $variables['comment_mode'] == COMMENT_NODE_HIDDEN) {
+ $variables['icon_class'] = 'closed';
+ $variables['icon_title'] = t('Closed topic');
+ }
+
+ if ($variables['sticky'] == 1) {
+ $variables['icon_class'] = 'sticky';
+ $variables['icon_title'] = t('Sticky topic');
+ }
+}
+
+/**
+ * Preprocesses variables for forum-submitted.tpl.php.
+ *
+ * The submission information will be displayed in the forum list and topic
+ * list.
+ *
+ * @param $variables
+ * An array containing the following elements:
+ * - topic: The topic object.
+ *
+ * @see forum-submitted.tpl.php
+ * @see theme_forum_submitted()
+ */
+function template_preprocess_forum_submitted(&$variables) {
+ $variables['author'] = isset($variables['topic']->uid) ? theme('username', array('account' => $variables['topic'])) : '';
+ $variables['time'] = isset($variables['topic']->created) ? format_interval(REQUEST_TIME - $variables['topic']->created) : '';
+}
+
+/**
+ * Gets the last time the user viewed a node.
+ *
+ * @param $nid
+ * The node ID.
+ *
+ * @return
+ * The timestamp when the user last viewed this node, if the user has
+ * previously viewed the node; otherwise NODE_NEW_LIMIT.
+ */
+function _forum_user_last_visit($nid) {
+ global $user;
+ $history = &drupal_static(__FUNCTION__, array());
+
+ if (empty($history)) {
+ $result = db_query('SELECT nid, timestamp FROM {history} WHERE uid = :uid', array(':uid' => $user->uid));
+ foreach ($result as $t) {
+ $history[$t->nid] = $t->timestamp > NODE_NEW_LIMIT ? $t->timestamp : NODE_NEW_LIMIT;
+ }
+ }
+ return isset($history[$nid]) ? $history[$nid] : NODE_NEW_LIMIT;
+}
+
+/**
+ * Gets topic sorting information based on an integer code.
+ *
+ * @param $sortby
+ * One of the following integers indicating the sort criteria:
+ * - 1: Date - newest first.
+ * - 2: Date - oldest first.
+ * - 3: Posts with the most comments first.
+ * - 4: Posts with the least comments first.
+ *
+ * @return
+ * An array with the following values:
+ * - field: A field for an SQL query.
+ * - sort: 'asc' or 'desc'.
+ */
+function _forum_get_topic_order($sortby) {
+ switch ($sortby) {
+ case 1:
+ return array('field' => 'f.last_comment_timestamp', 'sort' => 'desc');
+ break;
+ case 2:
+ return array('field' => 'f.last_comment_timestamp', 'sort' => 'asc');
+ break;
+ case 3:
+ return array('field' => 'f.comment_count', 'sort' => 'desc');
+ break;
+ case 4:
+ return array('field' => 'f.comment_count', 'sort' => 'asc');
+ break;
+ }
+}
+
+/**
+ * Updates the taxonomy index for a given node.
+ *
+ * @param $nid
+ * The ID of the node to update.
+ */
+function _forum_update_forum_index($nid) {
+ $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(
+ ':nid' => $nid,
+ ':status' => COMMENT_PUBLISHED,
+ ))->fetchField();
+
+ if ($count > 0) {
+ // Comments exist.
+ $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(
+ ':nid' => $nid,
+ ':status' => COMMENT_PUBLISHED,
+ ))->fetchObject();
+ db_update('forum_index')
+ ->fields( array(
+ 'comment_count' => $count,
+ 'last_comment_timestamp' => $last_reply->created,
+ ))
+ ->condition('nid', $nid)
+ ->execute();
+ }
+ else {
+ // Comments do not exist.
+ $node = db_query('SELECT uid, created FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
+ db_update('forum_index')
+ ->fields( array(
+ 'comment_count' => 0,
+ 'last_comment_timestamp' => $node->created,
+ ))
+ ->condition('nid', $nid)
+ ->execute();
+ }
+}
+
+/**
+ * Implements hook_rdf_mapping().
+ */
+function forum_rdf_mapping() {
+ return array(
+ array(
+ 'type' => 'node',
+ 'bundle' => 'forum',
+ 'mapping' => array(
+ 'rdftype' => array('sioc:Post', 'sioct:BoardPost'),
+ 'taxonomy_forums' => array(
+ 'predicates' => array('sioc:has_container'),
+ 'type' => 'rel',
+ ),
+ ),
+ ),
+ array(
+ 'type' => 'taxonomy_term',
+ 'bundle' => 'forums',
+ 'mapping' => array(
+ 'rdftype' => array('sioc:Container', 'sioc:Forum'),
+ ),
+ ),
+ );
+}
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum.pages.inc b/kolab.org/www/drupal-7.26/modules/forum/forum.pages.inc
new file mode 100644
index 0000000..8538310
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum.pages.inc
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * User page callbacks for the Forum module.
+ */
+
+/**
+ * Page callback: Prints a forum listing.
+ *
+ * @param $forum_term
+ * A tree of all forums for a given taxonomy term ID. Defaults to NULL. See
+ * the return object of forum_forum_load() for a complete definition.
+ *
+ * @return
+ * A string containing HTML representing the themed forum listing.
+ *
+ * @see forum_menu()
+ */
+function forum_page($forum_term = NULL) {
+ if (!isset($forum_term)) {
+ // On the main page, display all the top-level forums.
+ $forum_term = forum_forum_load(0);
+ }
+
+ $forum_per_page = variable_get('forum_per_page', 25);
+ $sortby = variable_get('forum_order', 1);
+
+ if (empty($forum_term->container)) {
+ $topics = forum_get_topics($forum_term->tid, $sortby, $forum_per_page);
+ }
+ else {
+ $topics = '';
+ }
+
+ return theme('forums', array('forums' => $forum_term->forums, 'topics' => $topics, 'parents' => $forum_term->parents, 'tid' => $forum_term->tid, 'sortby' => $sortby, 'forums_per_page' => $forum_per_page));
+}
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forum.test b/kolab.org/www/drupal-7.26/modules/forum/forum.test
new file mode 100644
index 0000000..bc68a3e
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forum.test
@@ -0,0 +1,687 @@
+<?php
+
+/**
+ * @file
+ * Tests for forum.module.
+ */
+
+/**
+ * Provides automated tests for the Forum module.
+ */
+class ForumTestCase extends DrupalWebTestCase {
+
+ /**
+ * A user with various administrative privileges.
+ */
+ protected $admin_user;
+
+ /**
+ * A user that can create forum topics and edit its own topics.
+ */
+ protected $edit_own_topics_user;
+
+ /**
+ * A user that can create, edit, and delete forum topics.
+ */
+ protected $edit_any_topics_user;
+
+ /**
+ * A user with no special privileges.
+ */
+ protected $web_user;
+
+ /**
+ * An array representing a container.
+ */
+ protected $container;
+
+ /**
+ * An array representing a forum.
+ */
+ protected $forum;
+
+ /**
+ * An array representing a root forum.
+ */
+ protected $root_forum;
+
+ /**
+ * An array of forum topic node IDs.
+ */
+ protected $nids;
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Forum functionality',
+ 'description' => 'Create, view, edit, delete, and change forum entries and verify its consistency in the database.',
+ 'group' => 'Forum',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy', 'comment', 'forum');
+ // Create users.
+ $this->admin_user = $this->drupalCreateUser(array(
+ 'access administration pages',
+ 'administer modules',
+ 'administer blocks',
+ 'administer forums',
+ 'administer menu',
+ 'administer taxonomy',
+ 'create forum content',
+ ));
+ $this->edit_any_topics_user = $this->drupalCreateUser(array(
+ 'access administration pages',
+ 'create forum content',
+ 'edit any forum content',
+ 'delete any forum content',
+ ));
+ $this->edit_own_topics_user = $this->drupalCreateUser(array(
+ 'create forum content',
+ 'edit own forum content',
+ 'delete own forum content',
+ ));
+ $this->web_user = $this->drupalCreateUser(array());
+ }
+
+ /**
+ * Tests disabling and re-enabling the Forum module.
+ */
+ function testEnableForumField() {
+ $this->drupalLogin($this->admin_user);
+
+ // Disable the Forum module.
+ $edit = array();
+ $edit['modules[Core][forum][enable]'] = FALSE;
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
+ module_list(TRUE);
+ $this->assertFalse(module_exists('forum'), 'Forum module is not enabled.');
+
+ // Attempt to re-enable the Forum module and ensure it does not try to
+ // recreate the taxonomy_forums field.
+ $edit = array();
+ $edit['modules[Core][forum][enable]'] = 'forum';
+ $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+ $this->assertText(t('The configuration options have been saved.'), 'Modules status has been updated.');
+ module_list(TRUE);
+ $this->assertTrue(module_exists('forum'), 'Forum module is enabled.');
+ }
+
+ /**
+ * Tests forum functionality through the admin and user interfaces.
+ */
+ function testForum() {
+ //Check that the basic forum install creates a default forum topic
+ $this->drupalGet("/forum");
+ // Look for the "General discussion" default forum
+ $this->assertText(t("General discussion"), "Found the default forum at the /forum listing");
+
+ // Do the admin tests.
+ $this->doAdminTests($this->admin_user);
+ // Generate topics to populate the active forum block.
+ $this->generateForumTopics($this->forum);
+
+ // Login an unprivileged user to view the forum topics and generate an
+ // active forum topics list.
+ $this->drupalLogin($this->web_user);
+ // Verify that this user is shown a message that they may not post content.
+ $this->drupalGet('forum/' . $this->forum['tid']);
+ $this->assertText(t('You are not allowed to post new content in the forum'), "Authenticated user without permission to post forum content is shown message in local tasks to that effect.");
+
+ $this->viewForumTopics($this->nids);
+
+ // Log in, and do basic tests for a user with permission to edit any forum
+ // content.
+ $this->doBasicTests($this->edit_any_topics_user, TRUE);
+ // Create a forum node authored by this user.
+ $any_topics_user_node = $this->createForumTopic($this->forum, FALSE);
+
+ // Log in, and do basic tests for a user with permission to edit only its
+ // own forum content.
+ $this->doBasicTests($this->edit_own_topics_user, FALSE);
+ // Create a forum node authored by this user.
+ $own_topics_user_node = $this->createForumTopic($this->forum, FALSE);
+ // Verify that this user cannot edit forum content authored by another user.
+ $this->verifyForums($this->edit_any_topics_user, $any_topics_user_node, FALSE, 403);
+
+ // Verify that this user is shown a local task to add new forum content.
+ $this->drupalGet('forum');
+ $this->assertLink(t('Add new Forum topic'));
+ $this->drupalGet('forum/' . $this->forum['tid']);
+ $this->assertLink(t('Add new Forum topic'));
+
+ // Login a user with permission to edit any forum content.
+ $this->drupalLogin($this->edit_any_topics_user);
+ // Verify that this user can edit forum content authored by another user.
+ $this->verifyForums($this->edit_own_topics_user, $own_topics_user_node, TRUE);
+
+ // Verify the topic and post counts on the forum page.
+ $this->drupalGet('forum');
+
+ // Verify row for testing forum.
+ $forum_arg = array(':forum' => 'forum-list-' . $this->forum['tid']);
+
+ // Topics cell contains number of topics and number of unread topics.
+ $xpath = $this->buildXPathQuery('//tr[@id=:forum]//td[@class="topics"]', $forum_arg);
+ $topics = $this->xpath($xpath);
+ $topics = trim($topics[0]);
+ $this->assertEqual($topics, '6', 'Number of topics found.');
+
+ // Verify the number of unread topics.
+ $unread_topics = _forum_topics_unread($this->forum['tid'], $this->edit_any_topics_user->uid);
+ $unread_topics = format_plural($unread_topics, '1 new', '@count new');
+ $xpath = $this->buildXPathQuery('//tr[@id=:forum]//td[@class="topics"]//a', $forum_arg);
+ $this->assertFieldByXPath($xpath, $unread_topics, 'Number of unread topics found.');
+
+ // Verify total number of posts in forum.
+ $xpath = $this->buildXPathQuery('//tr[@id=:forum]//td[@class="posts"]', $forum_arg);
+ $this->assertFieldByXPath($xpath, '6', 'Number of posts found.');
+
+ // Test loading multiple forum nodes on the front page.
+ $this->drupalLogin($this->drupalCreateUser(array('administer content types', 'create forum content')));
+ $this->drupalPost('admin/structure/types/manage/forum', array('node_options[promote]' => 'promote'), t('Save content type'));
+ $this->createForumTopic($this->forum, FALSE);
+ $this->createForumTopic($this->forum, FALSE);
+ $this->drupalGet('node');
+
+ // Test adding a comment to a forum topic.
+ $node = $this->createForumTopic($this->forum, FALSE);
+ $edit = array();
+ $edit['comment_body[' . LANGUAGE_NONE . '][0][value]'] = $this->randomName();
+ $this->drupalPost("node/$node->nid", $edit, t('Save'));
+ $this->assertResponse(200);
+
+ // Test editing a forum topic that has a comment.
+ $this->drupalLogin($this->edit_any_topics_user);
+ $this->drupalGet('forum/' . $this->forum['tid']);
+ $this->drupalPost("node/$node->nid/edit", array(), t('Save'));
+ $this->assertResponse(200);
+
+ // Make sure constructing a forum node programmatically produces no notices.
+ $node = new stdClass;
+ $node->type = 'forum';
+ $node->title = 'Test forum notices';
+ $node->uid = 1;
+ $node->taxonomy_forums[LANGUAGE_NONE][0]['tid'] = $this->root_forum['tid'];
+ node_save($node);
+ }
+
+ /**
+ * Tests that forum nodes can't be added without a parent.
+ *
+ * Verifies that forum nodes are not created without choosing "forum" from the
+ * select list.
+ */
+ function testAddOrphanTopic() {
+ // Must remove forum topics to test creating orphan topics.
+ $vid = variable_get('forum_nav_vocabulary');
+ $tree = taxonomy_get_tree($vid);
+ foreach ($tree as $term) {
+ taxonomy_term_delete($term->tid);
+ }
+
+ // Create an orphan forum item.
+ $this->drupalLogin($this->admin_user);
+ $this->drupalPost('node/add/forum', array('title' => $this->randomName(10), 'body[' . LANGUAGE_NONE .'][0][value]' => $this->randomName(120)), t('Save'));
+
+ $nid_count = db_query('SELECT COUNT(nid) FROM {node}')->fetchField();
+ $this->assertEqual(0, $nid_count, 'A forum node was not created when missing a forum vocabulary.');
+
+ // Reset the defaults for future tests.
+ module_enable(array('forum'));
+ }
+
+ /**
+ * Runs admin tests on the admin user.
+ *
+ * @param object $user
+ * The logged in user.
+ */
+ private function doAdminTests($user) {
+ // Login the user.
+ $this->drupalLogin($user);
+
+ // Enable the active forum block.
+ $edit = array();
+ $edit['blocks[forum_active][region]'] = 'sidebar_second';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $this->assertResponse(200);
+ $this->assertText(t('The block settings have been updated.'), 'Active forum topics forum block was enabled');
+
+ // Enable the new forum block.
+ $edit = array();
+ $edit['blocks[forum_new][region]'] = 'sidebar_second';
+ $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
+ $this->assertResponse(200);
+ $this->assertText(t('The block settings have been updated.'), '[New forum topics] Forum block was enabled');
+
+ // Retrieve forum menu id.
+ $mlid = db_query_range("SELECT mlid FROM {menu_links} WHERE link_path = 'forum' AND menu_name = 'navigation' AND module = 'system' ORDER BY mlid ASC", 0, 1)->fetchField();
+
+ // Add forum to navigation menu.
+ $edit = array();
+ $this->drupalPost('admin/structure/menu/manage/navigation', $edit, t('Save configuration'));
+ $this->assertResponse(200);
+
+ // Edit forum taxonomy.
+ // Restoration of the settings fails and causes subsequent tests to fail.
+ $this->container = $this->editForumTaxonomy();
+ // Create forum container.
+ $this->container = $this->createForum('container');
+ // Verify "edit container" link exists and functions correctly.
+ $this->drupalGet('admin/structure/forum');
+ $this->clickLink('edit container');
+ $this->assertRaw('Edit container', 'Followed the link to edit the container');
+ // Create forum inside the forum container.
+ $this->forum = $this->createForum('forum', $this->container['tid']);
+ // Verify the "edit forum" link exists and functions correctly.
+ $this->drupalGet('admin/structure/forum');
+ $this->clickLink('edit forum');
+ $this->assertRaw('Edit forum', 'Followed the link to edit the forum');
+ // Navigate back to forum structure page.
+ $this->drupalGet('admin/structure/forum');
+ // Create second forum in container.
+ $this->delete_forum = $this->createForum('forum', $this->container['tid']);
+ // Save forum overview.
+ $this->drupalPost('admin/structure/forum/', array(), t('Save'));
+ $this->assertRaw(t('The configuration options have been saved.'));
+ // Delete this second forum.
+ $this->deleteForum($this->delete_forum['tid']);
+ // Create forum at the top (root) level.
+ $this->root_forum = $this->createForum('forum');
+
+ // Test vocabulary form alterations.
+ $this->drupalGet('admin/structure/taxonomy/forums/edit');
+ $this->assertFieldByName('op', t('Save'), 'Save button found.');
+ $this->assertNoFieldByName('op', t('Delete'), 'Delete button not found.');
+
+ // Test term edit form alterations.
+ $this->drupalGet('taxonomy/term/' . $this->container['tid'] . '/edit');
+ // Test parent field been hidden by forum module.
+ $this->assertNoField('parent[]', 'Parent field not found.');
+
+ // Test tags vocabulary form is not affected.
+ $this->drupalGet('admin/structure/taxonomy/tags/edit');
+ $this->assertFieldByName('op', t('Save'), 'Save button found.');
+ $this->assertFieldByName('op', t('Delete'), 'Delete button found.');
+ // Test tags vocabulary term form is not affected.
+ $this->drupalGet('admin/structure/taxonomy/tags/add');
+ $this->assertField('parent[]', 'Parent field found.');
+ // Test relations fieldset exists.
+ $relations_fieldset = $this->xpath("//fieldset[@id='edit-relations']");
+ $this->assertTrue(isset($relations_fieldset[0]), 'Relations fieldset element found.');
+ }
+
+ /**
+ * Edits the forum taxonomy.
+ */
+ function editForumTaxonomy() {
+ // Backup forum taxonomy.
+ $vid = variable_get('forum_nav_vocabulary', '');
+ $original_settings = taxonomy_vocabulary_load($vid);
+
+ // Generate a random name/description.
+ $title = $this->randomName(10);
+ $description = $this->randomName(100);
+
+ $edit = array(
+ 'name' => $title,
+ 'description' => $description,
+ 'machine_name' => drupal_strtolower(drupal_substr($this->randomName(), 3, 9)),
+ );
+
+ // Edit the vocabulary.
+ $this->drupalPost('admin/structure/taxonomy/' . $original_settings->machine_name . '/edit', $edit, t('Save'));
+ $this->assertResponse(200);
+ $this->assertRaw(t('Updated vocabulary %name.', array('%name' => $title)), 'Vocabulary was edited');
+
+ // Grab the newly edited vocabulary.
+ entity_get_controller('taxonomy_vocabulary')->resetCache();
+ $current_settings = taxonomy_vocabulary_load($vid);
+
+ // Make sure we actually edited the vocabulary properly.
+ $this->assertEqual($current_settings->name, $title, 'The name was updated');
+ $this->assertEqual($current_settings->description, $description, 'The description was updated');
+
+ // Restore the original vocabulary.
+ taxonomy_vocabulary_save($original_settings);
+ drupal_static_reset('taxonomy_vocabulary_load');
+ $current_settings = taxonomy_vocabulary_load($vid);
+ $this->assertEqual($current_settings->name, $original_settings->name, 'The original vocabulary settings were restored');
+ }
+
+ /**
+ * Creates a forum container or a forum.
+ *
+ * @param $type
+ * The forum type (forum container or forum).
+ * @param $parent
+ * The forum parent. This defaults to 0, indicating a root forum.
+ * another forum).
+ *
+ * @return
+ * The created taxonomy term data.
+ */
+ function createForum($type, $parent = 0) {
+ // Generate a random name/description.
+ $name = $this->randomName(10);
+ $description = $this->randomName(100);
+
+ $edit = array(
+ 'name' => $name,
+ 'description' => $description,
+ 'parent[0]' => $parent,
+ 'weight' => '0',
+ );
+
+ // Create forum.
+ $this->drupalPost('admin/structure/forum/add/' . $type, $edit, t('Save'));
+ $this->assertResponse(200);
+ $type = ($type == 'container') ? 'forum container' : 'forum';
+ $this->assertRaw(t('Created new @type %term.', array('%term' => $name, '@type' => t($type))), format_string('@type was created', array('@type' => ucfirst($type))));
+
+ // Verify forum.
+ $term = db_query("SELECT * FROM {taxonomy_term_data} t WHERE t.vid = :vid AND t.name = :name AND t.description = :desc", array(':vid' => variable_get('forum_nav_vocabulary', ''), ':name' => $name, ':desc' => $description))->fetchAssoc();
+ $this->assertTrue(!empty($term), 'The ' . $type . ' exists in the database');
+
+ // Verify forum hierarchy.
+ $tid = $term['tid'];
+ $parent_tid = db_query("SELECT t.parent FROM {taxonomy_term_hierarchy} t WHERE t.tid = :tid", array(':tid' => $tid))->fetchField();
+ $this->assertTrue($parent == $parent_tid, 'The ' . $type . ' is linked to its container');
+
+ return $term;
+ }
+
+ /**
+ * Deletes a forum.
+ *
+ * @param $tid
+ * The forum ID.
+ */
+ function deleteForum($tid) {
+ // Delete the forum.
+ $this->drupalPost('admin/structure/forum/edit/forum/' . $tid, array(), t('Delete'));
+ $this->drupalPost(NULL, array(), t('Delete'));
+
+ // Assert that the forum no longer exists.
+ $this->drupalGet('forum/' . $tid);
+ $this->assertResponse(404, 'The forum was not found');
+
+ // Assert that the associated term has been removed from the
+ // forum_containers variable.
+ $containers = variable_get('forum_containers', array());
+ $this->assertFalse(in_array($tid, $containers), 'The forum_containers variable has been updated.');
+ }
+
+ /**
+ * Runs basic tests on the indicated user.
+ *
+ * @param $user
+ * The logged in user.
+ * @param $admin
+ * User has 'access administration pages' privilege.
+ */
+ private function doBasicTests($user, $admin) {
+ // Login the user.
+ $this->drupalLogin($user);
+ // Attempt to create forum topic under a container.
+ $this->createForumTopic($this->container, TRUE);
+ // Create forum node.
+ $node = $this->createForumTopic($this->forum, FALSE);
+ // Verify the user has access to all the forum nodes.
+ $this->verifyForums($user, $node, $admin);
+ }
+
+ /**
+ * Creates forum topic.
+ *
+ * @param array $forum
+ * A forum array.
+ * @param boolean $container
+ * TRUE if $forum is a container; FALSE otherwise.
+ *
+ * @return object
+ * The created topic node.
+ */
+ function createForumTopic($forum, $container = FALSE) {
+ // Generate a random subject/body.
+ $title = $this->randomName(20);
+ $body = $this->randomName(200);
+
+ $langcode = LANGUAGE_NONE;
+ $edit = array(
+ "title" => $title,
+ "body[$langcode][0][value]" => $body,
+ );
+ $tid = $forum['tid'];
+
+ // Create the forum topic, preselecting the forum ID via a URL parameter.
+ $this->drupalPost('node/add/forum/' . $tid, $edit, t('Save'));
+
+ $type = t('Forum topic');
+ if ($container) {
+ $this->assertNoRaw(t('@type %title has been created.', array('@type' => $type, '%title' => $title)), 'Forum topic was not created');
+ $this->assertRaw(t('The item %title is a forum container, not a forum.', array('%title' => $forum['name'])), 'Error message was shown');
+ return;
+ }
+ else {
+ $this->assertRaw(t('@type %title has been created.', array('@type' => $type, '%title' => $title)), 'Forum topic was created');
+ $this->assertNoRaw(t('The item %title is a forum container, not a forum.', array('%title' => $forum['name'])), 'No error message was shown');
+ }
+
+ // Retrieve node object, ensure that the topic was created and in the proper forum.
+ $node = $this->drupalGetNodeByTitle($title);
+ $this->assertTrue($node != NULL, format_string('Node @title was loaded', array('@title' => $title)));
+ $this->assertEqual($node->taxonomy_forums[LANGUAGE_NONE][0]['tid'], $tid, 'Saved forum topic was in the expected forum');
+
+ // View forum topic.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertRaw($title, 'Subject was found');
+ $this->assertRaw($body, 'Body was found');
+
+ return $node;
+ }
+
+ /**
+ * Verifies that the logged in user has access to a forum nodes.
+ *
+ * @param $node_user
+ * The user who creates the node.
+ * @param $node
+ * The node being checked.
+ * @param $admin
+ * Boolean to indicate whether the user can 'access administration pages'.
+ * @param $response
+ * The exptected HTTP response code.
+ */
+ private function verifyForums($node_user, $node, $admin, $response = 200) {
+ $response2 = ($admin) ? 200 : 403;
+
+ // View forum help node.
+ $this->drupalGet('admin/help/forum');
+ $this->assertResponse($response2);
+ if ($response2 == 200) {
+ $this->assertTitle(t('Forum | Drupal'), 'Forum help title was displayed');
+ $this->assertText(t('Forum'), 'Forum help node was displayed');
+ }
+
+ // Verify the forum blocks were displayed.
+ $this->drupalGet('');
+ $this->assertResponse(200);
+ $this->assertText(t('New forum topics'), '[New forum topics] Forum block was displayed');
+
+ // View forum container page.
+ $this->verifyForumView($this->container);
+ // View forum page.
+ $this->verifyForumView($this->forum, $this->container);
+ // View root forum page.
+ $this->verifyForumView($this->root_forum);
+
+ // View forum node.
+ $this->drupalGet('node/' . $node->nid);
+ $this->assertResponse(200);
+ $this->assertTitle($node->title . ' | Drupal', 'Forum node was displayed');
+ $breadcrumb = array(
+ l(t('Home'), NULL),
+ l(t('Forums'), 'forum'),
+ l($this->container['name'], 'forum/' . $this->container['tid']),
+ l($this->forum['name'], 'forum/' . $this->forum['tid']),
+ );
+ $this->assertRaw(theme('breadcrumb', array('breadcrumb' => $breadcrumb)), 'Breadcrumbs were displayed');
+
+ // View forum edit node.
+ $this->drupalGet('node/' . $node->nid . '/edit');
+ $this->assertResponse($response);
+ if ($response == 200) {
+ $this->assertTitle('Edit Forum topic ' . $node->title . ' | Drupal', 'Forum edit node was displayed');
+ }
+
+ if ($response == 200) {
+ // Edit forum node (including moving it to another forum).
+ $edit = array();
+ $langcode = LANGUAGE_NONE;
+ $edit["title"] = 'node/' . $node->nid;
+ $edit["body[$langcode][0][value]"] = $this->randomName(256);
+ // Assume the topic is initially associated with $forum.
+ $edit["taxonomy_forums[$langcode]"] = $this->root_forum['tid'];
+ $edit['shadow'] = TRUE;
+ $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
+ $this->assertRaw(t('Forum topic %title has been updated.', array('%title' => $edit["title"])), 'Forum node was edited');
+
+ // Verify topic was moved to a different forum.
+ $forum_tid = db_query("SELECT tid FROM {forum} WHERE nid = :nid AND vid = :vid", array(
+ ':nid' => $node->nid,
+ ':vid' => $node->vid,
+ ))->fetchField();
+ $this->assertTrue($forum_tid == $this->root_forum['tid'], 'The forum topic is linked to a different forum');
+
+ // Delete forum node.
+ $this->drupalPost('node/' . $node->nid . '/delete', array(), t('Delete'));
+ $this->assertResponse($response);
+ $this->assertRaw(t('Forum topic %title has been deleted.', array('%title' => $edit['title'])), 'Forum node was deleted');
+ }
+ }
+
+ /**
+ * Verifies display of forum page.
+ *
+ * @param $forum
+ * A row from the taxonomy_term_data table in an array.
+ * @param $parent
+ * (optional) An array representing the forum's parent.
+ */
+ private function verifyForumView($forum, $parent = NULL) {
+ // View forum page.
+ $this->drupalGet('forum/' . $forum['tid']);
+ $this->assertResponse(200);
+ $this->assertTitle($forum['name'] . ' | Drupal', 'Forum name was displayed');
+
+ $breadcrumb = array(
+ l(t('Home'), NULL),
+ l(t('Forums'), 'forum'),
+ );
+ if (isset($parent)) {
+ $breadcrumb[] = l($parent['name'], 'forum/' . $parent['tid']);
+ }
+
+ $this->assertRaw(theme('breadcrumb', array('breadcrumb' => $breadcrumb)), 'Breadcrumbs were displayed');
+ }
+
+ /**
+ * Generates forum topics to test the display of an active forum block.
+ *
+ * @param array $forum
+ * The foorum array (a row from taxonomy_term_data table).
+ */
+ private function generateForumTopics($forum) {
+ $this->nids = array();
+ for ($i = 0; $i < 5; $i++) {
+ $node = $this->createForumTopic($this->forum, FALSE);
+ $this->nids[] = $node->nid;
+ }
+ }
+
+ /**
+ * Views forum topics to test the display of an active forum block.
+ *
+ * @todo The logic here is completely incorrect, since the active forum topics
+ * block is determined by comments on the node, not by views.
+ * @todo DIE
+ *
+ * @param $nids
+ * An array of forum node IDs.
+ */
+ private function viewForumTopics($nids) {
+ for ($i = 0; $i < 2; $i++) {
+ foreach ($nids as $nid) {
+ $this->drupalGet('node/' . $nid);
+ $this->drupalGet('node/' . $nid);
+ $this->drupalGet('node/' . $nid);
+ }
+ }
+ }
+}
+
+/**
+ * Tests the forum index listing.
+ */
+class ForumIndexTestCase extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Forum index',
+ 'description' => 'Tests the forum index listing.',
+ 'group' => 'Forum',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('taxonomy', 'comment', 'forum');
+
+ // Create a test user.
+ $web_user = $this->drupalCreateUser(array('create forum content', 'edit own forum content', 'edit any forum content', 'administer nodes'));
+ $this->drupalLogin($web_user);
+ }
+
+ /**
+ * Tests the forum index for published and unpublished nodes.
+ */
+ function testForumIndexStatus() {
+
+ $langcode = LANGUAGE_NONE;
+
+ // The forum ID to use.
+ $tid = 1;
+
+ // Create a test node.
+ $title = $this->randomName(20);
+ $edit = array(
+ "title" => $title,
+ "body[$langcode][0][value]" => $this->randomName(200),
+ );
+
+ // Create the forum topic, preselecting the forum ID via a URL parameter.
+ $this->drupalPost('node/add/forum/' . $tid, $edit, t('Save'));
+
+ // Check that the node exists in the database.
+ $node = $this->drupalGetNodeByTitle($title);
+ $this->assertTrue(!empty($node), 'New forum node found in database.');
+
+ // Verify that the node appears on the index.
+ $this->drupalGet('forum/' . $tid);
+ $this->assertText($title, 'Published forum topic appears on index.');
+
+ // Unpublish the node.
+ $edit = array(
+ 'status' => FALSE,
+ );
+ $this->drupalPost("node/{$node->nid}/edit", $edit, t('Save'));
+ $this->drupalGet("node/{$node->nid}");
+ $this->assertText(t('Access denied'), 'Unpublished node is no longer accessible.');
+
+ // Verify that the node no longer appears on the index.
+ $this->drupalGet('forum/' . $tid);
+ $this->assertNoText($title, 'Unpublished forum topic no longer appears on index.');
+ }
+}
diff --git a/kolab.org/www/drupal-7.26/modules/forum/forums.tpl.php b/kolab.org/www/drupal-7.26/modules/forum/forums.tpl.php
new file mode 100644
index 0000000..6a0e02e
--- /dev/null
+++ b/kolab.org/www/drupal-7.26/modules/forum/forums.tpl.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Displays a forum.
+ *
+ * May contain forum containers as well as forum topics.
+ *
+ * Available variables:
+ * - $forums: The forums to display (as processed by forum-list.tpl.php).
+ * - $topics: The topics to display (as processed by forum-topic-list.tpl.php).
+ * - $forums_defined: A flag to indicate that the forums are configured.
+ *
+ * @see template_preprocess_forums()
+ *
+ * @ingroup themeable
+ */
+?>
+<?php if ($forums_defined): ?>
+<div id="forum">
+ <?php print $forums; ?>
+ <?php print $topics; ?>
+</div>
+<?php endif; ?>