A fork of the NoNonsenseForum, we use as bugtracker http://camendesign.com/nononsense_forum https://github.com/Kroc/NoNonsenseForum
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

658 lines
37KB

  1. <?php //display a particular thread’s contents
  2. /* ====================================================================================================================== */
  3. /* NoNonsense Forum v26 © Copyright (CC-BY) Kroc Camen 2010-2015
  4. licenced under Creative Commons Attribution 3.0 <creativecommons.org/licenses/by/3.0/deed.en_GB>
  5. you may do whatever you want to this code as long as you give credit to Kroc Camen, <camendesign.com>
  6. */
  7. //bootstrap the forum; you should read that file first
  8. require_once './start.php';
  9. //get the post message, the other fields (name / pass) are retrieved automatically in 'start.php'
  10. define ('TEXT', mb_substr (@$_POST['text'], 0, SIZE_TEXT));
  11. //which thread to show
  12. //TODO: an error here should generate a 404, but we can't create a 404 in PHP that will send the server's provided 404 page.
  13. // I may revist this if I create an NNF-provided 404 page
  14. $FILE = (preg_match ('/^[_a-z0-9-]+$/', @$_GET['file']) ? $_GET['file'] : '') or die ('Malformed request');
  15. //load the thread (have to read lock status from the file)
  16. //TODO: if file is missing, give 404, as above
  17. $xml = @simplexml_load_file ("$FILE.rss") or require FORUM_LIB.'error_xml.php';
  18. $thread = $xml->channel->xpath ('item');
  19. //handle a rounding problem with working out the number of pages (PHP 5.3 has a fix for this)
  20. $PAGES = count ($thread) % FORUM_POSTS == 1 ? floor (count ($thread) / FORUM_POSTS) : ceil (count ($thread) / FORUM_POSTS);
  21. //validate the page number, when no page number is specified default to the last page
  22. $PAGE = !PAGE || PAGE > $PAGES ? $PAGES : PAGE;
  23. //access rights for the current user
  24. define ('CAN_REPLY', FORUM_ENABLED && (
  25. //- if you are a moderator (doesn’t matter if the forum or thread is locked)
  26. IS_MOD ||
  27. //- if you are a member, the forum lock doesn’t matter, but you can’t reply to locked threads (only mods can)
  28. (!(bool) $xml->channel->xpath ('category[.="locked"]') && IS_MEMBER) ||
  29. //- if you are neither a mod nor a member, then as long as:
  30. // 1. the *thread* is not locked, and
  31. // 2. the *forum* is such that anybody can reply (unlocked or news/thread-locked), then you can reply
  32. (!(bool) $xml->channel->xpath ('category[.="locked"]') && (FORUM_LOCK != 'posts'))
  33. ));
  34. /* ======================================================================================================================
  35. thread stick / unstick action
  36. ====================================================================================================================== */
  37. if ( (isset ($_POST['stick']) || isset ($_POST['unstick'])) &&
  38. //the site admin, or the first mod of the sub-forum have stick / unstick rights
  39. (IS_ADMIN || strtolower (NAME) === strtolower ((string) @$MODS['LOCAL'][0]))
  40. ) {
  41. //add or remove the filename from "sticky.txt"
  42. if (in_array ("$FILE.rss", $stickies = getStickies ())) {
  43. $stickies = array_diff ($stickies, array ("$FILE.rss"));
  44. } else {
  45. $stickies[] = "$FILE.rss";
  46. };
  47. file_put_contents ('sticky.txt', implode ("\r\n", $stickies), LOCK_EX);
  48. //regenerate the folder's RSS file
  49. indexRSS ();
  50. //redirect to eat the form submission
  51. header ("Location: $url", true, 303);
  52. exit;
  53. }
  54. /* ======================================================================================================================
  55. thread lock / unlock action
  56. ====================================================================================================================== */
  57. if ((isset ($_POST['lock']) || isset ($_POST['unlock'])) && IS_MOD) {
  58. //get a read/write lock on the file so that between now and saving, no other posts could slip in
  59. //normally we could use a write-only lock 'c', but on Windows you can't read the file when write-locked!
  60. $f = fopen ("$FILE.rss", 'r+'); flock ($f, LOCK_EX);
  61. //we have to read the XML using the file handle that's locked because in Windows, functions like
  62. //`get_file_contents`, or even `simplexml_load_file`, won't work due to the lock
  63. $xml = simplexml_load_string (fread ($f, filesize ("$FILE.rss"))) or require FORUM_LIB.'error_xml.php';
  64. //if there’s a "locked" category, remove it
  65. if ((bool) $xml->channel->xpath ('category[.="locked"]')) {
  66. //note: for simplicity this removes *all* channel categories as NNF only uses one at the moment,
  67. // in the future the specific "locked" category needs to be removed
  68. unset ($xml->channel->category);
  69. //when unlocking, go to the thread
  70. $url = FORUM_URL.url (PATH_URL, $FILE).'#nnf_reply-form';
  71. } else {
  72. //if no "locked" category, add it
  73. $xml->channel->category[] = 'locked';
  74. //if locking, return to the index
  75. //(TODO: could return to the particular page in the index the thread is on--complex!)
  76. $url = FORUM_URL.url (PATH_URL);
  77. }
  78. //commit the data
  79. rewind ($f); ftruncate ($f, 0); fwrite ($f, $xml->asXML ());
  80. //close the lock / file
  81. flock ($f, LOCK_UN); fclose ($f);
  82. //try set the modified date of the file back to the time of the last reply
  83. //(un/locking a thread does not push the thread back to the top of the index)
  84. //note: this may fail if the file is not owned by the Apache process
  85. @touch ("$FILE.rss", strtotime ($xml->channel->item[0]->pubDate));
  86. //regenerate the folder's RSS file
  87. indexRSS ();
  88. //redirect to eat the form submission
  89. header ("Location: $url", true, 303);
  90. exit;
  91. }
  92. /* ======================================================================================================================
  93. append link clicked
  94. ====================================================================================================================== */
  95. if ($ID = (preg_match ('/^[A-Z0-9]+$/i', @$_GET['append']) ? $_GET['append'] : false)) {
  96. //get a write lock on the file so that between now and saving, no other posts could slip in
  97. $f = fopen ("$FILE.rss", 'r+'); flock ($f, LOCK_EX);
  98. $xml = simplexml_load_string (fread ($f, filesize ("$FILE.rss"))) or require FORUM_LIB.'error_xml.php';
  99. //find the post using the ID (we need to know the numerical index for later)
  100. for ($i=0; $i<count ($xml->channel->item); $i++) if (strstr ($xml->channel->item[$i]->link, '#') == "#$ID") break;
  101. $post = $xml->channel->item[$i];
  102. /* has the un/pw been submitted to authenticate the append?
  103. -------------------------------------------------------------------------------------------------------------- */
  104. if (AUTH && TEXT && CAN_REPLY && (
  105. //a moderator can always append
  106. IS_MOD ||
  107. //the owner of a post can append
  108. (strtolower (NAME) == strtolower ($post->author) && (
  109. //if the forum is post-locked, they must be a member to append to their own posts
  110. (FORUM_LOCK != 'posts') || IS_MEMBER
  111. ))
  112. )) {
  113. //check for duplicate append:
  114. if ( //normalise the original post and the append, and check the end of the original for a match
  115. substr (unformatText ($post->description), -strlen ($_ = unformatText (formatText (TEXT)))) !== $_
  116. ) {
  117. //append the given text to the reply
  118. $post->description = formatText (
  119. //NNF's markup is unique in that it is fully reversable just by stripping the HTML tags!
  120. //to ensure that appended title links do not duplicate title links in the existing text, we
  121. //convert the original HTML back to markup and add the appended text.
  122. //(`THEME_APPENDED` is defined in 'start.php' and is a shorthand to the translated string
  123. // used as a divider when appending text to a post)
  124. unformatText ($post->description)."\n\n".sprintf (THEME_APPENDED,
  125. safeHTML (NAME), date (DATE_FORMAT, time ())
  126. )."\n\n".TEXT,
  127. //provide the permalink to the thread and the post ID for title's self-link ID uniqueness
  128. FORUM_URL.url (PATH_URL, $FILE, $PAGE), $ID,
  129. //provide access to the whole discussion thread to be able to link "@user" names
  130. $xml
  131. );
  132. //commit the data
  133. rewind ($f); ftruncate ($f, 0); fwrite ($f, $xml->asXML ());
  134. //close the lock / file
  135. flock ($f, LOCK_UN); fclose ($f);
  136. //try set the modified date of the file back to the time of the last reply
  137. //(appending to a post does not push the thread back to the top of the index)
  138. //note: this may fail if the file is not owned by the Apache process
  139. @touch ("$FILE.rss", strtotime ($xml->channel->item[0]->pubDate));
  140. //regenerate the folder's RSS file
  141. indexRSS ();
  142. }
  143. //return to the appended post
  144. header ('Location: '.FORUM_URL.url (PATH_URL, $FILE, $PAGE)."#$ID", true, 303);
  145. exit;
  146. }
  147. //close the lock / file
  148. flock ($f, LOCK_UN); fclose ($f);
  149. /* template the append page
  150. -------------------------------------------------------------------------------------------------------------- */
  151. $template = prepareTemplate (
  152. //template, no canonical URL //HTML title
  153. THEME_ROOT.'append.html', '', sprintf (THEME_TITLE_APPEND, $post->title)
  154. //the preview post:
  155. )->set (array (
  156. '#nnf_post-title' => $xml->channel->title,
  157. '#nnf_post-title@id' => substr (strstr ($post->link, '#'), 1),
  158. 'time#nnf_post-time' => date (DATE_FORMAT, strtotime ($post->pubDate)),
  159. 'time#nnf_post-time@datetime' => gmdate ('r', strtotime ($post->pubDate)),
  160. '#nnf_post-author' => $post->author
  161. ))->setValue (
  162. '#nnf_post-text', $post->description, true
  163. )->remove (array (
  164. //if the user who made the post is a mod, also mark the whole post as by a mod
  165. //(you might want to style any posts made by a mod differently)
  166. '.nnf_post@class, #nnf_post-author@class' => !isMod ($post->author) ? 'nnf_mod' : false
  167. //the append form:
  168. ))->set (array (
  169. //set the field values from what was typed in before
  170. 'input#nnf_name-field-http@value' => NAME, //set the maximum field sizes
  171. 'input#nnf_name-field@value' => NAME, 'input#nnf_name-field@maxlength' => SIZE_NAME,
  172. 'input#nnf_pass-field@value' => PASS, 'input#nnf_pass-field@maxlength' => SIZE_PASS,
  173. 'textarea#nnf_text-field' => TEXT, 'textarea#nnf_text-field@maxlength' => SIZE_TEXT
  174. //is the user already signed-in?
  175. ))->remove (AUTH_HTTP
  176. //don’t need the usual name / password fields and the deafult message for anonymous users
  177. ? '#nnf_name, #nnf_pass, #nnf_email, #nnf_error-none-append'
  178. //user is not signed in, remove the "you are signed in as:" field and the message for signed in users
  179. : '#nnf_name-http, #nnf_error-none-http'
  180. //handle error messages
  181. )->remove (array (
  182. //if there's an error of any sort, remove the default messages
  183. '#nnf_error-none-append, #nnf_error-none-http' => FORM_SUBMIT,
  184. //if the username & password are correct, remove the error message
  185. '#nnf_error-auth-append' => !FORM_SUBMIT || !TEXT || !NAME || !PASS || AUTH,
  186. //if the password is valid, remove the error message
  187. '#nnf_error-pass-append' => !FORM_SUBMIT || !TEXT || !NAME || PASS,
  188. //if the name is valid, remove the error message
  189. '#nnf_error-name-append' => !FORM_SUBMIT || !TEXT || NAME,
  190. //if the message text is valid, remove the error message
  191. '#nnf_error-text' => !FORM_SUBMIT || TEXT
  192. ));
  193. //call the theme-specific templating function, in 'theme.php', before outputting
  194. theme_custom ($template);
  195. exit ($template);
  196. }
  197. /* ======================================================================================================================
  198. delete link clicked
  199. ====================================================================================================================== */
  200. if (isset ($_GET['delete'])) {
  201. //the ID of the post to delete. will be omitted if deleting the whole thread
  202. $ID = (preg_match ('/^[A-Z0-9]+$/i', @$_GET['delete']) ? $_GET['delete'] : false);
  203. //get a write lock on the file so that between now and saving, no other posts could slip in
  204. $f = fopen ("$FILE.rss", 'r+'); flock ($f, LOCK_EX);
  205. //load the thread to get the post preview
  206. $xml = simplexml_load_string (fread ($f, filesize ("$FILE.rss"))) or require FORUM_LIB.'error_xml.php';
  207. //access the particular post. if no ID is provided (deleting the whole thread) use the last item in the RSS file
  208. //(the first post), otherwise find the ID of the specific post
  209. if (!$ID) {
  210. $post = $xml->channel->item[count ($xml->channel->item) - 1];
  211. } else {
  212. //find the post using the ID (we need to know the numerical index for later)
  213. for ($i=0; $i<count ($xml->channel->item); $i++) if (
  214. strstr ($xml->channel->item[$i]->link, '#') == "#$ID"
  215. ) break;
  216. $post = $xml->channel->item[$i];
  217. }
  218. /* has the un/pw been submitted to authenticate the delete?
  219. -------------------------------------------------------------------------------------------------------------- */
  220. if (AUTH && CAN_REPLY && (
  221. //a moderator can always delete
  222. IS_MOD ||
  223. //the owner of a post can delete
  224. (strtolower (NAME) == strtolower ($post->author) && (
  225. //if the forum is post-locked, they must be a member to delete their own posts
  226. (FORUM_LOCK != 'posts') || IS_MEMBER
  227. ))
  228. //deleting a post?
  229. )) if ($ID) {
  230. if (( //full delete? (option ticked, is moderator, and post is on the last page)
  231. (IS_MOD && $i <= (count ($xml->channel->item)-2) % FORUM_POSTS) &&
  232. //if the post has already been blanked, delete it fully
  233. (isset ($_POST['remove']) || $post->xpath ('category[.="deleted"]'))
  234. ) || //if the post is corrupt, remove it entirely instead of blanking it
  235. @simplexml_load_string (
  236. //most HTML entities are not allowed in XML, we need to convert these to test XML validity
  237. '<body>'.DOMTemplate::html_entity_decode ($post->description).'</body>'
  238. ) === false
  239. ) {
  240. //remove the post from the thread entirely
  241. unset ($xml->channel->item[$i]);
  242. //we’ll redirect to the last page (which may have changed number when the post was deleted)
  243. $url = FORUM_URL.url (PATH_URL, $FILE).'#nnf_replies';
  244. } else {
  245. //remove the post text and replace with the deleted messgae
  246. $post->description = (NAME == (string) $post->author) ? THEME_DEL_USER : THEME_DEL_MOD;
  247. //add a "deleted" category so we know to no longer allow it to be edited or deleted again
  248. if (!$post->xpath ('category[.="deleted"]')) $post->category[] = 'deleted';
  249. //need to know what page this post is on to redirect back to it
  250. $url = FORUM_URL.url (PATH_URL, $FILE, $PAGE)."#$ID";
  251. }
  252. //commit the data
  253. rewind ($f); ftruncate ($f, 0); fwrite ($f, $xml->asXML ());
  254. //close the lock / file
  255. flock ($f, LOCK_UN); fclose ($f);
  256. //try set the modified date of the file back to the time of the last reply
  257. //(so that deleting does not push the thread back to the top of the index)
  258. //note: this may fail if the file is not owned by the Apache process
  259. @touch ("$FILE.rss", strtotime ($xml->channel->item[0]->pubDate));
  260. //regenerate the folder's RSS file
  261. indexRSS ();
  262. //return to the deleted post / last page
  263. header ("Location: $url", true, 303);
  264. exit;
  265. } else {
  266. //close the lock / file
  267. flock ($f, LOCK_UN); fclose ($f);
  268. //delete the thread for reals
  269. @unlink (FORUM_ROOT.PATH_DIR."$FILE.rss");
  270. //regenerate the folder's RSS file
  271. indexRSS ();
  272. //return to the index
  273. header ('Location: '.FORUM_URL.url (PATH_URL), true, 303);
  274. exit;
  275. }
  276. //close the lock / file
  277. flock ($f, LOCK_UN); fclose ($f);
  278. /* template the delete page
  279. -------------------------------------------------------------------------------------------------------------- */
  280. $template = prepareTemplate (
  281. //template, no canonical URL //HTML title
  282. THEME_ROOT.'delete.html', '', sprintf (THEME_TITLE_DELETE, $post->title)
  283. //the preview post:
  284. )->set (array (
  285. '#nnf_post-title' => $post->title,
  286. '#nnf_post-title@id' => substr (strstr ($post->link, '#'), 1),
  287. 'time#nnf_post-time' => date (DATE_FORMAT, strtotime ($post->pubDate)),
  288. 'time#nnf_post-time@datetime' => gmdate ('r', strtotime ($post->pubDate)),
  289. '#nnf_post-author' => $post->author
  290. ))->remove (array (
  291. //if the user who made the post is a mod, also mark the whole post as by a mod
  292. //(you might want to style any posts made by a mod differently)
  293. '.nnf_post@class, #nnf_post-author@class' => !isMod ($post->author) ? 'nnf_mod' : false
  294. //the authentication form:
  295. ))->set (array (
  296. //set the field values from input //set the maximum field sizes
  297. 'input#nnf_name-field@value' => NAME, 'input#nnf_name-field@maxlength' => SIZE_NAME,
  298. 'input#nnf_pass-field@value' => PASS, 'input#nnf_pass-field@maxlength' => SIZE_PASS
  299. //are we deleting the whole thread, or just one reply?
  300. ))->remove ($ID
  301. ? '#nnf_error-none-thread'
  302. : '#nnf_error-none-reply, #nnf_remove' //if deleting the whole thread, also remove the checkbox option
  303. //handle error messages
  304. )->remove (array (
  305. //if there's an error of any sort, remove the default messages
  306. '#nnf_error-none-thread, #nnf_error-none-reply' => FORM_SUBMIT,
  307. //if the username & password are correct, remove the error message
  308. '#nnf_error-auth-delete' => !FORM_SUBMIT || !NAME || !PASS || AUTH,
  309. //if the password is valid, remove the error message
  310. '#nnf_error-pass-delete' => !FORM_SUBMIT || !NAME || PASS,
  311. //if the name is valid, remove the error message
  312. '#nnf_error-name-delete' => !FORM_SUBMIT || NAME
  313. ));
  314. try { //insert the post-text, dealing with an invalid HTML error
  315. $template->setValue ('#nnf_post-text', $post->description, true);
  316. $template->remove (array ('.nnf_post@class' => 'nnf_error'));
  317. } catch (Exception $e) {
  318. //if the HTML was invalid, replace with the corruption message
  319. $template->setValue ('#nnf_post-text', THEME_HTML_ERROR, true);
  320. }
  321. //call the theme-specific templating function, in 'theme.php', before outputting
  322. theme_custom ($template);
  323. exit ($template);
  324. }
  325. /* ======================================================================================================================
  326. new reply submitted
  327. ====================================================================================================================== */
  328. //was the submit button clicked? (and is the info valid?)
  329. if (CAN_REPLY && AUTH && TEXT) {
  330. //get a read/write lock on the file so that between now and saving, no other posts could slip in
  331. //normally we could use a write-only lock 'c', but on Windows you can't read the file when write-locked!
  332. $f = fopen ("$FILE.rss", 'r+'); flock ($f, LOCK_EX);
  333. //we have to read the XML using the file handle that's locked because in Windows, functions like
  334. //`get_file_contents`, or even `simplexml_load_file`, won't work due to the lock
  335. $xml = simplexml_load_string (fread ($f, filesize ("$FILE.rss"))) or require FORUM_LIB.'error_xml.php';
  336. //ignore a double-post (could be an accident with the back button)
  337. if ( //same author?
  338. NAME == $xml->channel->item[0]->author &&
  339. //check if the markup text is the same (strips out HTML due to possible unique HTML IDs)
  340. strip_tags ($xml->channel->item[0]->description) == strip_tags (formatText (TEXT))
  341. ) {
  342. //if you can't post / double-post, redirect back to the previous post
  343. $url = $xml->channel->item[0]->link;
  344. } else {
  345. //where will this post exist?
  346. $post_id = base_convert (microtime (), 10, 36);
  347. $page = (count ($thread)+1) % FORUM_POSTS == 1
  348. ? floor ((count ($thread)+1) / FORUM_POSTS)
  349. : ceil ((count ($thread)+1) / FORUM_POSTS)
  350. ;
  351. $url = FORUM_URL.url (PATH_URL, $FILE, $page).'#'.$post_id;
  352. //re-template the whole thread:
  353. $rss = new DOMTemplate (file_get_contents (FORUM_LIB.'rss-template.xml'));
  354. $rss->set (array (
  355. '/rss/channel/title' => $xml->channel->title,
  356. '/rss/channel/link' => FORUM_URL.url (PATH_URL, $FILE)
  357. ))->remove (array (
  358. //is the thread unlocked?
  359. '/rss/channel/category' => !$xml->channel->xpath ('category[.="locked"]')
  360. ));
  361. //template the new reply first
  362. $items = $rss->repeat ('/rss/channel/item');
  363. $items->set (array (
  364. //add the "RE:" prefix, and reply number to the title
  365. './title' => sprintf (THEME_RE,
  366. count ($xml->channel->item), //number of the reply
  367. $xml->channel->title //thread title
  368. ),
  369. './link' => $url,
  370. './author' => NAME,
  371. './pubDate' => gmdate ('r'),
  372. './description' => formatText (TEXT, //process markup into HTML
  373. //provide a permalink and post ID for title self-links
  374. FORUM_URL.url (PATH_URL, $FILE, $page), $post_id,
  375. //provide reference to the thread to link "@user" names
  376. $xml
  377. )
  378. ))->remove (
  379. //the new reply isn’t deleted, so remove the category marker
  380. './category'
  381. )->next ();
  382. //copy the remaining replies across
  383. foreach ($xml->channel->item as $item) $items->set (array (
  384. './title' => $item->title,
  385. './link' => $item->link,
  386. './author' => $item->author,
  387. './pubDate' => $item->pubDate,
  388. './description' => $item->description
  389. ))->remove (array (
  390. //has the reply been deleted? (blanked)
  391. './category' => !$item->xpath ('./category')
  392. ))->next ();
  393. //write the file: first move the write-head to 0, remove the file's contents, and then write new one
  394. rewind ($f); ftruncate ($f, 0); fwrite ($f, $rss);
  395. }
  396. //close the lock / file
  397. flock ($f, LOCK_UN); fclose ($f);
  398. //regenerate the forum / sub-forums's RSS file
  399. indexRSS ();
  400. //refresh page to see the new post added
  401. header ("Location: $url", true, 303);
  402. exit;
  403. }
  404. /* ======================================================================================================================
  405. template thread
  406. ====================================================================================================================== */
  407. //is this thread stickied?
  408. define ('IS_STICKY', in_array ("$FILE.rss", $stickies = getStickies ()));
  409. /* load the template into DOM where we can manipulate it:
  410. --------------------------------------------------------------------------------------------------------------------- */
  411. //(see 'lib/domtemplate/domtemplate.php' or <camendesign.com/dom_templating> for more details)
  412. $template = prepareTemplate (
  413. THEME_ROOT.'thread.html',
  414. //canonical URL of this thread
  415. url (PATH_URL, $FILE, $PAGE),
  416. //HTML title:
  417. sprintf (THEME_TITLE,
  418. //title of the thread, obviously
  419. $xml->channel->title,
  420. //if on page 2 or greater, include the page number in the title
  421. $PAGE>1 ? sprintf (THEME_TITLE_PAGENO, $PAGE) : ''
  422. )
  423. )->set (array (
  424. //the thread itself is the RSS feed :)
  425. '//link[@rel="alternate"]/@href, '.
  426. 'a#nnf_rss@href' => FORUM_PATH.PATH_URL."$FILE.rss"
  427. ))->remove (array (
  428. //if replies can't be added (forum or thread is locked, user is not moderator / member),
  429. //remove the "add reply" link and anything else (like the input form) related to posting
  430. '#nnf_reply, #nnf_reply-form' => !CAN_REPLY,
  431. //if the forum is not post-locked (only mods can post / reply) then remove the warning message
  432. '.nnf_forum-locked' => FORUM_LOCK != 'posts',
  433. //is the user a mod and can un/lock or un/stick the thread?
  434. '#nnf_admin' => !IS_MOD,
  435. //is the thread already locked?
  436. '#nnf_lock' => $xml->channel->xpath ('category[.="locked"]'),
  437. '#nnf_unlock' => !$xml->channel->xpath ('category[.="locked"]'),
  438. //is the thread already stickied?
  439. '#nnf_stick' => IS_STICKY,
  440. '#nnf_unstick' => !IS_STICKY
  441. ));
  442. /* post
  443. ---------------------------------------------------------------------------------------------------------------------- */
  444. //take the first post from the thread (removing it from the rest)
  445. $post = array_pop ($thread);
  446. //remember the original poster’s name, for marking replies by the OP
  447. $author = (string) $post->author;
  448. //prepare the first post, which on this forum appears above all pages of replies
  449. $template->set (array (
  450. '#nnf_post-title' => $xml->channel->title,
  451. 'time#nnf_post-time' => date (DATE_FORMAT, strtotime ($post->pubDate)),
  452. 'time#nnf_post-time@datetime' => gmdate ('r', strtotime ($post->pubDate)),
  453. '#nnf_post-author' => $post->author,
  454. 'a#nnf_post-append@href' => url (PATH_URL, $FILE, $PAGE, 'append',
  455. substr (strstr ($post->link, '#'), 1)).'#append',
  456. 'a#nnf_post-delete@href' => url (PATH_URL, $FILE, $PAGE, 'delete')
  457. ))->remove (array (
  458. //if the user who made the post is a mod, also mark the whole post as by a mod
  459. //(you might want to style any posts made by a mod differently)
  460. '.nnf_post@class, #nnf_post-author@class' => !isMod ($post->author) ? 'nnf_mod' : false,
  461. //append / delete links?
  462. '#nnf_post-append, #nnf_post-delete' => !CAN_REPLY
  463. ));
  464. try { //insert the post-text, dealing with an invalid HTML error
  465. $template->setValue ('#nnf_post-text', $post->description, true);
  466. $template->remove (array ('.nnf_post@class' => 'nnf_error'));
  467. } catch (Exception $e) {
  468. //if the HTML was invalid, replace with the corruption message
  469. $template->setValue ('#nnf_post-text', THEME_HTML_ERROR, true);
  470. //remove the append button
  471. $template->remove ('#nnf_post-append');
  472. }
  473. /* replies
  474. ---------------------------------------------------------------------------------------------------------------------- */
  475. if (!count ($thread)) {
  476. $template->remove ('#nnf_replies');
  477. } else {
  478. //sort the other way around
  479. //<stackoverflow.com/questions/2119686/sorting-an-array-of-simplexml-objects/2120569#2120569>
  480. foreach ($thread as &$node) $sort[] = strtotime ($node->pubDate);
  481. array_multisort ($sort, SORT_ASC, $thread);
  482. //do the page links
  483. theme_pageList ($template, $FILE, $PAGE, $PAGES);
  484. //slice the full list into the current page
  485. $thread = array_slice ($thread, ($PAGE-1) * FORUM_POSTS, FORUM_POSTS);
  486. //get the dummy list-item to repeat (removes it and takes a copy)
  487. $item = $template->repeat ('.nnf_reply');
  488. //index number of the replies, accounting for which page we are on
  489. $no = ($PAGE-1) * FORUM_POSTS;
  490. //apply the data to the template (a reply)
  491. foreach ($thread as &$reply) {
  492. $item->set (array (
  493. './@id' => substr (strstr ($reply->link, '#'), 1),
  494. 'time.nnf_reply-time' => date (DATE_FORMAT, strtotime ($reply->pubDate)),
  495. 'time.nnf_reply-time@datetime' => gmdate ('r', strtotime ($reply->pubDate)),
  496. '.nnf_reply-author' => $reply->author,
  497. 'a.nnf_reply-number' => sprintf (THEME_REPLYNO, ++$no),
  498. 'a.nnf_reply-number@href' => url (PATH_URL, $FILE, $PAGE).strstr ($reply->link,'#'),
  499. 'a.nnf_reply-append@href' => url (PATH_URL, $FILE, $PAGE, 'append',
  500. substr (strstr ($reply->link, '#'), 1)).'#append',
  501. 'a.nnf_reply-delete@href' => url (PATH_URL, $FILE, $PAGE, 'delete',
  502. substr (strstr ($reply->link, '#'), 1))
  503. ))->remove (array (
  504. //has the reply been deleted (blanked)?
  505. './@class' => $reply->xpath ('category[.="deleted"]') ? false : 'nnf_deleted',
  506. ))->remove (array (
  507. //is this reply from the person who started the thread?
  508. './@class' => strtolower ($reply->author) == strtolower ($author) ? false :'nnf_op'
  509. ))->remove (array (
  510. //if the user who made the reply is a mod, also mark the whole post as by a mod
  511. //(you might want to style any posts made by a mod differently)
  512. './@class, .nnf_reply-author@class' => isMod ($reply->author) ? false : 'nnf_mod'
  513. ))->remove (array (
  514. //if the current user in the curent forum can append/delete the current reply:
  515. '.nnf_reply-append, .nnf_reply-delete' => !(CAN_REPLY && (
  516. //moderators can always see append/delete links on all replies
  517. IS_MOD ||
  518. //if you are not signed in, all append/delete links are shown (if forum/thread locking is off)
  519. //if you are signed in, then only links on replies with your name will show
  520. !AUTH_HTTP ||
  521. //if this reply is the by the owner (they can append/delete to their own replies)
  522. (strtolower (NAME) == strtolower ($reply->author) && (
  523. //if the forum is post-locked, they must be a member to append/delete their own replies
  524. (FORUM_LOCK != 'posts') || IS_MEMBER
  525. ))
  526. )),
  527. //append link not available when the reply has been deleted
  528. '.nnf_reply-append' => $reply->xpath ('category[.="deleted"]'),
  529. //delete link not available when the reply has been deleted, except to mods
  530. '.nnf_reply-delete' => $reply->xpath ('category[.="deleted"]') && !IS_MOD
  531. ));
  532. try { //insert the post-text, dealing with an invalid HTML error
  533. $item->setValue ('.nnf_reply-text', $reply->description, true);
  534. $item->remove (array ('./@class' => 'nnf_error'));
  535. } catch (Exception $e) {
  536. //if the HTML was invalid, replace with the corruption message
  537. $item->setValue ('.nnf_reply-text', THEME_HTML_ERROR, true);
  538. //remove the append button
  539. $item->remove ('.nnf_reply-append');
  540. }
  541. $item->next ();
  542. }
  543. }
  544. /* reply form
  545. ---------------------------------------------------------------------------------------------------------------------- */
  546. if (CAN_REPLY) $template->set (array (
  547. //set the field values from what was typed in before
  548. 'input#nnf_name-field-http@value' => NAME, //set the maximum field sizes
  549. 'input#nnf_name-field@value' => NAME, 'input#nnf_name-field@maxlength' => SIZE_NAME,
  550. 'input#nnf_pass-field@value' => PASS, 'input#nnf_pass-field@maxlength' => SIZE_PASS,
  551. 'textarea#nnf_text-field' => TEXT, 'textarea#nnf_text-field@maxlength' => SIZE_TEXT
  552. //is the user already signed-in?
  553. ))->remove (AUTH_HTTP
  554. //don’t need the usual name / password fields and the deafult message for anonymous users
  555. ? '#nnf_name, #nnf_pass, #nnf_email, #nnf_error-none'
  556. //user is not signed in, remove the "you are signed in as:" field and the message for signed in users
  557. : '#nnf_name-http, #nnf_error-none-http'
  558. //are new registrations allowed?
  559. )->remove (FORUM_NEWBIES
  560. ? '#nnf_error-newbies' //yes: remove the warning message
  561. : '#nnf_error-none' //no: remove the default message
  562. //handle error messages
  563. )->remove (array (
  564. //if there's an error of any sort, remove the default messages
  565. '#nnf_error-none, #nnf_error-none-http, #nnf_error-newbies' => FORM_SUBMIT,
  566. //if the username & password are correct, remove the error message
  567. '#nnf_error-auth' => !FORM_SUBMIT || !TEXT || !NAME || !PASS || AUTH,
  568. //if the password is valid, remove the error message
  569. '#nnf_error-pass' => !FORM_SUBMIT || !TEXT || !NAME || PASS,
  570. //if the name is valid, remove the error message
  571. '#nnf_error-name' => !FORM_SUBMIT || !TEXT || NAME,
  572. //if the message text is valid, remove the error message
  573. '#nnf_error-text' => !FORM_SUBMIT || TEXT
  574. ));
  575. //call the theme-specific templating function, in 'theme.php', before outputting
  576. theme_custom ($template);
  577. exit ($template);
  578. ?>