forum.module 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. <?php
  2. /**
  3. * @file
  4. * Provides discussion forums.
  5. */
  6. use Drupal\comment\CommentInterface;
  7. use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
  8. use Drupal\Core\Entity\EntityInterface;
  9. use Drupal\Core\Entity\EntityTypeInterface;
  10. use Drupal\Core\Link;
  11. use Drupal\Core\Url;
  12. use Drupal\Core\Form\FormStateInterface;
  13. use Drupal\Core\Routing\RouteMatchInterface;
  14. use Drupal\taxonomy\VocabularyInterface;
  15. use Drupal\user\Entity\User;
  16. /**
  17. * Implements hook_help().
  18. */
  19. function forum_help($route_name, RouteMatchInterface $route_match) {
  20. switch ($route_name) {
  21. case 'help.page.forum':
  22. $output = '';
  23. $output .= '<h3>' . t('About') . '</h3>';
  24. $output .= '<p>' . t('The Forum module lets you create threaded discussion forums with functionality similar to other message board systems. In a forum, users post topics and threads in nested hierarchies, allowing discussions to be categorized and grouped.') . '</p>';
  25. $output .= '<p>' . t('The Forum module adds and uses a content type called <em>Forum topic</em>. For background information on content types, see the <a href=":node_help">Node module help page</a>.', [':node_help' => Url::fromRoute('help.page', ['name' => 'node'])->toString()]) . '</p>';
  26. $output .= '<p>' . t('A forum is represented by a hierarchical structure, consisting of:');
  27. $output .= '<ul>';
  28. $output .= '<li>' . t('<em>Forums</em> (for example, <em>Recipes for cooking vegetables</em>)') . '</li>';
  29. $output .= '<li>' . t('<em>Forum topics</em> submitted by users (for example, <em>How to cook potatoes</em>), which start discussions.') . '</li>';
  30. $output .= '<li>' . t('Threaded <em>comments</em> submitted by users (for example, <em>You wash the potatoes first and then...</em>).') . '</li>';
  31. $output .= '<li>' . t('Optional <em>containers</em>, used to group similar forums. Forums can be placed inside containers, and vice versa.') . '</li>';
  32. $output .= '</ul>';
  33. $output .= '</p>';
  34. $output .= '<p>' . t('For more information, see the <a href=":forum">online documentation for the Forum module</a>.', [':forum' => 'https://www.drupal.org/documentation/modules/forum']) . '</p>';
  35. $output .= '<h3>' . t('Uses') . '</h3>';
  36. $output .= '<dl>';
  37. $output .= '<dt>' . t('Setting up the forum structure') . '</dt>';
  38. $output .= '<dd>' . t('Visit the <a href=":forums">Forums page</a> to set up containers and forums to hold your discussion topics.', [':forums' => Url::fromRoute('forum.overview')->toString()]) . '</dd>';
  39. $output .= '<dt>' . t('Starting a discussion') . '</dt>';
  40. $output .= '<dd>' . t('The <a href=":create-topic">Forum topic</a> link on the <a href=":content-add">Add content</a> page creates the first post of a new threaded discussion, or thread.', [':create-topic' => Url::fromRoute('node.add', ['node_type' => 'forum'])->toString(), ':content-add' => Url::fromRoute('node.add_page')->toString()]) . '</dd>';
  41. $output .= '<dt>' . t('Navigating in the forum') . '</dt>';
  42. $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>.', [':forums' => Url::fromRoute('forum.index')->toString()]) . '</dd>';
  43. $output .= '<dt>' . t('Moving forum topics') . '</dt>';
  44. $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>';
  45. $output .= '<dt>' . t('Locking and disabling comments') . '</dt>';
  46. $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>';
  47. $output .= '</dl>';
  48. return $output;
  49. case 'forum.overview':
  50. $output = '<p>' . t('Forums contain forum topics. Use containers to group related forums.') . '</p>';
  51. $more_help_link = [
  52. '#type' => 'link',
  53. '#url' => Url::fromRoute('help.page', ['name' => 'forum']),
  54. '#title' => t('More help'),
  55. '#attributes' => [
  56. 'class' => ['icon-help'],
  57. ],
  58. ];
  59. $container = [
  60. '#theme' => 'container',
  61. '#children' => $more_help_link,
  62. '#attributes' => [
  63. 'class' => ['more-link'],
  64. ],
  65. ];
  66. $output .= \Drupal::service('renderer')->renderPlain($container);
  67. return $output;
  68. case 'forum.add_container':
  69. return '<p>' . t('Use containers to group related forums.') . '</p>';
  70. case 'forum.add_forum':
  71. return '<p>' . t('A forum holds related forum topics.') . '</p>';
  72. case 'forum.settings':
  73. return '<p>' . t('Adjust the display of your forum topics. Organize the forums on the <a href=":forum-structure">forum structure page</a>.', [':forum-structure' => Url::fromRoute('forum.overview')->toString()]) . '</p>';
  74. }
  75. }
  76. /**
  77. * Implements hook_theme().
  78. */
  79. function forum_theme() {
  80. return [
  81. 'forums' => [
  82. 'variables' => ['forums' => [], 'topics' => [], 'topics_pager' => [], 'parents' => NULL, 'term' => NULL, 'sortby' => NULL, 'forum_per_page' => NULL, 'header' => []],
  83. ],
  84. 'forum_list' => [
  85. 'variables' => ['forums' => NULL, 'parents' => NULL, 'tid' => NULL],
  86. ],
  87. 'forum_icon' => [
  88. 'variables' => ['new_posts' => NULL, 'num_posts' => 0, 'comment_mode' => 0, 'sticky' => 0, 'first_new' => FALSE],
  89. ],
  90. 'forum_submitted' => [
  91. 'variables' => ['topic' => NULL],
  92. ],
  93. ];
  94. }
  95. /**
  96. * Implements hook_entity_type_build().
  97. */
  98. function forum_entity_type_build(array &$entity_types) {
  99. /** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  100. // Register forum specific forms.
  101. $entity_types['taxonomy_term']
  102. ->setFormClass('forum', 'Drupal\forum\Form\ForumForm')
  103. ->setFormClass('container', 'Drupal\forum\Form\ContainerForm')
  104. ->setLinkTemplate('forum-edit-container-form', '/admin/structure/forum/edit/container/{taxonomy_term}')
  105. ->setLinkTemplate('forum-delete-form', '/admin/structure/forum/delete/forum/{taxonomy_term}')
  106. ->setLinkTemplate('forum-edit-form', '/admin/structure/forum/edit/forum/{taxonomy_term}');
  107. }
  108. /**
  109. * Implements hook_entity_bundle_info_alter().
  110. */
  111. function forum_entity_bundle_info_alter(&$bundles) {
  112. // Take over URI construction for taxonomy terms that are forums.
  113. if ($vid = \Drupal::config('forum.settings')->get('vocabulary')) {
  114. if (isset($bundles['taxonomy_term'][$vid])) {
  115. $bundles['taxonomy_term'][$vid]['uri_callback'] = 'forum_uri';
  116. }
  117. }
  118. }
  119. /**
  120. * Entity URI callback used in forum_entity_bundle_info_alter().
  121. */
  122. function forum_uri($forum) {
  123. return Url::fromRoute('forum.page', ['taxonomy_term' => $forum->id()]);
  124. }
  125. /**
  126. * Implements hook_entity_bundle_field_info_alter().
  127. */
  128. function forum_entity_bundle_field_info_alter(&$fields, EntityTypeInterface $entity_type, $bundle) {
  129. if ($entity_type->id() == 'node' && !empty($fields['taxonomy_forums'])) {
  130. $fields['taxonomy_forums']->addConstraint('ForumLeaf', []);
  131. }
  132. }
  133. /**
  134. * Implements hook_ENTITY_TYPE_presave() for node entities.
  135. *
  136. * Assigns the forum taxonomy when adding a topic from within a forum.
  137. */
  138. function forum_node_presave(EntityInterface $node) {
  139. if (\Drupal::service('forum_manager')->checkNodeType($node)) {
  140. // Make sure all fields are set properly:
  141. $node->icon = !empty($node->icon) ? $node->icon : '';
  142. if (!$node->taxonomy_forums->isEmpty()) {
  143. $node->forum_tid = $node->taxonomy_forums->target_id;
  144. // Only do a shadow copy check if this is not a new node.
  145. if (!$node->isNew()) {
  146. $old_tid = \Drupal::service('forum.index_storage')->getOriginalTermId($node);
  147. if ($old_tid && isset($node->forum_tid) && ($node->forum_tid != $old_tid) && !empty($node->shadow)) {
  148. // A shadow copy needs to be created. Retain new term and add old term.
  149. $node->taxonomy_forums[count($node->taxonomy_forums)] = ['target_id' => $old_tid];
  150. }
  151. }
  152. }
  153. }
  154. }
  155. /**
  156. * Implements hook_ENTITY_TYPE_update() for node entities.
  157. */
  158. function forum_node_update(EntityInterface $node) {
  159. if (\Drupal::service('forum_manager')->checkNodeType($node)) {
  160. // If this is not a new revision and does exist, update the forum record,
  161. // otherwise insert a new one.
  162. /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
  163. $forum_index_storage = \Drupal::service('forum.index_storage');
  164. if ($node->getRevisionId() == $node->original->getRevisionId() && $forum_index_storage->getOriginalTermId($node)) {
  165. if (!empty($node->forum_tid)) {
  166. $forum_index_storage->update($node);
  167. }
  168. // The node is removed from the forum.
  169. else {
  170. $forum_index_storage->delete($node);
  171. }
  172. }
  173. else {
  174. if (!empty($node->forum_tid)) {
  175. $forum_index_storage->create($node);
  176. }
  177. }
  178. // If the node has a shadow forum topic, update the record for this
  179. // revision.
  180. if (!empty($node->shadow)) {
  181. $forum_index_storage->deleteRevision($node);
  182. $forum_index_storage->create($node);
  183. }
  184. // If the node is published, update the forum index.
  185. if ($node->isPublished()) {
  186. $forum_index_storage->deleteIndex($node);
  187. $forum_index_storage->createIndex($node);
  188. }
  189. // When a forum node is unpublished, remove it from the forum_index table.
  190. else {
  191. $forum_index_storage->deleteIndex($node);
  192. }
  193. }
  194. }
  195. /**
  196. * Implements hook_ENTITY_TYPE_insert() for node entities.
  197. */
  198. function forum_node_insert(EntityInterface $node) {
  199. if (\Drupal::service('forum_manager')->checkNodeType($node)) {
  200. /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
  201. $forum_index_storage = \Drupal::service('forum.index_storage');
  202. if (!empty($node->forum_tid)) {
  203. $forum_index_storage->create($node);
  204. }
  205. // If the node is published, update the forum index.
  206. if ($node->isPublished()) {
  207. $forum_index_storage->createIndex($node);
  208. }
  209. }
  210. }
  211. /**
  212. * Implements hook_ENTITY_TYPE_predelete() for node entities.
  213. */
  214. function forum_node_predelete(EntityInterface $node) {
  215. if (\Drupal::service('forum_manager')->checkNodeType($node)) {
  216. /** @var \Drupal\forum\ForumIndexStorageInterface $forum_index_storage */
  217. $forum_index_storage = \Drupal::service('forum.index_storage');
  218. $forum_index_storage->delete($node);
  219. $forum_index_storage->deleteIndex($node);
  220. }
  221. }
  222. /**
  223. * Implements hook_ENTITY_TYPE_storage_load() for node entities.
  224. */
  225. function forum_node_storage_load($nodes) {
  226. $node_vids = [];
  227. foreach ($nodes as $node) {
  228. if (\Drupal::service('forum_manager')->checkNodeType($node)) {
  229. $node_vids[] = $node->getRevisionId();
  230. }
  231. }
  232. if (!empty($node_vids)) {
  233. $result = \Drupal::service('forum.index_storage')->read($node_vids);
  234. foreach ($result as $record) {
  235. $nodes[$record->nid]->forum_tid = $record->tid;
  236. }
  237. }
  238. }
  239. /**
  240. * Implements hook_ENTITY_TYPE_update() for comment entities.
  241. */
  242. function forum_comment_update(CommentInterface $comment) {
  243. if ($comment->getCommentedEntityTypeId() == 'node') {
  244. \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
  245. }
  246. }
  247. /**
  248. * Implements hook_ENTITY_TYPE_insert() for comment entities.
  249. */
  250. function forum_comment_insert(CommentInterface $comment) {
  251. if ($comment->getCommentedEntityTypeId() == 'node') {
  252. \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
  253. }
  254. }
  255. /**
  256. * Implements hook_ENTITY_TYPE_delete() for comment entities.
  257. */
  258. function forum_comment_delete(CommentInterface $comment) {
  259. if ($comment->getCommentedEntityTypeId() == 'node') {
  260. \Drupal::service('forum.index_storage')->updateIndex($comment->getCommentedEntity());
  261. }
  262. }
  263. /**
  264. * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\taxonomy\VocabularyForm.
  265. */
  266. function forum_form_taxonomy_vocabulary_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  267. $vid = \Drupal::config('forum.settings')->get('vocabulary');
  268. $vocabulary = $form_state->getFormObject()->getEntity();
  269. if ($vid == $vocabulary->id()) {
  270. $form['help_forum_vocab'] = [
  271. '#markup' => t('This is the designated forum vocabulary. Some of the normal vocabulary options have been removed.'),
  272. '#weight' => -1,
  273. ];
  274. // Forum's vocabulary always has single hierarchy. Forums and containers
  275. // have only one parent or no parent for root items. By default this value
  276. // is 0.
  277. $form['hierarchy']['#value'] = VocabularyInterface::HIERARCHY_SINGLE;
  278. // Do not allow to delete forum's vocabulary.
  279. $form['actions']['delete']['#access'] = FALSE;
  280. // Do not allow to change a vid of forum's vocabulary.
  281. $form['vid']['#disabled'] = TRUE;
  282. }
  283. }
  284. /**
  285. * Implements hook_form_FORM_ID_alter() for \Drupal\taxonomy\TermForm.
  286. */
  287. function forum_form_taxonomy_term_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  288. $vid = \Drupal::config('forum.settings')->get('vocabulary');
  289. if (isset($form['vid']['#value']) && $form['vid']['#value'] == $vid) {
  290. // Hide multiple parents select from forum terms.
  291. $form['relations']['parent']['#access'] = FALSE;
  292. }
  293. }
  294. /**
  295. * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
  296. */
  297. function forum_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  298. $node = $form_state->getFormObject()->getEntity();
  299. if (isset($node->taxonomy_forums) && !$node->isNew()) {
  300. $forum_terms = $node->taxonomy_forums;
  301. // If editing, give option to leave shadows.
  302. $shadow = (count($forum_terms) > 1);
  303. $form['shadow'] = [
  304. '#type' => 'checkbox',
  305. '#title' => t('Leave shadow copy'),
  306. '#default_value' => $shadow,
  307. '#description' => t('If you move this topic, you can leave a link in the old forum to the new forum.'),
  308. ];
  309. $form['forum_tid'] = ['#type' => 'value', '#value' => $node->forum_tid];
  310. }
  311. if (isset($form['taxonomy_forums'])) {
  312. $widget =& $form['taxonomy_forums']['widget'];
  313. $widget['#multiple'] = FALSE;
  314. if (empty($widget['#default_value'])) {
  315. // If there is no default forum already selected, try to get the forum
  316. // ID from the URL (e.g., if we are on a page like node/add/forum/2, we
  317. // expect "2" to be the ID of the forum that was requested).
  318. $requested_forum_id = \Drupal::request()->query->get('forum_id');
  319. $widget['#default_value'] = is_numeric($requested_forum_id) ? $requested_forum_id : '';
  320. }
  321. }
  322. }
  323. /**
  324. * Implements hook_preprocess_HOOK() for block templates.
  325. */
  326. function forum_preprocess_block(&$variables) {
  327. if ($variables['configuration']['provider'] == 'forum') {
  328. $variables['attributes']['role'] = 'navigation';
  329. }
  330. }
  331. /**
  332. * Implements hook_theme_suggestions_HOOK().
  333. */
  334. function forum_theme_suggestions_forums(array $variables) {
  335. $suggestions = [];
  336. $tid = $variables['term']->id();
  337. // Provide separate template suggestions based on what's being output. Topic
  338. // ID is also accounted for. Check both variables to be safe then the inverse.
  339. // Forums with topic IDs take precedence.
  340. if ($variables['forums'] && !$variables['topics']) {
  341. $suggestions[] = 'forums__containers';
  342. $suggestions[] = 'forums__' . $tid;
  343. $suggestions[] = 'forums__containers__' . $tid;
  344. }
  345. elseif (!$variables['forums'] && $variables['topics']) {
  346. $suggestions[] = 'forums__topics';
  347. $suggestions[] = 'forums__' . $tid;
  348. $suggestions[] = 'forums__topics__' . $tid;
  349. }
  350. else {
  351. $suggestions[] = 'forums__' . $tid;
  352. }
  353. return $suggestions;
  354. }
  355. /**
  356. * Prepares variables for forums templates.
  357. *
  358. * Default template: forums.html.twig.
  359. *
  360. * @param array $variables
  361. * An array containing the following elements:
  362. * - forums: An array of all forum objects to display for the given taxonomy
  363. * term ID. If tid = 0 then all the top-level forums are displayed.
  364. * - topics: An array of all the topics in the current forum.
  365. * - parents: An array of taxonomy term objects that are ancestors of the
  366. * current term ID.
  367. * - term: Taxonomy term of the current forum.
  368. * - sortby: One of the following integers indicating the sort criteria:
  369. * - 1: Date - newest first.
  370. * - 2: Date - oldest first.
  371. * - 3: Posts with the most comments first.
  372. * - 4: Posts with the least comments first.
  373. * - forum_per_page: The maximum number of topics to display per page.
  374. */
  375. function template_preprocess_forums(&$variables) {
  376. $variables['tid'] = $variables['term']->id();
  377. if ($variables['forums_defined'] = count($variables['forums']) || count($variables['parents'])) {
  378. if (!empty($variables['forums'])) {
  379. $variables['forums'] = [
  380. '#theme' => 'forum_list',
  381. '#forums' => $variables['forums'],
  382. '#parents' => $variables['parents'],
  383. '#tid' => $variables['tid'],
  384. ];
  385. }
  386. if ($variables['term'] && empty($variables['term']->forum_container->value) && !empty($variables['topics'])) {
  387. $forum_topic_list_header = $variables['header'];
  388. $table = [
  389. '#theme' => 'table__forum_topic_list',
  390. '#responsive' => FALSE,
  391. '#attributes' => ['id' => 'forum-topic-' . $variables['tid']],
  392. '#header' => [],
  393. '#rows' => [],
  394. ];
  395. if (!empty($forum_topic_list_header)) {
  396. $table['#header'] = $forum_topic_list_header;
  397. }
  398. /** @var \Drupal\node\NodeInterface $topic */
  399. foreach ($variables['topics'] as $id => $topic) {
  400. $variables['topics'][$id]->icon = [
  401. '#theme' => 'forum_icon',
  402. '#new_posts' => $topic->new,
  403. '#num_posts' => $topic->comment_count,
  404. '#comment_mode' => $topic->comment_mode,
  405. '#sticky' => $topic->isSticky(),
  406. '#first_new' => $topic->first_new,
  407. ];
  408. // We keep the actual tid in forum table, if it's different from the
  409. // current tid then it means the topic appears in two forums, one of
  410. // them is a shadow copy.
  411. if ($variables['tid'] != $topic->forum_tid) {
  412. $variables['topics'][$id]->moved = TRUE;
  413. $variables['topics'][$id]->title = $topic->getTitle();
  414. $variables['topics'][$id]->message = Link::fromTextAndUrl(t('This topic has been moved'), Url::fromRoute('forum.page', ['taxonomy_term' => $topic->forum_tid]))->toString();
  415. }
  416. else {
  417. $variables['topics'][$id]->moved = FALSE;
  418. $variables['topics'][$id]->title_link = Link::fromTextAndUrl($topic->getTitle(), $topic->toUrl())->toString();
  419. $variables['topics'][$id]->message = '';
  420. }
  421. $forum_submitted = [
  422. '#theme' => 'forum_submitted',
  423. '#topic' => (object) [
  424. 'uid' => $topic->getOwnerId(),
  425. 'name' => $topic->getOwner()->getDisplayName(),
  426. 'created' => $topic->getCreatedTime(),
  427. ],
  428. ];
  429. $variables['topics'][$id]->submitted = \Drupal::service('renderer')->render($forum_submitted);
  430. $forum_submitted = [
  431. '#theme' => 'forum_submitted',
  432. '#topic' => isset($topic->last_reply) ? $topic->last_reply : NULL,
  433. ];
  434. $variables['topics'][$id]->last_reply = \Drupal::service('renderer')->render($forum_submitted);
  435. $variables['topics'][$id]->new_text = '';
  436. $variables['topics'][$id]->new_url = '';
  437. if ($topic->new_replies) {
  438. $page_number = \Drupal::entityTypeManager()->getStorage('comment')
  439. ->getNewCommentPageNumber($topic->comment_count, $topic->new_replies, $topic, 'comment_forum');
  440. $query = $page_number ? ['page' => $page_number] : NULL;
  441. $variables['topics'][$id]->new_text = \Drupal::translation()->formatPlural($topic->new_replies, '1 new post<span class="visually-hidden"> in topic %title</span>', '@count new posts<span class="visually-hidden"> in topic %title</span>', ['%title' => $variables['topics'][$id]->label()]);
  442. $variables['topics'][$id]->new_url = Url::fromRoute('entity.node.canonical', ['node' => $topic->id()], ['query' => $query, 'fragment' => 'new'])->toString();
  443. }
  444. // Build table rows from topics.
  445. $row = [];
  446. $row[] = [
  447. 'data' => [
  448. $topic->icon,
  449. [
  450. '#markup' => '<div class="forum__title"><div>' . $topic->title_link . '</div><div>' . $topic->submitted . '</div></div>',
  451. ],
  452. ],
  453. 'class' => ['forum__topic'],
  454. ];
  455. if ($topic->moved) {
  456. $row[] = [
  457. 'data' => $topic->message,
  458. 'colspan' => '2',
  459. ];
  460. }
  461. else {
  462. $new_replies = '';
  463. if ($topic->new_replies) {
  464. $new_replies = '<br /><a href="' . $topic->new_url . '">' . $topic->new_text . '</a>';
  465. }
  466. $row[] = [
  467. 'data' => [
  468. [
  469. '#prefix' => $topic->comment_count,
  470. '#markup' => $new_replies,
  471. ],
  472. ],
  473. 'class' => ['forum__replies'],
  474. ];
  475. $row[] = [
  476. 'data' => $topic->last_reply,
  477. 'class' => ['forum__last-reply'],
  478. ];
  479. }
  480. $table['#rows'][] = $row;
  481. }
  482. $variables['topics_original'] = $variables['topics'];
  483. $variables['topics'] = $table;
  484. $variables['topics_pager'] = [
  485. '#type' => 'pager',
  486. ];
  487. }
  488. }
  489. }
  490. /**
  491. * Prepares variables for forum list templates.
  492. *
  493. * Default template: forum-list.html.twig.
  494. *
  495. * @param array $variables
  496. * An array containing the following elements:
  497. * - forums: An array of all forum objects to display for the given taxonomy
  498. * term ID. If tid = 0 then all the top-level forums are displayed.
  499. * - parents: An array of taxonomy term objects that are ancestors of the
  500. * current term ID.
  501. * - tid: Taxonomy term ID of the current forum.
  502. */
  503. function template_preprocess_forum_list(&$variables) {
  504. $user = \Drupal::currentUser();
  505. $row = 0;
  506. // Sanitize each forum so that the template can safely print the data.
  507. foreach ($variables['forums'] as $id => $forum) {
  508. $variables['forums'][$id]->description = ['#markup' => $forum->description->value];
  509. $variables['forums'][$id]->link = forum_uri($forum);
  510. $variables['forums'][$id]->name = $forum->label();
  511. $variables['forums'][$id]->is_container = !empty($forum->forum_container->value);
  512. $variables['forums'][$id]->zebra = $row % 2 == 0 ? 'odd' : 'even';
  513. $row++;
  514. $variables['forums'][$id]->new_text = '';
  515. $variables['forums'][$id]->new_url = '';
  516. $variables['forums'][$id]->new_topics = 0;
  517. $variables['forums'][$id]->old_topics = $forum->num_topics;
  518. $variables['forums'][$id]->icon_class = 'default';
  519. $variables['forums'][$id]->icon_title = t('No new posts');
  520. if ($user->isAuthenticated()) {
  521. $variables['forums'][$id]->new_topics = \Drupal::service('forum_manager')->unreadTopics($forum->id(), $user->id());
  522. if ($variables['forums'][$id]->new_topics) {
  523. $variables['forums'][$id]->new_text = \Drupal::translation()->formatPlural($variables['forums'][$id]->new_topics, '1 new post<span class="visually-hidden"> in forum %title</span>', '@count new posts<span class="visually-hidden"> in forum %title</span>', ['%title' => $variables['forums'][$id]->label()]);
  524. $variables['forums'][$id]->new_url = Url::fromRoute('forum.page', ['taxonomy_term' => $forum->id()], ['fragment' => 'new'])->toString();
  525. $variables['forums'][$id]->icon_class = 'new';
  526. $variables['forums'][$id]->icon_title = t('New posts');
  527. }
  528. $variables['forums'][$id]->old_topics = $forum->num_topics - $variables['forums'][$id]->new_topics;
  529. }
  530. $forum_submitted = ['#theme' => 'forum_submitted', '#topic' => $forum->last_post];
  531. $variables['forums'][$id]->last_reply = \Drupal::service('renderer')->render($forum_submitted);
  532. }
  533. $variables['pager'] = [
  534. '#type' => 'pager',
  535. ];
  536. // Give meaning to $tid for themers. $tid actually stands for term ID.
  537. $variables['forum_id'] = $variables['tid'];
  538. unset($variables['tid']);
  539. }
  540. /**
  541. * Prepares variables for forum icon templates.
  542. *
  543. * Default template: forum-icon.html.twig.
  544. *
  545. * @param array $variables
  546. * An array containing the following elements:
  547. * - new_posts: Indicates whether or not the topic contains new posts.
  548. * - num_posts: The total number of posts in all topics.
  549. * - comment_mode: An integer indicating whether comments are open, closed,
  550. * or hidden.
  551. * - sticky: Indicates whether the topic is sticky.
  552. * - first_new: Indicates whether this is the first topic with new posts.
  553. */
  554. function template_preprocess_forum_icon(&$variables) {
  555. $variables['hot_threshold'] = \Drupal::config('forum.settings')->get('topics.hot_threshold');
  556. if ($variables['num_posts'] > $variables['hot_threshold']) {
  557. $variables['icon_status'] = $variables['new_posts'] ? 'hot-new' : 'hot';
  558. $variables['icon_title'] = $variables['new_posts'] ? t('Hot topic, new comments') : t('Hot topic');
  559. }
  560. else {
  561. $variables['icon_status'] = $variables['new_posts'] ? 'new' : 'default';
  562. $variables['icon_title'] = $variables['new_posts'] ? t('New comments') : t('Normal topic');
  563. }
  564. if ($variables['comment_mode'] == CommentItemInterface::CLOSED || $variables['comment_mode'] == CommentItemInterface::HIDDEN) {
  565. $variables['icon_status'] = 'closed';
  566. $variables['icon_title'] = t('Closed topic');
  567. }
  568. if ($variables['sticky'] == 1) {
  569. $variables['icon_status'] = 'sticky';
  570. $variables['icon_title'] = t('Sticky topic');
  571. }
  572. $variables['attributes']['title'] = $variables['icon_title'];
  573. }
  574. /**
  575. * Prepares variables for forum submission information templates.
  576. *
  577. * The submission information will be displayed in the forum list and topic
  578. * list.
  579. *
  580. * Default template: forum-submitted.html.twig.
  581. *
  582. * @param array $variables
  583. * An array containing the following elements:
  584. * - topic: The topic object.
  585. */
  586. function template_preprocess_forum_submitted(&$variables) {
  587. $variables['author'] = '';
  588. if (isset($variables['topic']->uid)) {
  589. $username = ['#theme' => 'username', '#account' => User::load($variables['topic']->uid)];
  590. $variables['author'] = \Drupal::service('renderer')->render($username);
  591. }
  592. $variables['time'] = isset($variables['topic']->created) ? \Drupal::service('date.formatter')->formatTimeDiffSince($variables['topic']->created) : '';
  593. }