Newer
Older
Dries Buytaert
committed
* Provides discussion forums.
use Drupal\Core\Entity\EntityInterface;
Angie Byron
committed
use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
Angie Byron
committed
use Drupal\taxonomy\Plugin\Core\Entity\Term;
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_help().
Dries Buytaert
committed
*/
Gábor Hojtsy
committed
function forum_help($path, $arg) {
switch ($path) {
Dries Buytaert
committed
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>';
Angie Byron
committed
$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>';
Dries Buytaert
committed
$output .= '<dt>' . t('Forum navigation') . '</dt>';
$output .= '<dd>' . t('Enabling the Forum module provides a default <em>Forums</em> menu item in the Tools menu that links to the <a href="@forums">Forums page</a>.', array('@forums' => url('forum'))) . '</dd>';
Angie Byron
committed
$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>';
Angie Byron
committed
$output .= '</dl>';
Dries Buytaert
committed
return $output;
Dries Buytaert
committed
case 'admin/structure/forum':
Angie Byron
committed
$output = '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
Dries Buytaert
committed
$output .= theme('more_help_link', array('url' => 'admin/help/forum'));
Angie Byron
committed
return $output;
Dries Buytaert
committed
case 'admin/structure/forum/add/container':
Angie Byron
committed
return '<p>' . t('Use containers to group related forums.') . '</p>';
Dries Buytaert
committed
case 'admin/structure/forum/add/forum':
Angie Byron
committed
return '<p>' . t('A forum holds related forum topics.') . '</p>';
Dries Buytaert
committed
case 'admin/structure/forum/settings':
Angie Byron
committed
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>';
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_theme().
Dries Buytaert
committed
*/
function forum_theme() {
return array(
Dries Buytaert
committed
'forums' => array(
'template' => 'forums',
'variables' => array('forums' => NULL, 'topics' => NULL, 'parents' => NULL, 'tid' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
Dries Buytaert
committed
),
'forum_list' => array(
'template' => 'forum-list',
'variables' => array('forums' => NULL, 'parents' => NULL, 'tid' => NULL),
Dries Buytaert
committed
),
'forum_topic_list' => array(
'template' => 'forum-topic-list',
'variables' => array('tid' => NULL, 'topics' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL),
Dries Buytaert
committed
),
'forum_icon' => array(
'template' => 'forum-icon',
'variables' => array('new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0, 'first_new' => FALSE),
Dries Buytaert
committed
),
Dries Buytaert
committed
'forum_submitted' => array(
'template' => 'forum-submitted',
'variables' => array('topic' => NULL),
),
Dries Buytaert
committed
'forum_form' => array(
'render element' => 'form',
Dries Buytaert
committed
'file' => 'forum.admin.inc',
),
Dries Buytaert
committed
);
}
Dries Buytaert
committed
* Implements hook_menu().
Dries Buytaert
committed
function forum_menu() {
$items['forum'] = array(
'title' => 'Forums',
Dries Buytaert
committed
'page callback' => 'forum_page',
'access arguments' => array('access content'),
'file' => 'forum.pages.inc',
Dries Buytaert
committed
);
Angie Byron
committed
$items['forum/%forum_forum'] = array(
'title' => 'Forums',
'title callback' => 'entity_page_label',
'title arguments' => array(1),
Angie Byron
committed
'page callback' => 'forum_page',
'page arguments' => array(1),
'access arguments' => array('access content'),
'file' => 'forum.pages.inc',
);
Dries Buytaert
committed
$items['admin/structure/forum'] = array(
'title' => 'Forums',
Angie Byron
committed
'description' => 'Control forum hierarchy settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array('forum_overview'),
Dries Buytaert
committed
'access arguments' => array('administer forums'),
'file' => 'forum.admin.inc',
Dries Buytaert
committed
);
Dries Buytaert
committed
$items['admin/structure/forum/list'] = array(
'title' => 'List',
Dries Buytaert
committed
'type' => MENU_DEFAULT_LOCAL_TASK,
);
Dries Buytaert
committed
$items['admin/structure/forum/add/container'] = array(
'title' => 'Add container',
Dries Buytaert
committed
'page callback' => 'forum_form_main',
'page arguments' => array('container'),
Dries Buytaert
committed
'access arguments' => array('administer forums'),
Angie Byron
committed
'type' => MENU_LOCAL_ACTION,
Dries Buytaert
committed
'parent' => 'admin/structure/forum',
'file' => 'forum.admin.inc',
Dries Buytaert
committed
);
Dries Buytaert
committed
$items['admin/structure/forum/add/forum'] = array(
'title' => 'Add forum',
Dries Buytaert
committed
'page callback' => 'forum_form_main',
'page arguments' => array('forum'),
Dries Buytaert
committed
'access arguments' => array('administer forums'),
Angie Byron
committed
'type' => MENU_LOCAL_ACTION,
Dries Buytaert
committed
'parent' => 'admin/structure/forum',
'file' => 'forum.admin.inc',
Dries Buytaert
committed
);
Dries Buytaert
committed
$items['admin/structure/forum/settings'] = array(
'title' => 'Settings',
'weight' => 100,
Dries Buytaert
committed
'type' => MENU_LOCAL_TASK,
Dries Buytaert
committed
'parent' => 'admin/structure/forum',
Angie Byron
committed
'route_name' => 'forum_settings',
Dries Buytaert
committed
);
Angie Byron
committed
$items['admin/structure/forum/edit/container/%taxonomy_term'] = array(
'title' => 'Edit container',
'page callback' => 'forum_form_main',
Dries Buytaert
committed
'page arguments' => array('container', 5),
Dries Buytaert
committed
'access arguments' => array('administer forums'),
'file' => 'forum.admin.inc',
Dries Buytaert
committed
);
Angie Byron
committed
$items['admin/structure/forum/edit/forum/%taxonomy_term'] = array(
'title' => 'Edit forum',
Dries Buytaert
committed
'page callback' => 'forum_form_main',
'page arguments' => array('forum', 5),
Dries Buytaert
committed
'access arguments' => array('administer forums'),
'file' => 'forum.admin.inc',
Dries Buytaert
committed
);
return $items;
}
Dries Buytaert
committed
/**
* Implements hook_menu_local_tasks().
Dries Buytaert
committed
*/
function forum_menu_local_tasks(&$data, $router_item, $root_path) {
Dries Buytaert
committed
global $user;
Angie Byron
committed
// 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]->id() : 0);
Angie Byron
committed
$forum_term = forum_forum_load($tid);
if ($forum_term) {
Dries Buytaert
committed
$links = array();
// Loop through all bundles for forum taxonomy vocabulary field.
$field = field_info_field('taxonomy_forums');
Dries Buytaert
committed
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_label($type))),
'href' => 'node/add/' . $type . '/' . $forum_term->id(),
Dries Buytaert
committed
),
);
}
}
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(
Angie Byron
committed
'title' => t('<a href="@login">Log in</a> to post new content in the forum.', array(
Dries Buytaert
committed
'@login' => url('user/login', array('query' => drupal_get_destination())),
)),
'localized_options' => array('html' => TRUE),
),
);
}
}
$data['actions'] += $links;
Dries Buytaert
committed
}
}
}
Dries Buytaert
committed
Alex Pott
committed
/**
* Implements hook_menu_local_tasks_alter().
*
* Remove the 'Add Forum' and 'Add container' local tasks on the delete form.
*/
function forum_menu_local_tasks_alter(&$data, $router_item, $root_path) {
if ($root_path == 'admin/structure/forum' && !empty($router_item['map'][3]) &&
$router_item['map'][3] == 'delete') {
$data = array();
}
}
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_entity_bundle_info_alter().
Dries Buytaert
committed
*/
Dries Buytaert
committed
function forum_entity_bundle_info_alter(&$bundles) {
// Take over URI construction for taxonomy terms that are forums.
Dries Buytaert
committed
if ($vid = config('forum.settings')->get('vocabulary')) {
Dries Buytaert
committed
if (isset($bundles['taxonomy_term'][$vid])) {
$bundles['taxonomy_term'][$vid]['uri_callback'] = 'forum_uri';
Dries Buytaert
committed
}
}
}
/**
Jennifer Hodgdon
committed
* Entity URI callback used in forum_entity_info_alter().
Dries Buytaert
committed
*/
function forum_uri($forum) {
return array(
'path' => 'forum/' . $forum->id(),
Dries Buytaert
committed
);
}
Dries Buytaert
committed
/**
Jennifer Hodgdon
committed
* Checks whether a node can be used in a forum, based on its content type.
Angie Byron
committed
*
Angie Byron
committed
* @param \Drupal\Core\Entity\EntityInterface $node
* A node entity.
*
Angie Byron
committed
* @return
* Boolean indicating if the node can be assigned to a forum.
Dries Buytaert
committed
*/
Angie Byron
committed
function _forum_node_check_node_type(EntityInterface $node) {
Angie Byron
committed
// Fetch information about the forum field.
$instance = field_info_instance('node', 'taxonomy_forums', $node->type);
return !empty($instance);
Dries Buytaert
committed
}
Dries Buytaert
committed
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_node_view().
Dries Buytaert
committed
*/
Angie Byron
committed
function forum_node_view(EntityInterface $node, EntityDisplay $display, $view_mode) {
Dries Buytaert
committed
$vid = config('forum.settings')->get('vocabulary');
Dries Buytaert
committed
$vocabulary = taxonomy_vocabulary_load($vid);
Angie Byron
committed
if (_forum_node_check_node_type($node)) {
Dries Buytaert
committed
if ($view_mode == 'full' && node_is_page($node)) {
Dries Buytaert
committed
// Breadcrumb navigation
$breadcrumb[] = l(t('Home'), NULL);
$breadcrumb[] = l($vocabulary->name, 'forum');
if ($parents = taxonomy_term_load_parents_all($node->forum_tid)) {
Dries Buytaert
committed
$parents = array_reverse($parents);
Angie Byron
committed
foreach ($parents as $parent) {
$breadcrumb[] = l($parent->label(), 'forum/' . $parent->id());
Dries Buytaert
committed
}
}
drupal_set_breadcrumb($breadcrumb);
Dries Buytaert
committed
Dries Buytaert
committed
}
}
}
Dries Buytaert
committed
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_node_validate().
*
Jennifer Hodgdon
committed
* Checks in particular that the node is assigned only a "leaf" term in the
* forum taxonomy.
Dries Buytaert
committed
*/
Angie Byron
committed
function forum_node_validate(EntityInterface $node, $form) {
Angie Byron
committed
if (_forum_node_check_node_type($node)) {
$langcode = $form['taxonomy_forums']['#language'];
Dries Buytaert
committed
// vocabulary is selected, not a "container" term.
Angie Byron
committed
if (!empty($node->taxonomy_forums[$langcode])) {
Dries Buytaert
committed
// Extract the node's proper topic ID.
Dries Buytaert
committed
$containers = config('forum.settings')->get('containers');
Angie Byron
committed
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;
}
Dries Buytaert
committed
$term = taxonomy_term_load($item['tid']);
Dries Buytaert
committed
if (!$term) {
form_set_error('taxonomy_forums', t('Select a forum.'));
continue;
}
Dries Buytaert
committed
$used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid', 0, 1, array(
':tid' => $term->id(),
':vid' => $term->bundle(),
Angie Byron
committed
))->fetchField();
if ($used && in_array($term->id(), $containers)) {
Angie Byron
committed
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->label())));
Dries Buytaert
committed
}
}
Dries Buytaert
committed
}
}
}
Dries Buytaert
committed
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_node_presave().
Dries Buytaert
committed
*
Jennifer Hodgdon
committed
* Assigns the forum taxonomy when adding a topic from within a forum.
Dries Buytaert
committed
*/
Angie Byron
committed
function forum_node_presave(EntityInterface $node) {
Angie Byron
committed
if (_forum_node_check_node_type($node)) {
Dries Buytaert
committed
// Make sure all fields are set properly:
$node->icon = !empty($node->icon) ? $node->icon : '';
Dries Buytaert
committed
reset($node->taxonomy_forums);
$langcode = key($node->taxonomy_forums);
Angie Byron
committed
if (!empty($node->taxonomy_forums[$langcode])) {
Dries Buytaert
committed
$node->forum_tid = $node->taxonomy_forums[$langcode][0]['tid'];
// Only do a shadow copy check if this is not a new node.
if (!$node->isNew()) {
$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);
}
Dries Buytaert
committed
}
}
}
}
Gábor Hojtsy
committed
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_node_update().
Dries Buytaert
committed
*/
Angie Byron
committed
function forum_node_update(EntityInterface $node) {
Angie Byron
committed
if (_forum_node_check_node_type($node)) {
Angie Byron
committed
// If this is not a new revision and does exist, update the forum record,
// otherwise insert a new one.
if ($node->getRevisionId() == $node->original->getRevisionId() && db_query('SELECT tid FROM {forum} WHERE nid=:nid', array(':nid' => $node->nid))->fetchField()) {
Angie Byron
committed
if (!empty($node->forum_tid)) {
Dries Buytaert
committed
db_update('forum')
Angie Byron
committed
->fields(array('tid' => $node->forum_tid))
Dries Buytaert
committed
->condition('vid', $node->vid)
->execute();
Dries Buytaert
committed
}
// The node is removed from the forum.
else {
Dries Buytaert
committed
db_delete('forum')
->condition('nid', $node->nid)
->execute();
Dries Buytaert
committed
}
}
else {
Angie Byron
committed
if (!empty($node->forum_tid)) {
Dries Buytaert
committed
db_insert('forum')
->fields(array(
Angie Byron
committed
'tid' => $node->forum_tid,
Dries Buytaert
committed
'vid' => $node->vid,
'nid' => $node->nid,
))
->execute();
Gábor Hojtsy
committed
}
Dries Buytaert
committed
}
Angie Byron
committed
// If the node has a shadow forum topic, update the record for this
// revision.
Dries Buytaert
committed
if (!empty($node->shadow)) {
Angie Byron
committed
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();
}
Dries Buytaert
committed
}
}
Gábor Hojtsy
committed
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_node_insert().
Dries Buytaert
committed
*/
Angie Byron
committed
function forum_node_insert(EntityInterface $node) {
Angie Byron
committed
if (_forum_node_check_node_type($node)) {
if (!empty($node->forum_tid)) {
Dries Buytaert
committed
$nid = db_insert('forum')
->fields(array(
Angie Byron
committed
'tid' => $node->forum_tid,
Dries Buytaert
committed
'vid' => $node->vid,
'nid' => $node->nid,
))
->execute();
Dries Buytaert
committed
}
}
}
Gábor Hojtsy
committed
Dries Buytaert
committed
/**
catch
committed
* Implements hook_node_predelete().
Dries Buytaert
committed
*/
Angie Byron
committed
function forum_node_predelete(EntityInterface $node) {
Angie Byron
committed
if (_forum_node_check_node_type($node)) {
Dries Buytaert
committed
db_delete('forum')
->condition('nid', $node->nid)
->execute();
Angie Byron
committed
db_delete('forum_index')
->condition('nid', $node->nid)
->execute();
Dries Buytaert
committed
}
Dries Buytaert
committed
}
Dries Buytaert
committed
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_node_load().
Dries Buytaert
committed
*/
Angie Byron
committed
function forum_node_load($nodes) {
$node_vids = array();
foreach ($nodes as $node) {
Angie Byron
committed
if (_forum_node_check_node_type($node)) {
$node_vids[] = $node->vid;
}
}
if (!empty($node_vids)) {
Angie Byron
committed
$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;
}
Dries Buytaert
committed
}
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_node_info().
Dries Buytaert
committed
*/
function forum_node_info() {
Neil Drumm
committed
return array(
'forum' => array(
Steven Wittens
committed
'name' => t('Forum topic'),
'base' => 'forum',
Angie Byron
committed
'description' => t('A <em>forum topic</em> starts a new discussion thread within a forum.'),
Neil Drumm
committed
'title_label' => t('Subject'),
)
);
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_permission().
Dries Buytaert
committed
*/
function forum_permission() {
$perms = array(
Angie Byron
committed
'administer forums' => array(
'title' => t('Administer forums'),
),
);
return $perms;
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_taxonomy_term_delete().
Dries Buytaert
committed
*/
Dries Buytaert
committed
function forum_taxonomy_term_delete(Term $term) {
Angie Byron
committed
// For containers, remove the tid from the forum_containers variable.
Dries Buytaert
committed
$config = config('forum.settings');
$containers = $config->get('containers');
$key = array_search($term->id(), $containers);
Angie Byron
committed
if ($key !== FALSE) {
unset($containers[$key]);
}
Dries Buytaert
committed
$config->set('containers', $containers)->save();
Angie Byron
committed
}
Gerhard Killesreiter
committed
Angie Byron
committed
/**
Dries Buytaert
committed
* Implements hook_comment_publish().
Angie Byron
committed
*
Jennifer Hodgdon
committed
* This actually handles the insertion and update of published nodes since
Angie Byron
committed
* comment_save() calls hook_comment_publish() for all published comments.
*/
function forum_comment_publish($comment) {
Dries Buytaert
committed
_forum_update_forum_index($comment->nid->target_id);
Angie Byron
committed
}
/**
Dries Buytaert
committed
* Implements hook_comment_update().
Angie Byron
committed
*
Jennifer Hodgdon
committed
* The Comment module doesn't call hook_comment_unpublish() when saving
* individual comments, so we need to check for those here.
Angie Byron
committed
*/
function forum_comment_update($comment) {
Jennifer Hodgdon
committed
// comment_save() calls hook_comment_publish() for all published comments,
// so we need to handle all other values here.
if (!$comment->status->value) {
Dries Buytaert
committed
_forum_update_forum_index($comment->nid->target_id);
Angie Byron
committed
}
}
/**
Dries Buytaert
committed
* Implements hook_comment_unpublish().
Angie Byron
committed
*/
function forum_comment_unpublish($comment) {
Dries Buytaert
committed
_forum_update_forum_index($comment->nid->target_id);
Angie Byron
committed
}
/**
Dries Buytaert
committed
* Implements hook_comment_delete().
Angie Byron
committed
*/
function forum_comment_delete($comment) {
Dries Buytaert
committed
_forum_update_forum_index($comment->nid->target_id);
Angie Byron
committed
}
/**
Dries Buytaert
committed
* Implements hook_field_storage_pre_insert().
Angie Byron
committed
*/
function forum_field_storage_pre_insert(EntityInterface $entity, &$skip_fields) {
if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
Angie Byron
committed
$query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
Angie Byron
committed
foreach ($entity->getTranslationLanguages() as $langcode => $language) {
$translation = $entity->getTranslation($langcode, FALSE);
$query->values(array(
'nid' => $entity->id(),
'title' => $translation->title->value,
'tid' => $translation->taxonomy_forums->tid,
'sticky' => $entity->sticky,
'created' => $entity->created,
'comment_count' => 0,
'last_comment_timestamp' => $entity->created,
));
Angie Byron
committed
}
$query->execute();
}
}
/**
Dries Buytaert
committed
* Implements hook_field_storage_pre_update().
Angie Byron
committed
*/
function forum_field_storage_pre_update(EntityInterface $entity, &$skip_fields) {
Angie Byron
committed
$first_call = &drupal_static(__FUNCTION__, array());
if ($entity->entityType() == '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,
));
}
Angie Byron
committed
}
$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();
}
Dries Buytaert
committed
}
}
Gerhard Killesreiter
committed
/**
* Implements hook_form_BASE_FORM_ID_alter().
Gerhard Killesreiter
committed
*/
function forum_form_taxonomy_vocabulary_form_alter(&$form, &$form_state, $form_id) {
Dries Buytaert
committed
$vid = config('forum.settings')->get('vocabulary');
$vocabulary = $form_state['controller']->getEntity();
if ($vid == $vocabulary->id()) {
$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'] = TAXONOMY_HIERARCHY_SINGLE;
// Do not allow to delete forum's vocabulary.
$form['actions']['delete']['#access'] = FALSE;
// Do not allow to change a vid of forum's vocabulary.
$form['vid']['#disabled'] = TRUE;
}
}
/**
* Implements hook_form_FORM_ID_alter() for taxonomy_term_form().
*/
function forum_form_taxonomy_term_form_alter(&$form, &$form_state, $form_id) {
Dries Buytaert
committed
$vid = config('forum.settings')->get('vocabulary');
if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {
Gábor Hojtsy
committed
// Hide multiple parents select from forum terms.
$form['relations']['parent']['#access'] = FALSE;
Gábor Hojtsy
committed
}
}
/**
* 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'])) {
Angie Byron
committed
$langcode = $form['taxonomy_forums']['#language'];
Gábor Hojtsy
committed
// Make the vocabulary required for 'real' forum-nodes.
Angie Byron
committed
$form['taxonomy_forums'][$langcode]['#required'] = TRUE;
$form['taxonomy_forums'][$langcode]['#multiple'] = FALSE;
Dries Buytaert
committed
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).
Dries Buytaert
committed
$requested_forum_id = arg(3);
$form['taxonomy_forums'][$langcode]['#default_value'] = is_numeric($requested_forum_id) ? $requested_forum_id : '';
Dries Buytaert
committed
}
Gábor Hojtsy
committed
}
Gerhard Killesreiter
committed
}
/**
Jennifer Hodgdon
committed
* Render API callback: Lists nodes based on the element's #query property.
*
* This function can be used as a #pre_render callback.
*
* @see \Drupal\forum\Plugin\block\block\NewTopicsBlock::blockBuild()
* @see \Drupal\forum\Plugin\block\block\ActiveTopicsBlock::blockBuild()
Jennifer Hodgdon
committed
*/
function forum_block_view_pre_render($elements) {
$result = $elements['#query']->execute();
if ($node_title_list = node_title_list($result)) {
Dries Buytaert
committed
$elements['forum_list'] = $node_title_list;
Dries Buytaert
committed
$elements['forum_more'] = array('#theme' => 'more_link', '#url' => 'forum', '#title' => t('Read the latest forum topics.'));
}
return $elements;
Dries Buytaert
committed
/**
Dries Buytaert
committed
* Implements hook_form().
Dries Buytaert
committed
*/
Angie Byron
committed
function forum_form(EntityInterface $node, &$form_state) {
$type = node_type_load($node->type);
$form['title'] = array(
'#type' => 'textfield',
'#title' => check_plain($type->title_label),
'#default_value' => !empty($node->title) ? $node->title : '',
'#required' => TRUE, '#weight' => -5
);
Angie Byron
committed
Dries Buytaert
committed
if (!empty($node->nid)) {
Angie Byron
committed
$forum_terms = $node->taxonomy_forums;
Jennifer Hodgdon
committed
// If editing, give option to leave shadows.
Dries Buytaert
committed
$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.'));
Angie Byron
committed
$form['forum_tid'] = array('#type' => 'value', '#value' => $node->forum_tid);
Angie Byron
committed
* Returns a tree of all forums for a given taxonomy term ID.
Jennifer Hodgdon
committed
* (optional) Taxonomy term ID of the forum. If not given all forums will be
* returned.
*
Angie Byron
committed
* A tree of taxonomy objects, with the following additional properties:
Jennifer Hodgdon
committed
* - 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.
Angie Byron
committed
function forum_forum_load($tid = NULL) {
Dries Buytaert
committed
$cache = &drupal_static(__FUNCTION__, array());
Angie Byron
committed
// Return a cached forum tree if available.
if (!isset($tid)) {
$tid = 0;
}
Dries Buytaert
committed
if (isset($cache[$tid])) {
return $cache[$tid];
}
Dries Buytaert
committed
$config = config('forum.settings');
$vid = $config->get('vocabulary');
Angie Byron
committed
// Load and validate the parent term.
if ($tid) {
$forum_term = taxonomy_term_load($tid);
if (!$forum_term || ($forum_term->bundle() != $vid)) {
Angie Byron
committed
return $cache[$tid] = FALSE;
}
}
// If $tid is 0, create an empty entity to hold the child terms.
elseif ($tid === 0) {
$forum_term = entity_create('taxonomy_term', array(
Angie Byron
committed
'tid' => 0,
'vid' => $vid,
));
Angie Byron
committed
}
// Determine if the requested term is a container.
if (!$forum_term->id() || in_array($forum_term->id(), $config->get('containers'))) {
Angie Byron
committed
$forum_term->container = 1;
}
// Load parent terms.
$forum_term->parents = taxonomy_term_load_parents_all($forum_term->id());
Angie Byron
committed
// Load the tree below.
$forums = array();
Angie Byron
committed
$_forums = taxonomy_get_tree($vid, $tid, NULL, TRUE);
Dries Buytaert
committed
$query = db_select('node', 'n');
$query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
$query->join('forum', 'f', 'n.vid = f.vid');
Dries Buytaert
committed
$query->addExpression('COUNT(n.nid)', 'topic_count');
$query->addExpression('SUM(ncs.comment_count)', 'comment_count');
$counts = $query
Dries Buytaert
committed
->fields('f', array('tid'))
->condition('n.status', 1)
Dries Buytaert
committed
->groupBy('tid')
->addTag('node_access')
->execute()
->fetchAllAssoc('tid');
Angie Byron
committed
// Determine if the child term is a container.
if (in_array($forum->id(), $config->get('containers'))) {
Angie Byron
committed
// Merge in the topic and post counters.
if (!empty($counts[$forum->id()])) {
$forum->num_topics = $counts[$forum->id()]->topic_count;
$forum->num_posts = $counts[$forum->id()]->topic_count + $counts[$forum->id()]->comment_count;
}
else {
$forum->num_topics = 0;
$forum->num_posts = 0;
}
Angie Byron
committed
// Query "Last Post" information for this forum.
Dries Buytaert
committed
$query = db_select('node', 'n');
$query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->id()));
Dries Buytaert
committed
$query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
catch
committed
$query->join('users', 'u', 'ncs.last_comment_uid = u.uid');
$query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u.name END', 'last_comment_name');
Dries Buytaert
committed
$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();
Angie Byron
committed
// Merge in the "Last Post" information.
$last_post = new stdClass();
if (!empty($topic->last_comment_timestamp)) {
Angie Byron
committed
$last_post->created = $topic->last_comment_timestamp;
$last_post->name = $topic->last_comment_name;
$last_post->uid = $topic->last_comment_uid;
}
$forums[$forum->id()] = $forum;
Angie Byron
committed
// Cache the result, and return the tree.
$forum_term->forums = $forums;
$cache[$tid] = $forum_term;
return $forum_term;
/**
Jennifer Hodgdon
committed
* Calculates the number of new posts in a forum that the user has not yet read.
*
Dries Buytaert
committed
* Nodes are new if they are newer than HISTORY_READ_LIMIT.
Jennifer Hodgdon
committed
*
* @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) {
Dries Buytaert
committed
$query = db_select('node', 'n');
Dries Buytaert
committed
$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));
Dries Buytaert
committed
$query->addExpression('COUNT(n.nid)', 'count');
return $query
->condition('status', 1)
Dries Buytaert
committed
->condition('n.created', HISTORY_READ_LIMIT, '>')
Dries Buytaert
committed
->isNull('h.nid')
->addTag('node_access')
->execute()
->fetchField();
Jennifer Hodgdon
committed
/**
* 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) {
Angie Byron
committed
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'),
if ($forum_topic_list_header[$i]['field'] == $order['field']) {
$forum_topic_list_header[$i]['sort'] = $order['sort'];
catch
committed
$query = db_select('forum_index', 'f')
->extend('Drupal\Core\Database\Query\PagerSelectExtender')
->extend('Drupal\Core\Database\Query\TableSortExtender');
Angie Byron
committed
$query->fields('f');
Dries Buytaert
committed
$query
Angie Byron
committed
->condition('f.tid', $tid)
Dries Buytaert
committed
->addTag('node_access')
Dries Buytaert
committed
->addMetaData('base_table', 'forum_index')
Angie Byron
committed
->orderBy('f.sticky', 'DESC')
Dries Buytaert
committed
->orderByHeader($forum_topic_list_header)
->limit($forum_per_page);
Angie Byron
committed
$count_query = db_select('forum_index', 'f');
$count_query->condition('f.tid', $tid);
Dries Buytaert
committed
$count_query->addExpression('COUNT(*)');
Angie Byron
committed
$count_query->addTag('node_access');
Dries Buytaert
committed
$count_query->addMetaData('base_table', 'forum_index');
Dries Buytaert
committed
$query->setCountQuery($count_query);
$result = $query->execute();
Angie Byron
committed
$nids = array();
foreach ($result as $record) {
$nids[] = $record->nid;
}
if ($nids) {
$nodes = node_load_multiple($nids);
$query = db_select('node', 'n')
->extend('Drupal\Core\Database\Query\TableSortExtender');
$query->fields('n', array('nid'));
$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 = array();
foreach ($query->execute() as $row) {
$topic = $nodes[$row->nid];
$topic->comment_mode = $topic->comment;
foreach ($row as $key => $value) {
$topic->{$key} = $value;
}
$result[] = $topic;
}
Angie Byron
committed
}
else {
$result = array();
}
$first_new_found = FALSE;
Dries Buytaert
committed
foreach ($result as $topic) {
Jennifer Hodgdon
committed
// A forum is new if the topic is new, or if there are new comments since
// the user's last visit.
Angie Byron
committed
if ($topic->forum_tid != $tid) {
Angie Byron
committed
$topic->new = $topic->new_replies || ($topic->last_comment_timestamp > $history);
Kjartan Mannes
committed
}
else {
Dries Buytaert
committed
// Do not track "new replies" status for topics if the user is anonymous.
Kjartan Mannes
committed
}
// 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;
}
Angie Byron
committed
if ($topic->comment_count > 0) {
$last_reply = new stdClass();
Angie Byron
committed
$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->nid] = $topic;
/**
* Implements hook_preprocess_HOOK() for block.html.twig.
*/
function forum_preprocess_block(&$variables) {
if ($variables['configuration']['module'] == 'forum') {
Dries Buytaert
committed
$variables['attributes']['role'] = 'navigation';
}
}
* Prepares variables for forums templates.
*
* Default template: forums.html.twig.
*
* @param array $variables
Jennifer Hodgdon
committed
* 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.