forum.module 47.2 KB
Newer Older
Dries's avatar
 
Dries committed
1 2
<?php

Dries's avatar
 
Dries committed
3 4
/**
 * @file
5
 * Provides discussion forums.
Dries's avatar
 
Dries committed
6 7
 */

8
use Drupal\Core\Entity\EntityInterface;
9
use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
10
use Drupal\taxonomy\Plugin\Core\Entity\Term;
11

12
/**
13
 * Implements hook_help().
14
 */
15 16
function forum_help($path, $arg) {
  switch ($path) {
17
    case 'admin/help#forum':
18 19 20 21 22 23 24 25 26 27 28 29
      $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>';
30
      $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>';
31 32
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dl>';
33 34 35 36
      $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>';
37 38
      $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>';
39 40 41
      $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>';
42
      $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>';
43
      $output .= '</dl>';
44
      return $output;
45
    case 'admin/structure/forum':
46
      $output = '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
47
      $output .= theme('more_help_link', array('url' => 'admin/help/forum'));
48
      return $output;
49
    case 'admin/structure/forum/add/container':
50
      return '<p>' . t('Use containers to group related forums.') . '</p>';
51
    case 'admin/structure/forum/add/forum':
52
      return '<p>' . t('A forum holds related forum topics.') . '</p>';
53
    case 'admin/structure/forum/settings':
54
      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's avatar
 
Dries committed
55 56 57
  }
}

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

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

163
/**
164
 * Implements hook_menu_local_tasks().
165
 */
166
function forum_menu_local_tasks(&$data, $router_item, $root_path) {
167 168
  global $user;

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

216 217 218 219 220 221 222 223 224 225 226 227
/**
 * 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();
  }
}

228
/**
229
 * Implements hook_entity_bundle_info_alter().
230
 */
231
function forum_entity_bundle_info_alter(&$bundles) {
232
  // Take over URI construction for taxonomy terms that are forums.
233
  if ($vid = config('forum.settings')->get('vocabulary')) {
234 235
    if (isset($bundles['taxonomy_term'][$vid])) {
      $bundles['taxonomy_term'][$vid]['uri_callback'] = 'forum_uri';
236 237 238 239 240
    }
  }
}

/**
241
 * Entity URI callback used in forum_entity_info_alter().
242 243 244
 */
function forum_uri($forum) {
  return array(
245
    'path' => 'forum/' . $forum->id(),
246 247 248
  );
}

249
/**
250
 * Checks whether a node can be used in a forum, based on its content type.
251
 *
252
 * @param \Drupal\Core\Entity\EntityInterface $node
253
 *   A node entity.
254
 *
255 256
 * @return
 *   Boolean indicating if the node can be assigned to a forum.
257
 */
258
function _forum_node_check_node_type(EntityInterface $node) {
259
  // Fetch information about the forum field.
260 261
  $instance = field_info_instance('node', 'taxonomy_forums', $node->type);
  return !empty($instance);
262
}
263

264
/**
265
 * Implements hook_node_validate().
266
 *
267 268
 * Checks in particular that the node is assigned only a "leaf" term in the
 * forum taxonomy.
269
 */
270
function forum_node_validate(EntityInterface $node, $form) {
271 272
  if (_forum_node_check_node_type($node)) {
    $langcode = $form['taxonomy_forums']['#language'];
273
    // vocabulary is selected, not a "container" term.
274
    if (!empty($node->taxonomy_forums[$langcode])) {
275
      // Extract the node's proper topic ID.
276
      $containers = config('forum.settings')->get('containers');
277 278 279 280 281 282 283
      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;
        }
284
        $term = taxonomy_term_load($item['tid']);
285 286 287 288
        if (!$term) {
          form_set_error('taxonomy_forums', t('Select a forum.'));
          continue;
        }
289
        $used = db_query_range('SELECT 1 FROM {taxonomy_term_data} WHERE tid = :tid AND vid = :vid', 0, 1, array(
290
          ':tid' => $term->id(),
291
          ':vid' => $term->bundle(),
292
        ))->fetchField();
293
        if ($used && in_array($term->id(), $containers)) {
294
          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())));
295 296
        }
      }
297 298 299
    }
  }
}
300

301
/**
302
 * Implements hook_node_presave().
303
 *
304
 * Assigns the forum taxonomy when adding a topic from within a forum.
305
 */
306
function forum_node_presave(EntityInterface $node) {
307
  if (_forum_node_check_node_type($node)) {
308 309
    // Make sure all fields are set properly:
    $node->icon = !empty($node->icon) ? $node->icon : '';
310 311
    reset($node->taxonomy_forums);
    $langcode = key($node->taxonomy_forums);
312
    if (!empty($node->taxonomy_forums[$langcode])) {
313
      $node->forum_tid = $node->taxonomy_forums[$langcode][0]['tid'];
314 315 316 317 318 319 320
      // 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);
        }
321 322 323 324
      }
    }
  }
}
325

326
/**
327
 * Implements hook_node_update().
328
 */
329
function forum_node_update(EntityInterface $node) {
330
  if (_forum_node_check_node_type($node)) {
331 332 333
    // 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()) {
334
      if (!empty($node->forum_tid)) {
335
        db_update('forum')
336
          ->fields(array('tid' => $node->forum_tid))
337 338
          ->condition('vid', $node->vid)
          ->execute();
339 340 341
      }
      // The node is removed from the forum.
      else {
342 343 344
        db_delete('forum')
          ->condition('nid', $node->nid)
          ->execute();
345 346 347
      }
    }
    else {
348
      if (!empty($node->forum_tid)) {
349 350
        db_insert('forum')
          ->fields(array(
351
            'tid' => $node->forum_tid,
352 353 354 355
            'vid' => $node->vid,
            'nid' => $node->nid,
          ))
          ->execute();
356
      }
357
    }
358 359
    // If the node has a shadow forum topic, update the record for this
    // revision.
360
    if (!empty($node->shadow)) {
361 362 363 364 365 366 367 368 369 370 371 372
      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();
     }
373 374
  }
}
375

376
/**
377
 * Implements hook_node_insert().
378
 */
379
function forum_node_insert(EntityInterface $node) {
380 381
  if (_forum_node_check_node_type($node)) {
    if (!empty($node->forum_tid)) {
382 383
      $nid = db_insert('forum')
        ->fields(array(
384
          'tid' => $node->forum_tid,
385 386 387 388
          'vid' => $node->vid,
          'nid' => $node->nid,
        ))
        ->execute();
389 390 391
    }
  }
}
392

393
/**
394
 * Implements hook_node_predelete().
395
 */
396
function forum_node_predelete(EntityInterface $node) {
397
  if (_forum_node_check_node_type($node)) {
398 399 400
    db_delete('forum')
      ->condition('nid', $node->nid)
      ->execute();
401 402 403
    db_delete('forum_index')
      ->condition('nid', $node->nid)
      ->execute();
404
  }
405
}
406

407
/**
408
 * Implements hook_node_load().
409
 */
410
function forum_node_load($nodes) {
411 412
  $node_vids = array();
  foreach ($nodes as $node) {
413
    if (_forum_node_check_node_type($node)) {
414 415 416 417
      $node_vids[] = $node->vid;
    }
  }
  if (!empty($node_vids)) {
418 419 420 421 422
    $query = db_select('forum', 'f');
    $query
      ->fields('f', array('nid', 'tid'))
      ->condition('f.vid', $node_vids);
    $result = $query->execute();
423 424 425
    foreach ($result as $record) {
      $nodes[$record->nid]->forum_tid = $record->tid;
    }
426
  }
427 428
}

429
/**
430
 * Implements hook_node_info().
431
 */
432
function forum_node_info() {
433 434
  return array(
    'forum' => array(
435
      'name' => t('Forum topic'),
436
      'base' => 'forum',
437
      'description' => t('A <em>forum topic</em> starts a new discussion thread within a forum.'),
438 439 440
      'title_label' => t('Subject'),
    )
  );
Dries's avatar
 
Dries committed
441 442
}

443
/**
444
 * Implements hook_permission().
445
 */
446
function forum_permission() {
447
  $perms = array(
448 449 450
    'administer forums' => array(
      'title' => t('Administer forums'),
    ),
451 452
  );
  return $perms;
Dries's avatar
 
Dries committed
453
}
Dries's avatar
 
Dries committed
454

455
/**
456
 * Implements hook_taxonomy_term_delete().
457
 */
458
function forum_taxonomy_term_delete(Term $term) {
459
  // For containers, remove the tid from the forum_containers variable.
460 461
  $config = config('forum.settings');
  $containers = $config->get('containers');
462
  $key = array_search($term->id(), $containers);
463 464 465
  if ($key !== FALSE) {
    unset($containers[$key]);
  }
466
  $config->set('containers', $containers)->save();
467
}
468

469
/**
470
 * Implements hook_comment_publish().
471
 *
472
 * This actually handles the insertion and update of published nodes since
473
 * $comment->save() calls hook_comment_publish() for all published comments.
474 475
 */
function forum_comment_publish($comment) {
476
  _forum_update_forum_index($comment->nid->target_id);
477 478 479
}

/**
480
 * Implements hook_comment_update().
481
 *
482 483
 * The Comment module doesn't call hook_comment_unpublish() when saving
 * individual comments, so we need to check for those here.
484 485
 */
function forum_comment_update($comment) {
486
  // $comment->save() calls hook_comment_publish() for all published comments,
487
  // so we need to handle all other values here.
488
  if (!$comment->status->value) {
489
    _forum_update_forum_index($comment->nid->target_id);
490 491 492 493
  }
}

/**
494
 * Implements hook_comment_unpublish().
495 496
 */
function forum_comment_unpublish($comment) {
497
  _forum_update_forum_index($comment->nid->target_id);
498 499 500
}

/**
501
 * Implements hook_comment_delete().
502 503
 */
function forum_comment_delete($comment) {
504
  _forum_update_forum_index($comment->nid->target_id);
505 506 507
}

/**
508
 * Implements hook_field_storage_pre_insert().
509
 */
510 511
function forum_field_storage_pre_insert(EntityInterface $entity, &$skip_fields) {
  if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
512
    $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
513 514 515 516 517 518 519 520 521 522 523
    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,
      ));
524 525 526 527 528 529
    }
    $query->execute();
  }
}

/**
530
 * Implements hook_field_storage_pre_update().
531
 */
532
function forum_field_storage_pre_update(EntityInterface $entity, &$skip_fields) {
533 534
  $first_call = &drupal_static(__FUNCTION__, array());

535
  if ($entity->entityType() == 'node' && _forum_node_check_node_type($entity)) {
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559

    // 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,
          ));
        }
560
      }
561 562 563 564
      $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);
Dries's avatar
 
Dries committed
565
    }
566 567 568 569 570 571

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

572 573
  }
}
Dries's avatar
 
Dries committed
574

575
/**
576
 * Implements hook_form_BASE_FORM_ID_alter().
577
 */
578
function forum_form_taxonomy_vocabulary_form_alter(&$form, &$form_state, $form_id) {
579
  $vid = config('forum.settings')->get('vocabulary');
580
  $vocabulary = $form_state['controller']->getEntity();
581
  if ($vid == $vocabulary->id()) {
582 583 584 585 586 587 588 589 590 591
    $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;
592 593
    // Do not allow to change a vid of forum's vocabulary.
    $form['vid']['#disabled'] = TRUE;
594 595 596 597
  }
}

/**
598
 * Implements hook_form_FORM_ID_alter() for taxonomy_term_form().
599
 */
600
function forum_form_taxonomy_term_form_alter(&$form, &$form_state, $form_id) {
601
  $vid = config('forum.settings')->get('vocabulary');
602
  if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {
603
    // Hide multiple parents select from forum terms.
604
    $form['relations']['parent']['#access'] = FALSE;
605
  }
606 607 608
}

/**
609
 * Implements hook_form_BASE_FORM_ID_alter() for node_form().
610 611
 */
function forum_form_node_form_alter(&$form, &$form_state, $form_id) {
612 613 614 615 616 617 618 619 620 621 622 623 624 625
  $node = $form_state['controller']->getEntity();
  if (isset($node->taxonomy_forums) && !$node->isNew()) {
    $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);
  }

626
  if (isset($form['taxonomy_forums'])) {
627
    $langcode = $form['taxonomy_forums']['#language'];
628
    // Make the vocabulary required for 'real' forum-nodes.
629 630
    $form['taxonomy_forums'][$langcode]['#required'] = TRUE;
    $form['taxonomy_forums'][$langcode]['#multiple'] = FALSE;
631 632
    if (empty($form['taxonomy_forums'][$langcode]['#default_value'])) {
      // If there is no default forum already selected, try to get the forum
633 634
      // 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).
635
      $requested_forum_id = arg(3);
636
      $form['taxonomy_forums'][$langcode]['#default_value'] = is_numeric($requested_forum_id) ? $requested_forum_id : '';
637
    }
638
  }
639 640
}

641
/**
642 643 644 645
 * Render API callback: Lists nodes based on the element's #query property.
 *
 * This function can be used as a #pre_render callback.
 *
646 647
 * @see \Drupal\forum\Plugin\block\block\NewTopicsBlock::build()
 * @see \Drupal\forum\Plugin\block\block\ActiveTopicsBlock::build()
648
 */
649 650 651
function forum_block_view_pre_render($elements) {
  $result = $elements['#query']->execute();
  if ($node_title_list = node_title_list($result)) {
652
    $elements['forum_list'] = $node_title_list;
653
    $elements['forum_more'] = array('#theme' => 'more_link', '#url' => 'forum', '#title' => t('Read the latest forum topics.'));
654
  }
655
  return $elements;
Dries's avatar
 
Dries committed
656 657
}

Dries's avatar
 
Dries committed
658
/**
659
 * Returns a tree of all forums for a given taxonomy term ID.
Dries's avatar
 
Dries committed
660 661
 *
 * @param $tid
662 663 664
 *   (optional) Taxonomy term ID of the forum. If not given all forums will be
 *   returned.
 *
Dries's avatar
 
Dries committed
665
 * @return
666
 *   A tree of taxonomy objects, with the following additional properties:
667 668 669 670
 *   - 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.
Dries's avatar
 
Dries committed
671
 */
672
function forum_forum_load($tid = NULL) {
673 674
  $cache = &drupal_static(__FUNCTION__, array());

675 676 677 678
  // Return a cached forum tree if available.
  if (!isset($tid)) {
    $tid = 0;
  }
679 680 681
  if (isset($cache[$tid])) {
    return $cache[$tid];
  }
682

683 684
  $config = config('forum.settings');
  $vid = $config->get('vocabulary');
685 686 687 688

  // Load and validate the parent term.
  if ($tid) {
    $forum_term = taxonomy_term_load($tid);
689
    if (!$forum_term || ($forum_term->bundle() != $vid)) {
690 691 692
      return $cache[$tid] = FALSE;
    }
  }
693
  // If $tid is 0, create an empty entity to hold the child terms.
694
  elseif ($tid === 0) {
695
    $forum_term = entity_create('taxonomy_term', array(
696
      'tid' => 0,
697
      'vid' => $vid,
698
    ));
699 700 701
  }

  // Determine if the requested term is a container.
702
  if (!$forum_term->id() || in_array($forum_term->id(), $config->get('containers'))) {
703 704 705 706
    $forum_term->container = 1;
  }

  // Load parent terms.
707
  $forum_term->parents = taxonomy_term_load_parents_all($forum_term->id());
708 709 710

  // Load the tree below.
  $forums = array();
711
  $_forums = taxonomy_get_tree($vid, $tid, NULL, TRUE);
Dries's avatar
 
Dries committed
712

Dries's avatar
 
Dries committed
713
  if (count($_forums)) {
714
    $query = db_select('node_field_data', 'n');
715
    $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid');
716
    $query->join('forum', 'f', 'n.vid = f.vid');
717 718 719
    $query->addExpression('COUNT(n.nid)', 'topic_count');
    $query->addExpression('SUM(ncs.comment_count)', 'comment_count');
    $counts = $query
720
      ->fields('f', array('tid'))
721
      ->condition('n.status', 1)
722 723 724
      // @todo This should be actually filtering on the desired node status
      //   field language and just fall back to the default language.
      ->condition('n.default_langcode', 1)
725 726 727 728
      ->groupBy('tid')
      ->addTag('node_access')
      ->execute()
      ->fetchAllAssoc('tid');
Dries's avatar
 
Dries committed
729
  }
Dries's avatar
 
Dries committed
730

Dries's avatar
 
Dries committed
731
  foreach ($_forums as $forum) {
732
    // Determine if the child term is a container.
733
    if (in_array($forum->id(), $config->get('containers'))) {
Dries's avatar
 
Dries committed
734 735
      $forum->container = 1;
    }
Dries's avatar
 
Dries committed
736