advanced_forum.module

Tracking 6.x-2.x branch
  1. drupal
    1. 6 contributions/advanced_forum/advanced_forum.module

Enables the look and feel of other popular forum software.

Functions & methods

NameDescription
advanced_forum_allowed_node_typesReturn an array of node types allowed in a given vocabulary or term ID.
advanced_forum_commentImplementation of hook_comment().
advanced_forum_create_topic_pagerCreates a pager to place on each multi-page topic of the topic listing page.
advanced_forum_ctools_plugin_api
advanced_forum_ctools_plugin_directoryTell CTools about what plugins we support.
advanced_forum_first_new_commentReturns the ID of the first unread comment.
advanced_forum_first_new_post_linkReturns a link directly to the first new post in a topic.
advanced_forum_form_alterImplementation of hook_form_alter().
advanced_forum_forum_toolsCreate a drop down list of forum actions.
advanced_forum_forum_topic_list_sortDisplay the "sort" widget. This is a specially hacked widget that only works with tablesorting. Tablesorting MUST be on for these widgets to appear.
advanced_forum_forum_topic_list_sort_form
advanced_forum_get_last_pageReturns the page number of the last page starting at 0 like the pager does.
advanced_forum_get_reply_link
advanced_forum_last_post_in_topicGet the comment id of the last post in a topic.
advanced_forum_last_post_linkGet a link to the last post in a topic.
advanced_forum_linkImplementation of hook_link().
advanced_forum_link_alterImplementation of hook_link_alter().
advanced_forum_menuImplementation of hook_menu().
advanced_forum_menu_alterImplementation of hook_menu_alter().
advanced_forum_nodeapiImplementation of hook_nodeapi().
advanced_forum_node_type_create_listGenerate a list of node creation links for a forum.
advanced_forum_page_first_newGet the page number with the first new post. This is simply a wrapper to either call the comment module version or the nodecomment module version.
advanced_forum_permImplementation of hook_perm().
advanced_forum_post_positionReturns the display position of a given reply post ID on a given node.
advanced_forum_reply_num_newGet the number of new posts on a topic. This is simply a wrapper to either call the comment module version or the nodecomment module version.
advanced_forum_statistics_latest_usersReturn the newest X active (not blocked) users, linked to their profiles.
advanced_forum_statistics_online_usersReturn an array of online usernames, linked to their profiles.
advanced_forum_statistics_repliesCounts total amount of replies. Initial posts are added to this total in the calling function.
advanced_forum_statistics_topicsCount total amount of forum threads.
advanced_forum_statistics_usersCount total amount of active users.
advanced_forum_themeImplementation of hook_theme().
advanced_forum_theme_registry_alterImplementation of hook_theme_registry_alter().
advanced_forum_type_is_in_forumReturn whether a given node type is allowed in the whole forum or given forum.
advanced_forum_unread_replies_in_forumCalculates the number of unread replies for each forum and returns the count for the requested forum.
advanced_forum_user_can_reply
advanced_forum_views_api
advanced_forum_views_post_renderPost render a view and replace any advanced forum tokens.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Enables the look and feel of other popular forum software.
  5. */
  6. // DRUPAL HOOKS **************************************************************/
  7. /**
  8. * Implementation of hook_perm().
  9. */
  10. function advanced_forum_perm() {
  11. return array(
  12. 'administer advanced forum',
  13. 'view forum statistics',
  14. 'view last edited notice',
  15. );
  16. }
  17. /**
  18. * Implementation of hook_menu().
  19. */
  20. function advanced_forum_menu() {
  21. $items['admin/settings/advanced-forum'] = array(
  22. 'access arguments' => array('administer advanced forum'),
  23. 'description' => 'Configure Advanced Forum with these settings.',
  24. 'page arguments' => array('advanced_forum_settings_page'),
  25. 'page callback' => 'drupal_get_form',
  26. 'title' => 'Advanced Forum',
  27. 'file' => 'includes/settings.inc',
  28. );
  29. $items['forum/markasread'] = array(
  30. 'access callback' => 'advanced_forum_markasread_access',
  31. 'page callback' => 'advanced_forum_markasread',
  32. 'type' => MENU_CALLBACK,
  33. 'file' => 'includes/mark-read.inc',
  34. );
  35. if (variable_get('advanced_forum_add_local_task', TRUE)) {
  36. $items['forum/view'] = array(
  37. 'title' => 'View Forums',
  38. 'page callback' => 'advanced_forum_page',
  39. 'type' => MENU_DEFAULT_LOCAL_TASK,
  40. 'weight' => -100,
  41. 'file' => 'includes/core-overrides.inc'
  42. );
  43. }
  44. return $items;
  45. }
  46. /**
  47. * Implementation of hook_menu_alter().
  48. */
  49. function advanced_forum_menu_alter(&$callbacks) {
  50. // Take over the forum page creation so we can add more information.
  51. $callbacks['forum']['page callback'] = 'advanced_forum_page';
  52. // Turn 'forum' into a normal menu item so it appears in navigation.
  53. $callbacks['forum']['type'] = MENU_NORMAL_ITEM;
  54. }
  55. /**
  56. * Implementation of hook_theme().
  57. */
  58. function advanced_forum_theme() {
  59. $items['advanced_forum_l'] = array(
  60. 'arguments' => array(
  61. 'text' => NULL,
  62. 'path' => NULL,
  63. 'options' => array(),
  64. 'button_class' => NULL,
  65. )
  66. );
  67. $items['advanced_forum_statistics'] = array(
  68. 'template' => 'advanced_forum-statistics',
  69. );
  70. $items['advanced_forum_topic_legend'] = array(
  71. 'template' => 'advanced_forum-topic-legend',
  72. );
  73. $items['advanced_forum_topic_header'] = array(
  74. 'template' => 'advanced_forum-topic-header',
  75. 'arguments' => array(
  76. 'node' => NULL,
  77. 'comment_count' => NULL,
  78. )
  79. );
  80. $items['advanced_forum_active_poster'] = array(
  81. 'template' => 'advanced_forum-active-poster',
  82. 'arguments' => array(
  83. 'forum' => NULL,
  84. 'account' => NULL,
  85. 'posts' => NULL,
  86. 'topics' => NULL,
  87. 'last_post' => NULL,
  88. )
  89. );
  90. $items['advanced_forum_forum_legend'] = array(
  91. 'template' => 'advanced_forum-forum-legend',
  92. );
  93. $items['advanced_forum_user_picture'] = array(
  94. 'arguments' => array(
  95. 'account' => NULL,
  96. )
  97. );
  98. $items['advanced_forum_reply_link'] = array(
  99. 'arguments' => array(
  100. 'node' => NULL,
  101. )
  102. );
  103. $items['advanced_forum_topic_pager'] = array(
  104. 'arguments' => array(
  105. 'pagecount' => NULL,
  106. 'topic' => NULL,
  107. )
  108. );
  109. $items['advanced_forum_shadow_topic'] = array(
  110. 'arguments' => array(
  111. 'title' => NULL,
  112. 'nid' => NULL,
  113. 'new_forum' => NULL,
  114. )
  115. );
  116. $items['advanced_forum_subforum_list'] = array(
  117. 'arguments' => array(
  118. 'subforum_list' => NULL,
  119. )
  120. );
  121. $items['advanced_forum_subcontainer_list'] = array(
  122. 'arguments' => array(
  123. 'subcontainer_list' => NULL,
  124. )
  125. );
  126. $items['advanced_forum_simple_author_pane'] = array(
  127. 'arguments' => array(
  128. 'context' => NULL,
  129. )
  130. );
  131. $items['advanced_forum_post_edited'] = array(
  132. 'arguments' => array(
  133. 'who' => NULL,
  134. 'when' => NULL,
  135. 'why' => NULL,
  136. )
  137. );
  138. $items['advanced_forum_node_type_create_list'] = array(
  139. 'arguments' => array(
  140. 'forum_id' => NULL,
  141. )
  142. );
  143. // These only exist if both search and nodecomment are on.
  144. if (module_exists('search') && module_exists('nodecomment')) {
  145. $items['advanced_forum_search_forum'] = array(
  146. 'arguments' => array('tid' => NULL),
  147. 'template' => 'advanced_forum-search-forum',
  148. );
  149. $items['advanced_forum_search_topic'] = array(
  150. 'arguments' => array('node' => NULL),
  151. 'template' => 'advanced_forum-search-topic',
  152. );
  153. $items['views_view_fields__advanced_forum_search'] = array(
  154. 'arguments' => array('view' => NULL, 'options' => NULL, 'row' => NULL),
  155. 'template' => 'advanced_forum_search_result',
  156. 'original hook' => 'views_view_fields',
  157. );
  158. $items['views_view_fields__advanced_forum_search_topic'] = array(
  159. 'arguments' => array('view' => NULL, 'options' => NULL, 'row' => NULL),
  160. 'template' => 'advanced_forum_search_result',
  161. 'original hook' => 'views_view_fields',
  162. );
  163. }
  164. // Templates for features added by Views
  165. $items['views_view_forum_topic_list__advanced_forum_topic_list'] = array(
  166. 'arguments' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
  167. 'template' => 'advanced_forum-topic-list-view',
  168. 'original hook' => 'views_view_forum_topic_list',
  169. );
  170. $items['views_view__advanced_forum_topic_list'] = array(
  171. 'arguments' => array('view' => NULL),
  172. 'template' => 'advanced_forum-topic-list-outer-view',
  173. 'original hook' => 'views_view',
  174. );
  175. $items['views_view__advanced_forum_group_topic_list'] = array(
  176. 'arguments' => array('view' => NULL),
  177. 'template' => 'advanced_forum-group-topic-list-outer-view',
  178. 'original hook' => 'views_view',
  179. );
  180. return $items;
  181. }
  182. /**
  183. * Implementation of hook_theme_registry_alter().
  184. */
  185. function advanced_forum_theme_registry_alter(&$theme_registry) {
  186. advanced_forum_load_style_includes();
  187. // Garland's phptemplate_comment_wrapper really sucks. Chances are, a theme
  188. // does NOT want to control this on forum nodes anyway, so we're going to take
  189. // it over:
  190. if (isset($theme_registry['comment_wrapper']['function']) && $theme_registry['comment_wrapper']['function'] == 'phptemplate_comment_wrapper') {
  191. $theme_registry['comment_wrapper']['function'] = 'advanced_forum_comment_wrapper';
  192. }
  193. // Optionally kill the next/previous forum topic navigation links because
  194. // it is a nasty query that can slow down the forums.
  195. if (!variable_get('advanced_forum_use_topic_navigation', FALSE)) {
  196. foreach ($theme_registry['forum_topic_navigation']['preprocess functions'] as $key => $value) {
  197. if ($value == 'template_preprocess_forum_topic_navigation') {
  198. unset($theme_registry['forum_topic_navigation']['preprocess functions'][$key]);
  199. }
  200. }
  201. }
  202. // Don't let core do its basic preprocess for forums, as we want to do
  203. // other stuff now.
  204. foreach ($theme_registry['forums']['preprocess functions'] as $key => $value) {
  205. if ($value == 'template_preprocess_forums') {
  206. unset($theme_registry['forums']['preprocess functions'][$key]);
  207. }
  208. }
  209. // We duplicate all of core's forum list preprocessing so no need to run
  210. // it twice. Running twice also causes problems with & in forum name.
  211. foreach ($theme_registry['forum_list']['preprocess functions'] as $key => $value) {
  212. if ($value == 'template_preprocess_forum_list') {
  213. unset($theme_registry['forum_list']['preprocess functions'][$key]);
  214. }
  215. }
  216. // Views handles the topic list pages so remove the core template preprocess.
  217. foreach ($theme_registry['forum_topic_list']['preprocess functions'] as $key => $value) {
  218. if ($value == 'template_preprocess_forum_topic_list') {
  219. unset($theme_registry['forum_topic_list']['preprocess functions'][$key]);
  220. }
  221. }
  222. // --- The following section manipulates the theme registry so the .tpl files
  223. // --- for the given templates can be found first in the (sub)theme directory
  224. // --- then in ancestor themes, if any, then in the active style directory
  225. // --- for advanced forum or any ancestor styles.
  226. // Affected templates
  227. $templates = array('node',
  228. 'comment',
  229. 'comment_wrapper',
  230. 'forums',
  231. 'forum_list',
  232. 'forum_topic_list',
  233. 'forum_icon',
  234. 'forum_submitted',
  235. 'forum_topic_navigation',
  236. 'author_pane',
  237. 'advanced_forum_statistics',
  238. 'advanced_forum_search_forum',
  239. 'advanced_forum_search_topic',
  240. 'advanced_forum_search_result',
  241. 'advanced_forum_topic_list_view',
  242. 'views_view_fields__advanced_forum_search',
  243. 'views_view_fields__advanced_forum_search_topic',
  244. 'views_view_forum_topic_list__advanced_forum_topic_list',
  245. 'views_view_forum_topic_list__advanced_forum_group_topic_list',
  246. 'views_view__advanced_forum_topic_list',
  247. 'views_view__advanced_forum_group_topic_list',
  248. 'advanced_forum_topic_legend',
  249. 'advanced_forum_forum_legend',
  250. 'advanced_forum_topic_header',
  251. 'advanced_forum_active_poster',
  252. );
  253. // Find all our ancestor themes and put them in an array.
  254. global $theme;
  255. $themes = list_themes();
  256. $ancestor_paths = array();
  257. $ancestor = $theme;
  258. while ($ancestor && isset($themes[$ancestor]->base_theme)) {
  259. array_unshift($ancestor_paths, dirname($themes[$themes[$ancestor]->base_theme]->filename));
  260. $ancestor = $themes[$ancestor]->base_theme;
  261. }
  262. // Get the sequence of styles to look in for templates
  263. $lineage = advanced_forum_style_lineage();
  264. if (!array_key_exists('naked', $lineage)) {
  265. // Add naked in at the end of the line to prevent problems if a style
  266. // doesn't include all needed templates.
  267. $lineage['naked'] = drupal_get_path('module', 'advanced_forum') . '/styles/naked';
  268. }
  269. foreach ($templates as $template) {
  270. // Sanity check in case the template is not being used.
  271. if (!empty($theme_registry[$template])) {
  272. // If there was a path in there, store it.
  273. $existing_path = array_shift($theme_registry[$template]['theme paths']);
  274. // Add paths for our style and ancestors before the existing path, if any.
  275. foreach ($lineage AS $style => $style_path) {
  276. array_unshift($theme_registry[$template]['theme paths'], $existing_path, $style_path);
  277. $existing_path = array_shift($theme_registry[$template]['theme paths']);
  278. }
  279. // If there are any ancestor paths (ie: we are in a subtheme, add those)
  280. foreach ($ancestor_paths as $ancestor_path) {
  281. $theme_registry[$template]['theme paths'][] = $ancestor_path;
  282. }
  283. // Put the active theme's path last since that takes precidence.
  284. $theme_registry[$template]['theme paths'][] = advanced_forum_path_to_theme();
  285. // Add preprocess functions if our style has them.
  286. $preprocess = array();
  287. foreach ($lineage as $key => $path) {
  288. if (function_exists('advanced_forum_' . $key . '_preprocess_' . $template)) {
  289. $preprocess[] = 'advanced_forum_' . $key . '_preprocess_' . $template;
  290. }
  291. }
  292. // There are preprocess functions to add, so figure out where we want to add
  293. // them.
  294. if ($preprocess) {
  295. $position = 0;
  296. foreach ($theme_registry[$template]['preprocess functions'] as $function) {
  297. $position++;
  298. // If we see either of these items, that means we can place our
  299. // preprocess functions after this.
  300. if (substr($function, 0, 25) == 'advanced_forum_preprocess' || substr($function, 0, 34) == 'template_preprocess_advanced_forum') {
  301. break;
  302. }
  303. }
  304. // Add in our new preprocess functions:
  305. array_splice($theme_registry[$template]['preprocess functions'], $position, 0, $preprocess);
  306. }
  307. }
  308. }
  309. }
  310. /**
  311. * Implementation of hook_nodeapi().
  312. */
  313. function advanced_forum_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  314. if ($op == 'update' || $op == 'insert' || $op == 'delete') {
  315. // Update the cached statistics.
  316. advanced_forum_statistics_replies(TRUE);
  317. }
  318. if ($op == 'view' && !empty($node->content['forum_navigation'])) {
  319. if (!empty($node->content['forum_navigation'])) {
  320. // Move the forum navigation to a seperate variable so it doesn't
  321. // get lumped in with the content.
  322. $node->advanced_forum_navigation = $node->content['forum_navigation']['#value'];
  323. $node->content['forum_navigation'] = NULL;
  324. }
  325. }
  326. }
  327. /**
  328. * Implementation of hook_comment().
  329. */
  330. function advanced_forum_comment(&$a1, $op) {
  331. if ($op == 'update' || $op == 'insert' || $op == 'delete') {
  332. // Update the cached statistics.
  333. advanced_forum_statistics_replies(TRUE);
  334. }
  335. }
  336. /**
  337. * Implementation of hook_link().
  338. */
  339. function advanced_forum_link($type, $node = NULL, $teaser = FALSE) {
  340. $links = array();
  341. if ($type == 'node' && (!isset($node->comment_target_nid))) {
  342. if (advanced_forum_is_styled($node, $teaser, $type)) {
  343. // Add edit / delete links to the node links to match replies.
  344. if (node_access('update', $node)) {
  345. $links['post_edit'] = array(
  346. 'title' => t('edit'),
  347. 'href' => 'node/'. $node->nid .'/edit',
  348. 'query' => drupal_get_destination(),
  349. );
  350. }
  351. if (node_access('delete', $node)) {
  352. $links['post_delete'] = array(
  353. 'title' => t('delete'),
  354. 'href' => 'node/'. $node->nid .'/delete',
  355. );
  356. }
  357. // Core only adds the link if the comment form is on a separate page
  358. // but we want the link there regardless for consistancy.
  359. // Nodecomment already handles this so only run if that's not enabled.
  360. if (!module_exists('nodecomment')) {
  361. if ($node->comment == COMMENT_NODE_READ_WRITE) {
  362. if (user_access('post comments')) {
  363. if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) != COMMENT_FORM_SEPARATE_PAGE) {
  364. $links['comment_add'] = array(
  365. 'title' => t('Add new comment'),
  366. 'href' => "comment/reply/$node->nid",
  367. 'attributes' => array('title' => t('Reply to this topic.')),
  368. 'fragment' => 'comment-form'
  369. );
  370. }
  371. }
  372. }
  373. }
  374. }
  375. }
  376. return $links;
  377. }
  378. /**
  379. * Implementation of hook_link_alter().
  380. */
  381. function advanced_forum_link_alter(&$links, $node, $comment = NULL) {
  382. if (empty($comment)) {
  383. $object = $node;
  384. $object_type = 'node';
  385. }
  386. else {
  387. $object = $comment;
  388. $object_type = 'comment';
  389. }
  390. // Check if we are altering links on a node that is displayed in a teaser.
  391. // @TODO: Find out if there's a better way to tell if this is a teaser.
  392. $teaser = !(arg(0) == 'node' && arg(1) > 0);
  393. if (advanced_forum_is_styled($object, $teaser, $object_type)) {
  394. // Change first post from "add comment" to "reply" if it isn't already.
  395. if (!empty($links['comment_add'])) {
  396. $links['comment_add']['title'] = t('reply');
  397. }
  398. // List the keys we are interested in.
  399. $affected_keys = array('post_edit', 'comment_edit', 'post_delete', 'comment_delete', 'quote', 'comment_add', 'comment_reply', 'comment_mover_node_prune', 'comment_mover_comment_prune');
  400. // Add extra span tags for image replacement.
  401. foreach ($links AS $key => $link) {
  402. if (in_array($key, $affected_keys)) {
  403. $current_classes = (empty($links[$key]['attributes']['class'])) ? '' : $links[$key]['attributes']['class'];
  404. $links[$key]['attributes']['class'] = "$current_classes af-button-small";
  405. $links[$key]['title'] = '<span>' . $links[$key]['title'] . '</span>';
  406. $links[$key]['html'] = TRUE;
  407. }
  408. }
  409. // Put the links in a consistent order.
  410. foreach ($affected_keys as $key) {
  411. if (isset($links[$key])) {
  412. $temp = $links[$key];
  413. unset($links[$key]);
  414. $links[$key] = $temp;
  415. }
  416. }
  417. }
  418. }
  419. /**
  420. * Implementation of hook_form_alter().
  421. */
  422. function advanced_forum_form_alter(&$form, &$form_state, $form_id) {
  423. if (!empty($form['#node']->type) && advanced_forum_type_is_in_forum($form['#node']->type) && isset($form['body_field']) && isset($form['body_field']['#after_build'])) {
  424. // Remove the teaser splitter.
  425. $teaser_js_build = array_search('node_teaser_js', $form['body_field']['#after_build']);
  426. unset($form['body_field']['#after_build'][$teaser_js_build]);
  427. $form['body_field']['teaser_js']['#access'] = FALSE;
  428. $form['body_field']['teaser_include']['#access'] = FALSE;
  429. }
  430. // Add our OG view as a potential RON for organic groups.
  431. if (!empty($form['og_settings']['group_details']['og_home_page_view'])) {
  432. $form['og_settings']['group_details']['og_home_page_view']['#options']['advanced_forum_group_topic_list'] = 'advanced_forum_group_topic_list';
  433. }
  434. }
  435. // MAKE VIEWS BITS WORK *****************************************************/
  436. function advanced_forum_views_api() {
  437. return array(
  438. 'api' => 2,
  439. 'path' => drupal_get_path('module', 'advanced_forum') . '/includes/views',
  440. 'file' => 'views.inc',
  441. );
  442. }
  443. // MAKE CTOOLS BITS WORK *****************************************************/
  444. /**
  445. * Tell CTools about what plugins we support.
  446. */
  447. function advanced_forum_ctools_plugin_directory($module, $plugin) {
  448. if ($module == 'advanced_forum') {
  449. return 'styles';
  450. }
  451. if ($module == 'page_manager' || $module == 'ctools') {
  452. return 'plugins/' . $plugin;
  453. }
  454. }
  455. function advanced_forum_ctools_plugin_api($module, $api) {
  456. if ($module == 'page_manager' && $api = 'pages_default') {
  457. return array(
  458. 'version' => 1,
  459. 'path' => drupal_get_path('module', 'advanced_forum') . '/includes/panels',
  460. );
  461. }
  462. }
  463. // THEME FUNCTIONS AND TEMPLATE PREPROCESSES **********************************/
  464. include_once drupal_get_path('module', 'advanced_forum') . '/includes/theme.inc';
  465. // STYLE RELATED FUNCTIONS ****************************************************/
  466. include_once drupal_get_path('module', 'advanced_forum') . '/includes/style.inc';
  467. // CORE FORUM PAGE OVERRIDES **************************************************/
  468. include_once drupal_get_path('module', 'advanced_forum') . '/includes/core-overrides.inc';
  469. // MARK AS READ ***************************************************************/
  470. include_once drupal_get_path('module', 'advanced_forum') . '/includes/mark-read.inc';
  471. // VIEWS RELATED GOODIES ******************************************************/
  472. /**
  473. * Post render a view and replace any advanced forum tokens.
  474. */
  475. function advanced_forum_views_post_render(&$view, &$output) {
  476. if (!is_object($view->style_plugin) || !$view->style_plugin->uses_row_plugin()) {
  477. return;
  478. }
  479. $plugin = $view->display_handler->get_option('row_plugin');
  480. if ($plugin == 'node' || $plugin == 'nodecomment_threaded') {
  481. // Look for token matches in the output:
  482. $matches = array();
  483. $tokens = array();
  484. // We want to change the look of the 'new' marker from the default, slightly:
  485. $tokens['<span class="new">' . t('new') . '</span>'] = '<span class="new">(' . t('new') . ')</span>';
  486. // Replace the Author Pane token with the actual Author Pane.
  487. // Note that this token will only exist if Author Pane is enabled.
  488. if (preg_match_all('/<!--post:author-pane-([\d]+)-->/us', $output, $matches)) {
  489. foreach ($matches[1] as $match => $uid) {
  490. $token = $matches[0][$match]; // This is the exact string that matched.
  491. if (!isset($tokens[$token])) {
  492. $account = user_load($uid);
  493. $tokens[$token] = theme('author_pane', $account, 'advanced_forum', variable_get('advanced_forum_user_picture_preset', ''), NULL, TRUE);
  494. }
  495. }
  496. }
  497. // Replace the Post edited token.
  498. if (preg_match_all('/<!--post:post-edited-([\d]+)-->/us', $output, $matches)) {
  499. foreach ($matches[1] as $match => $nid) {
  500. $token = $matches[0][$match]; // This is the exact string that matched.
  501. if (!isset($tokens[$token])) {
  502. if (user_access('view last edited notice')) {
  503. $sql = 'SELECT uid, log, timestamp FROM {node_revisions} WHERE nid = %d ORDER BY timestamp DESC';
  504. $row = db_fetch_object(db_query($sql, $nid));
  505. $tokens[$token] = theme('advanced_forum_post_edited', $row->uid, $row->timestamp, $row->log);
  506. }
  507. else {
  508. // No access; remove token.
  509. $tokens[$token] = '';
  510. }
  511. }
  512. }
  513. }
  514. // Replace the core Signature token.
  515. if (preg_match_all('/<!--post:signature-core-([\d]+)-->/us', $output, $matches)) {
  516. foreach ($matches[1] as $match => $uid) {
  517. $token = $matches[0][$match]; // This is the exact string that matched.
  518. if (!isset($tokens[$token])) {
  519. $account = user_load($uid);
  520. if ($account->signature) {
  521. $tokens[$token] = check_markup($account->signature, $account->signature_format, FALSE);
  522. }
  523. }
  524. }
  525. }
  526. // Replace the posted by viewer tokens with class if appropriate.
  527. if (preg_match_all('/<!--post:poster-id-([\d]+)-->/us', $output, $matches)) {
  528. foreach ($matches[1] as $match => $uid) {
  529. $token = $matches[0][$match]; // This is the exact string that matched.
  530. if (!isset($tokens[$token])) {
  531. global $user;
  532. if ($user->uid > 0 && $uid == $user->uid) {
  533. // This post is by current user.
  534. $tokens[$token] = " post-by-viewer";
  535. }
  536. else {
  537. $tokens[$token] = "";
  538. }
  539. }
  540. }
  541. }
  542. // Perform replacements.
  543. $output = strtr($output, $tokens);
  544. }
  545. }
  546. /**
  547. * Display the "sort" widget. This is a specially hacked widget that only
  548. * works with tablesorting. Tablesorting MUST be on for these widgets
  549. * to appear.
  550. */
  551. function advanced_forum_forum_topic_list_sort() {
  552. $form_state = array(
  553. 'method' => 'get',
  554. 'no_redirect' => TRUE,
  555. 'rerender' => TRUE,
  556. 'input' => $_GET,
  557. 'drop tokens' => TRUE,
  558. );
  559. ctools_include('form');
  560. return ctools_build_form('advanced_forum_forum_topic_list_sort_form', $form_state);
  561. }
  562. function advanced_forum_forum_topic_list_sort_form(&$form_state) {
  563. $view = views_get_view('advanced_forum_topic_list');
  564. if (!is_object($view)) {
  565. return;
  566. }
  567. $view->set_display('default');
  568. $view->init_handlers();
  569. $view->init_style();
  570. // Work up a list of possible fields.
  571. $handler = &$view->style_plugin;
  572. $fields = &$view->field;
  573. $columns = $handler->sanitize_columns($handler->options['columns'], $fields);
  574. $options = array();
  575. foreach ($columns as $field => $column) {
  576. if ($field == $column && empty($fields[$field]->options['exclude'])) {
  577. if (empty($handler->options['info'][$field]['sortable']) || !$fields[$field]->click_sortable()) {
  578. continue;
  579. }
  580. $label = check_plain(!empty($fields[$field]) ? $fields[$field]->label() : '');
  581. $options[$field] = $label;
  582. }
  583. }
  584. $form['inline'] = array(
  585. '#prefix' => '<div class="container-inline">',
  586. '#suffix' => '</div>',
  587. );
  588. $form['inline']['order'] = array(
  589. '#type' => 'select',
  590. '#options' => $options,
  591. '#default_value' => $handler->options['default'],
  592. );
  593. $form['inline']['sort'] = array(
  594. '#type' => 'select',
  595. '#options' => array(
  596. 'asc' => t('Up'),
  597. 'desc' => t('Down'),
  598. ),
  599. '#default_value' => 'desc',
  600. );
  601. $form['inline']['submit'] = array(
  602. '#id' => 'sort-topic-submit',
  603. '#name' => '',
  604. '#type' => 'submit',
  605. '#value' => t('Sort'),
  606. );
  607. if (isset($_GET['page'])) {
  608. $form['page'] = array(
  609. '#type' => 'hidden',
  610. '#default_value' => $_GET['page'],
  611. );
  612. }
  613. if (!variable_get('clean_url', FALSE)) {
  614. $form['q'] = array(
  615. '#type' => 'hidden',
  616. '#value' => $_GET['q'],
  617. );
  618. }
  619. $view->destroy();
  620. return $form;
  621. }
  622. // STATISTICS *****************************************************************/
  623. /**
  624. * Count total amount of forum threads.
  625. */
  626. function advanced_forum_statistics_topics() {
  627. return db_result(db_query('SELECT COUNT(DISTINCT(nid)) FROM {forum}'));
  628. }
  629. /**
  630. * Counts total amount of replies. Initial posts are added to this total
  631. * in the calling function.
  632. *
  633. * @param $refresh
  634. * TRUE if the stored count should be updated.
  635. * @return
  636. * Total number of replies in the forum.
  637. */
  638. function advanced_forum_statistics_replies($refresh = FALSE) {
  639. // Check for cached total.
  640. $total_replies = variable_get('advanced_forum_stats_replies', 0);
  641. // If there's no cache or we need to refresh the cache
  642. if ($refresh || $total_replies == 0) {
  643. if (module_exists('nodecomment')) {
  644. $total_replies = db_result(db_query('SELECT COUNT(cid) FROM {node_comments} c INNER JOIN {forum} f ON (f.nid = c.nid)'));
  645. }
  646. else {
  647. $total_replies = db_result(db_query('SELECT SUM(s.comment_count) FROM {node_comment_statistics} s INNER JOIN {forum} f ON (s.nid = f.nid)'));
  648. }
  649. variable_set('advanced_forum_stats_replies', $total_replies);
  650. }
  651. return $total_replies;
  652. }
  653. /**
  654. * Count total amount of active users.
  655. */
  656. function advanced_forum_statistics_users() {
  657. return db_result(db_query('SELECT COUNT(uid) FROM {users} WHERE status = 1'));
  658. }
  659. /**
  660. * Return the newest X active (not blocked) users, linked to their profiles.
  661. */
  662. function advanced_forum_statistics_latest_users() {
  663. $number_to_fetch = 5; // @TODO: Make this a setting.
  664. $sql = 'SELECT uid, name FROM {users} WHERE status = 1 AND access > 0 ORDER BY created DESC';
  665. $latest_users = db_query_range($sql, NULL, NULL, $number_to_fetch);
  666. while ($account = db_fetch_object($latest_users)) {
  667. $list[] = theme('username', $account);
  668. }
  669. return $list;
  670. }
  671. /**
  672. * Return an array of online usernames, linked to their profiles.
  673. */
  674. function advanced_forum_statistics_online_users() {
  675. $list = array();
  676. $interval = time() - variable_get('user_block_seconds_online', 900);
  677. $sql = 'SELECT DISTINCT u.uid, u.name, MAX(s.timestamp) as maxtime
  678. FROM {users} u
  679. INNER JOIN {sessions} s ON u.uid = s.uid
  680. WHERE s.timestamp >= %d AND s.uid > 0
  681. GROUP BY u.uid, u.name
  682. ORDER BY maxtime DESC';
  683. $authenticated_users = db_query($sql, $interval);
  684. while ($account = db_fetch_object($authenticated_users)) {
  685. $list[] = theme('username', $account);
  686. }
  687. return $list;
  688. }
  689. // CALCULATING LINKS - New, Last, Etc *****************************************/
  690. function advanced_forum_user_can_reply($node) {
  691. if (module_exists('nodecomment') && !empty($node->node_comment) && !empty($node->comment_type)) {
  692. $access = user_access('create ' . $node->comment_type . ' content');
  693. }
  694. else {
  695. $access = user_access('post comments');
  696. }
  697. return $access;
  698. }
  699. function advanced_forum_get_reply_link($node) {
  700. $reply_link = array();
  701. // Give nodecomment (if installed) first shot at the comment setting
  702. $comment_setting = (empty($node->node_comment)) ? $node->comment : $node->node_comment;
  703. // Anchor to the form is depends on if reply is a node or a comment.
  704. $fragment = (empty($node->node_comment)) ? 'comment-form' : 'node-form';
  705. if ($comment_setting == COMMENT_NODE_READ_WRITE) {
  706. if (advanced_forum_user_can_reply($node)) {
  707. if (variable_get('comment_form_location_'. $node->type, COMMENT_FORM_SEPARATE_PAGE) == COMMENT_FORM_SEPARATE_PAGE) {
  708. // Reply form is on separate page. Grab the href from the node links
  709. // so it's automatically corrected for Node Comments if needed.
  710. $reply_link['href'] = $node->links['comment_add']['href'];
  711. $reply_link['options']['fragment'] = $fragment;
  712. $reply_link['class'] = 'reply-allowed';
  713. $reply_link['title'] = t('Post reply');
  714. return $reply_link;
  715. }
  716. else {
  717. // Reply form is on same page. The reply button should jump down to it
  718. // rather than going to a new page.
  719. $reply_link['href'] = $_GET['q'];
  720. $reply_link['options']['fragment'] = $fragment;
  721. $current_page = isset($_GET['page']) ? $_GET['page'] : 0;
  722. $reply_link['options']['query'] = ($current_page) ? "page=$current_page" : NULL;
  723. $reply_link['class'] = 'reply-allowed';
  724. $reply_link['title'] = t('Quick reply');
  725. return $reply_link;
  726. }
  727. }
  728. else {
  729. // User does not have access to post replies on this node.
  730. return 'reply-forbidden';
  731. }
  732. }
  733. else {
  734. // Topic is locked.
  735. return 'reply-locked';
  736. }
  737. }
  738. /**
  739. * Get a link to the last post in a topic.
  740. *
  741. * @param $node
  742. * Node object
  743. * @return
  744. * Text linking to the last post in a topic.
  745. */
  746. function advanced_forum_last_post_link($node) {
  747. $last_comment_id = advanced_forum_last_post_in_topic($node->nid);
  748. // Return empty link if post doesn't have comments.
  749. if (empty($last_comment_id)) {
  750. return;
  751. }
  752. $last_page = advanced_forum_get_last_page($node);
  753. $query = ($last_page > 0) ? "page=$last_page" : '';
  754. $options = array(
  755. 'html' => TRUE,
  756. 'query' => $query,
  757. 'fragment' => "comment-$last_comment_id",
  758. );
  759. return theme('advanced_forum_l', t('Last post'), "node/$node->nid", $options, 'large');
  760. }
  761. /**
  762. * Returns a link directly to the first new post in a topic.
  763. *
  764. * @param $node
  765. * Node object
  766. * @param $comment_count
  767. * Number of comments on passed node.
  768. * @return
  769. * Link to the first unread post.
  770. */
  771. function advanced_forum_first_new_post_link($node, $comment_count) {
  772. $nid = $node->nid;
  773. $current_page = isset($_GET['page']) ? $_GET['page'] : 0;
  774. $number_new_comments = advanced_forum_reply_num_new($nid);
  775. if ($number_new_comments > 0) {
  776. $page_of_first_new = advanced_forum_page_first_new($comment_count, $number_new_comments, $node);
  777. // Note that we are linking to the cid anchor rather than "new" because
  778. // the new links will be gone if we go to another page.
  779. $cid_of_first_new = advanced_forum_first_new_comment($nid);
  780. $number_new = t("(!new new)", array('!new' => $number_new_comments));
  781. $options = array(
  782. 'html' => TRUE,
  783. 'query' => $page_of_first_new,
  784. 'fragment' => "comment-$cid_of_first_new"
  785. );
  786. return theme('advanced_forum_l', t('First unread'), "node/$nid", $options, 'large');
  787. }
  788. }
  789. /**
  790. * Get the page number with the first new post.
  791. * This is simply a wrapper to either call the comment module version or the
  792. * nodecomment module version.
  793. */
  794. function advanced_forum_page_first_new($num_comments, $new_replies, $node) {
  795. $comment_type = module_invoke('nodecomment', 'get_comment_type', $node->type);
  796. if (isset($comment_type)) {
  797. return nodecomment_new_page_count($num_comments, $new_replies, $node);
  798. }
  799. else {
  800. return comment_new_page_count($num_comments, $new_replies, $node);
  801. }
  802. }
  803. /**
  804. * Get the number of new posts on a topic.
  805. * This is simply a wrapper to either call the comment module version or the
  806. * nodecomment module version.
  807. */
  808. function advanced_forum_reply_num_new($nid, $timestamp = 0) {
  809. // Make a static cache because this function is called twice from the topic
  810. // header. Once to display the number and once to make the link to first new.
  811. static $number_new_for_node = array();
  812. if (empty($number_new_for_node[$nid])) {
  813. global $user;
  814. $node = node_load($nid);
  815. // We must also check the forum post itself to see if we have viewed it
  816. $viewed = 0; // If not told otherwise, it has been viewed before
  817. if ($user->uid) {
  818. $viewed = node_last_viewed($nid);
  819. // Set it to 1 if it has not been viewed before, but only if it has been
  820. // modified after NODE_NEW_LIMIT; that is, it wouldn't have been purged
  821. // from {history}.
  822. if ($viewed == 0) {
  823. if (node_last_changed($nid) > NODE_NEW_LIMIT) {
  824. $viewed = 1;
  825. }
  826. }
  827. else {
  828. // seems counterintuitive, but set to zero as node_last_viewed gave us a timestamp
  829. // indicating that the node has been viewed before
  830. $viewed = 0;
  831. }
  832. }
  833. $comment_type = module_invoke('nodecomment', 'get_comment_type', $node->type);
  834. if (isset($comment_type)) {
  835. $number_new_for_node[$nid] = nodecomment_num_new($nid, $timestamp) + $viewed;
  836. }
  837. else {
  838. $number_new_for_node[$nid] = comment_num_new($nid, $timestamp) + $viewed;
  839. }
  840. }
  841. return $number_new_for_node[$nid];
  842. }
  843. /**
  844. * Get the comment id of the last post in a topic.
  845. *
  846. * @param $node
  847. * Node object
  848. * @return
  849. * cid of last post.
  850. */
  851. function advanced_forum_last_post_in_topic($nid) {
  852. $node = node_load($nid);
  853. if (module_exists('nodecomment') && nodecomment_get_comment_type($node->type)) {
  854. // Nodecomment module version
  855. $query = 'SELECT nc.cid
  856. FROM {node_comments} nc
  857. INNER JOIN {node} n ON nc.nid = n.nid
  858. WHERE nc.nid = %d AND n.status = 1
  859. ORDER BY nc.cid DESC';
  860. $result = db_result(db_query_range($query, $nid, 0, 1));
  861. }
  862. else {
  863. // Comment module version
  864. $query = 'SELECT c.cid
  865. FROM {comments} c
  866. WHERE c.nid = %d AND c.status = %d
  867. ORDER BY c.cid DESC';
  868. $result = db_result(db_query_range($query, $nid, COMMENT_PUBLISHED, 0, 1));
  869. }
  870. return $result;
  871. }
  872. /**
  873. * Returns the page number of the last page starting at 0 like the pager does.
  874. */
  875. function advanced_forum_get_last_page($node) {
  876. $comments_per_page = _comment_get_display_setting('comments_per_page', $node);
  877. $comment_count = $node->comment_count;
  878. $last_page = ceil($comment_count / $comments_per_page) - 1;
  879. return $last_page;
  880. }
  881. /**
  882. * Returns the ID of the first unread comment.
  883. *
  884. * @param $nid
  885. * Node ID
  886. * @param $timestamp
  887. * Date/time used to override when the user last viewed the node.
  888. * @return
  889. * Comment ID
  890. */
  891. function advanced_forum_first_new_comment($nid, $timestamp = 0) {
  892. global $user;
  893. if ($user->uid) {
  894. // Retrieve the timestamp at which the current user last viewed the
  895. // specified node.
  896. if (!$timestamp) {
  897. $timestamp = node_last_viewed($nid);
  898. }
  899. // Set the timestamp to the limit if the node was last read past the cutoff
  900. $timestamp = ($timestamp > NODE_NEW_LIMIT ? $timestamp : NODE_NEW_LIMIT);
  901. // Use the timestamp to retrieve the oldest new comment.
  902. if (module_exists('nodecomment')) {
  903. $query = "SELECT nc.cid
  904. FROM {node_comments} nc
  905. INNER JOIN {node} n ON nc.cid = n.nid
  906. WHERE nc.nid = %d AND n.changed > %d AND n.status = 1
  907. ORDER BY nc.cid";
  908. $result = db_result(db_query_range($query, $nid, $timestamp, 0, 1));
  909. }
  910. else {
  911. // If this query is appearing in your slow query log @see: http://drupal.org/node/1728770
  912. $query = "SELECT c.cid
  913. FROM {comments} c
  914. WHERE c.nid = %d AND c.timestamp > %d AND c.status = %d
  915. ORDER BY c.cid";
  916. $result = db_result(db_query_range($query, $nid, $timestamp, COMMENT_PUBLISHED, 0, 1));
  917. }
  918. return $result;
  919. }
  920. else {
  921. return 0;
  922. }
  923. }
  924. // GENERAL UTILITY FUNCTIONS *************************************************/
  925. /**
  926. * Return an array of node types allowed in a given vocabulary or term ID.
  927. *
  928. * Note: TID is currently for future use and not acted on.
  929. */
  930. function advanced_forum_allowed_node_types($tid=0, $vid=0) {
  931. if (module_exists('forum_access')) {
  932. // Check with forum access to see if this forum allows node creation.
  933. // If it doesn't, send back an empty list.
  934. if (!forum_access_access($tid, 'create', NULL, TRUE)) {
  935. return array();
  936. }
  937. }
  938. // If no vocabulary is passed in, assume it should be the forum vocab.
  939. $vid = (empty($vid)) ? variable_get('forum_nav_vocabulary', '') : $vid;
  940. $vocabulary = taxonomy_vocabulary_load($vid);
  941. if (is_array($vocabulary->nodes)) {
  942. // There are some node types associated with this vocab so return them.
  943. return $vocabulary->nodes;
  944. }
  945. else {
  946. return array();
  947. }
  948. }
  949. /**
  950. * Return whether a given node type is allowed in the whole forum or given forum.
  951. */
  952. function advanced_forum_type_is_in_forum($type, $tid=0) {
  953. $forum_types = advanced_forum_allowed_node_types();
  954. if (in_array($type, $forum_types)) {
  955. return TRUE;
  956. }
  957. }
  958. /**
  959. * Generate a list of node creation links for a forum.
  960. *
  961. * This is used on the forum list, allowing us to have direct
  962. * links to create new nodes in the forum.
  963. */
  964. function advanced_forum_node_type_create_list($tid) {
  965. $allowed_types = advanced_forum_allowed_node_types($tid);
  966. // Ensure "new topic" is first.
  967. if (isset($allowed_types['forum'])) {
  968. unset($allowed_types['forum']);
  969. array_unshift($allowed_types, 'forum');
  970. }
  971. // Loop through all node types allowed in this forum.
  972. foreach ($allowed_types as $type) {
  973. // Check if this node type can be created by current user.
  974. if (node_access('create', $type)) {
  975. // Fetch the "General" name of the content type.
  976. $node_type = node_get_types('name', $type);
  977. // Remove the word "Forum" out of "Forum topic" to shorten it.
  978. // @TODO: this is a little dodgy and may not work right with
  979. // translations. Should be replaced if there's a better way.
  980. $node_type = str_replace('Forum', '', $node_type);
  981. // Push the link with title and url to the array.
  982. $forum_types[$type] = array(
  983. 'name' => $node_type,
  984. 'href' => 'node/add/'. str_replace('_', '-', $type) .'/'. $tid,
  985. );
  986. }
  987. }
  988. if (empty($forum_types)) {
  989. // The user is logged-in; but denied access to create any new forum content type.
  990. global $user;
  991. if ($user->uid) {
  992. return t('You are not allowed to post new content in this forum.');
  993. }
  994. // The user is not logged-in; and denied access to create any new forum content type.
  995. else {
  996. $login = url('user/login', array('query' => drupal_get_destination()));
  997. return t('<a href="@login">Login</a> to post new content in forum.', array('@login' => $login));
  998. }
  999. }
  1000. else {
  1001. return $forum_types;
  1002. }
  1003. }
  1004. /**
  1005. * Create a drop down list of forum actions.
  1006. */
  1007. function advanced_forum_forum_tools($tid=0) {
  1008. global $user;
  1009. if ($tid > 0) {
  1010. $targets[url("forum/active", array('query' => "forum[]=$tid"))] = t('View active posts in this forum');
  1011. $targets[url("forum/unanswered", array('query' => "forum[]=$tid"))] = t('View unanswered posts in this forum');
  1012. if ($user->uid) {
  1013. $targets[url("forum/new", array('query' => "forum[]=$tid"))] = t('View new posts in this forum');
  1014. if (module_exists('nodecomment')) {
  1015. $targets[url("forum/user", array('query' => "forum[]=$tid"))] = t('View your posts in this forum');
  1016. }
  1017. }
  1018. }
  1019. else {
  1020. $targets[url("forum/active")] = t('View active forum posts');
  1021. $targets[url("forum/unanswered")] = t('View unanswered forum posts');
  1022. if ($user->uid) {
  1023. $targets[url("forum/new")] = t('View new forum posts');
  1024. if (module_exists('nodecomment')) {
  1025. $targets[url("forum/user")] = t('View your forum posts');
  1026. }
  1027. }
  1028. }
  1029. // Add mark as read to the jump list.
  1030. // This code is a little odd and needs explaining. The return value of
  1031. // the mark_as_read function is already formed HTML and so is unsuitable
  1032. // for the jump list. The function already has built in the ability
  1033. // to add to an existing $links array, which has the URL and title text
  1034. // separated. Rather than add a third method just for the jump menu, I
  1035. // reused that functionality here.
  1036. $mark_as_read = array();
  1037. advanced_forum_get_mark_read_link($tid, $mark_as_read);
  1038. if (!empty($mark_as_read['mark-read']['href'])) {
  1039. $targets[url($mark_as_read['mark-read']['href'])] = $mark_as_read['mark-read']['title'];
  1040. }
  1041. $options['choose'] = t("- Forum Tools -");
  1042. // Create and return the jump menu.
  1043. ctools_include('jump-menu');
  1044. return drupal_get_form('ctools_jump_menu', $targets, $options);
  1045. }
  1046. /**
  1047. * Creates a pager to place on each multi-page topic of the topic listing page.
  1048. *
  1049. * @param $max_pages_to_display
  1050. * Number of pages to include on the pager.
  1051. * @param $topic
  1052. * Topic object to create a pager for.
  1053. * @return
  1054. * Object containing the linked pages ready assembly by the theme function.
  1055. */
  1056. function advanced_forum_create_topic_pager($max_pages_to_display, $topic) {
  1057. // Find the number of comments per page for the node type of the topic.
  1058. $comments_per_page = _comment_get_display_setting('comments_per_page', $topic);
  1059. if ($max_pages_to_display > 0 && $topic->num_comments > $comments_per_page) {
  1060. // Topic has more than one page and a pager is wanted. Start off the
  1061. // first page because that doesn't have a query.
  1062. $pager_array = array();
  1063. $current_display_page = 1;
  1064. $pager_array[] = l('1', "node/$topic->nid");
  1065. // Find the ending point. The pager URL is always 1 less than
  1066. // the number being displayed because the first page is 0.
  1067. $last_display_page = ceil($topic->num_comments / $comments_per_page);
  1068. $last_pager_page = $last_display_page - 1;
  1069. // Add pages until we run out or until we hit the max to show.
  1070. while (($current_display_page < $last_display_page) && ($current_display_page < $max_pages_to_display)) {
  1071. // Move to the next page
  1072. $current_display_page++;
  1073. // The page number we link to is 1 less than what's displayed
  1074. $link_to_page = $current_display_page - 1;
  1075. // Add the link to the array
  1076. $pager_array[] = l($current_display_page, "node/$topic->nid", array('query' => 'page=' . $link_to_page));
  1077. }
  1078. // Move to the next page
  1079. $current_display_page++;
  1080. if ($current_display_page == $last_display_page) {
  1081. // We are one past the max to display, but it's the last page,
  1082. // so putting the ...last is silly. Just display it normally.
  1083. $link_to_page = $current_display_page - 1;
  1084. $pager_array[] = l($current_display_page, "node/$topic->nid", array('query' => 'page=' . $link_to_page));
  1085. }
  1086. $pager_last = '';
  1087. if ($current_display_page < $last_display_page) {
  1088. // We are one past the max to display and still aren't
  1089. // on the last page, so put in ... Last Page(N)
  1090. $text = t('Last Page');
  1091. $pager_last_text = l($text, "node/$topic->nid", array('query' => 'page=' . $last_pager_page));
  1092. $pager_last_number = l($last_display_page, "node/$topic->nid", array('query' => 'page=' . $last_pager_page));
  1093. }
  1094. $topic_pager = new stdClass();
  1095. $topic_pager->initial_pages = (empty($pager_array)) ? array() : $pager_array;
  1096. $topic_pager->last_page_text = (empty($pager_last_text)) ? '' : $pager_last_text;
  1097. $topic_pager->last_page_number = (empty($pager_last_numer)) ? '' : $pager_last_number;
  1098. return $topic_pager;
  1099. }
  1100. }
  1101. /**
  1102. * Calculates the number of unread replies for each forum and returns the
  1103. * count for the requested forum.
  1104. */
  1105. function advanced_forum_unread_replies_in_forum($tid, $uid) {
  1106. static $result_cache = NULL;
  1107. if (is_NULL($result_cache)) {
  1108. $result_cache = array();
  1109. if (module_exists("nodecomment")) {
  1110. $sql = "SELECT COUNT(nc.cid) AS count, f.tid
  1111. FROM {node_comments} nc
  1112. INNER JOIN {forum} f ON nc.nid = f.nid
  1113. INNER JOIN {node} n ON nc.cid = n.nid
  1114. INNER JOIN {node} tn ON nc.nid = tn.nid and f.vid = tn.vid
  1115. LEFT JOIN {history} h ON nc.nid = h.nid AND h.uid = %d
  1116. WHERE n.status = 1 AND n.changed > %d AND (n.changed > h.timestamp OR h.timestamp IS NULL)
  1117. GROUP BY f.tid";
  1118. $sql = db_rewrite_sql($sql, 'nc', 'cid');
  1119. }
  1120. else {
  1121. $sql = "SELECT COUNT(c.cid) AS count, f.tid
  1122. FROM {comments} c
  1123. INNER JOIN {forum} f ON c.nid = f.nid
  1124. INNER JOIN {node} n ON f.vid = n.vid
  1125. LEFT JOIN {history} h ON c.nid = h.nid AND h.uid = %d
  1126. WHERE c.status = 0 AND c.timestamp > %d AND (c.timestamp > h.timestamp OR h.timestamp IS NULL)
  1127. GROUP BY f.tid";
  1128. $sql = db_rewrite_sql($sql, 'c', 'cid');
  1129. }
  1130. $result = db_query($sql, $uid, NODE_NEW_LIMIT);
  1131. while ($row = db_fetch_array($result)) {
  1132. $result_cache[$row['tid']] = $row['count'];
  1133. }
  1134. }
  1135. return (isset($result_cache[$tid])) ? $result_cache[$tid] : 0;
  1136. }
  1137. /**
  1138. * Returns the display position of a given reply post ID on a given node.
  1139. */
  1140. function advanced_forum_post_position($node_id, $post_id) {
  1141. static $post_order = array();
  1142. if (!isset($post_order[$node_id])) {
  1143. // Initialize the spot for this node's list.
  1144. $post_order[$node_id] = array();
  1145. // Make this work with either core comments or node comments.
  1146. $table = (module_exists('nodecomment')) ? "node_comments" : "comments";
  1147. // Get the list of CIDs from the database in order of oldest first.
  1148. // We are going to make that assumption for now for simplicity but may
  1149. // revisit in the future if there are requests for newest first.
  1150. $query = "SELECT c.cid FROM {" . $table . "} c WHERE c.nid = %d ORDER BY c.cid ASC";
  1151. // Cycle through the results and fill in the array.
  1152. $result = db_query($query, $node_id);
  1153. while ($post = db_fetch_array($result)) {
  1154. $post_order[$node_id][] = reset($post);
  1155. }
  1156. }
  1157. // Find the position of the passed in post ID.
  1158. $post_position = 0;
  1159. if (is_array($post_order[$node_id])) {
  1160. if (($index = array_search($post_id, $post_order[$node_id])) !== FALSE) {
  1161. $post_position = $index;
  1162. // We need to add 2 because the array starts at 0 and also because the topic
  1163. // node is post #1 on display but is not included in the index.
  1164. $post_position = $post_position + 2;
  1165. }
  1166. }
  1167. return $post_position;
  1168. }