PageRenderTime 58ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/sources/Subs.php

https://github.com/Arantor/Elkarte
PHP | 4142 lines | 2863 code | 493 blank | 786 comment | 702 complexity | fd7dfb7282fc0705c0cb0c5e04a37a29 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * @name ElkArte Forum
  4. * @copyright ElkArte Forum contributors
  5. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  6. *
  7. * This software is a derived product, based on:
  8. *
  9. * Simple Machines Forum (SMF)
  10. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  11. * license: BSD, See included LICENSE.TXT for terms and conditions.
  12. *
  13. * @version 1.0 Alpha
  14. *
  15. * This file has all the main functions in it that relate to, well, everything.
  16. *
  17. */
  18. if (!defined('ELKARTE'))
  19. die('No access...');
  20. /**
  21. * Update some basic statistics.
  22. *
  23. * 'member' statistic updates the latest member, the total member
  24. * count, and the number of unapproved members.
  25. * 'member' also only counts approved members when approval is on, but
  26. * is much more efficient with it off.
  27. *
  28. * 'message' changes the total number of messages, and the
  29. * highest message id by id_msg - which can be parameters 1 and 2,
  30. * respectively.
  31. *
  32. * 'topic' updates the total number of topics, or if parameter1 is true
  33. * simply increments them.
  34. *
  35. * 'subject' updateds the log_search_subjects in the event of a topic being
  36. * moved, removed or split. parameter1 is the topicid, parameter2 is the new subject
  37. *
  38. * 'postgroups' case updates those members who match condition's
  39. * post-based membergroups in the database (restricted by parameter1).
  40. *
  41. * @param string $type Stat type - can be 'member', 'message', 'topic', 'subject' or 'postgroups'
  42. * @param mixed $parameter1 = null
  43. * @param mixed $parameter2 = null
  44. */
  45. function updateStats($type, $parameter1 = null, $parameter2 = null)
  46. {
  47. global $modSettings, $smcFunc;
  48. switch ($type)
  49. {
  50. case 'member':
  51. $changes = array(
  52. 'memberlist_updated' => time(),
  53. );
  54. // #1 latest member ID, #2 the real name for a new registration.
  55. if (is_numeric($parameter1))
  56. {
  57. $changes['latestMember'] = $parameter1;
  58. $changes['latestRealName'] = $parameter2;
  59. updateSettings(array('totalMembers' => true), true);
  60. }
  61. // We need to calculate the totals.
  62. else
  63. {
  64. // Update the latest activated member (highest id_member) and count.
  65. $result = $smcFunc['db_query']('', '
  66. SELECT COUNT(*), MAX(id_member)
  67. FROM {db_prefix}members
  68. WHERE is_activated = {int:is_activated}',
  69. array(
  70. 'is_activated' => 1,
  71. )
  72. );
  73. list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result);
  74. $smcFunc['db_free_result']($result);
  75. // Get the latest activated member's display name.
  76. $result = $smcFunc['db_query']('', '
  77. SELECT real_name
  78. FROM {db_prefix}members
  79. WHERE id_member = {int:id_member}
  80. LIMIT 1',
  81. array(
  82. 'id_member' => (int) $changes['latestMember'],
  83. )
  84. );
  85. list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result);
  86. $smcFunc['db_free_result']($result);
  87. // Are we using registration approval?
  88. if ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion']))
  89. {
  90. // Update the amount of members awaiting approval - ignoring COPPA accounts, as you can't approve them until you get permission.
  91. $result = $smcFunc['db_query']('', '
  92. SELECT COUNT(*)
  93. FROM {db_prefix}members
  94. WHERE is_activated IN ({array_int:activation_status})',
  95. array(
  96. 'activation_status' => array(3, 4),
  97. )
  98. );
  99. list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result);
  100. $smcFunc['db_free_result']($result);
  101. }
  102. }
  103. updateSettings($changes);
  104. break;
  105. case 'message':
  106. if ($parameter1 === true && $parameter2 !== null)
  107. updateSettings(array('totalMessages' => true, 'maxMsgID' => $parameter2), true);
  108. else
  109. {
  110. // SUM and MAX on a smaller table is better for InnoDB tables.
  111. $result = $smcFunc['db_query']('', '
  112. SELECT SUM(num_posts + unapproved_posts) AS total_messages, MAX(id_last_msg) AS max_msg_id
  113. FROM {db_prefix}boards
  114. WHERE redirect = {string:blank_redirect}' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
  115. AND id_board != {int:recycle_board}' : ''),
  116. array(
  117. 'recycle_board' => isset($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
  118. 'blank_redirect' => '',
  119. )
  120. );
  121. $row = $smcFunc['db_fetch_assoc']($result);
  122. $smcFunc['db_free_result']($result);
  123. updateSettings(array(
  124. 'totalMessages' => $row['total_messages'] === null ? 0 : $row['total_messages'],
  125. 'maxMsgID' => $row['max_msg_id'] === null ? 0 : $row['max_msg_id']
  126. ));
  127. }
  128. break;
  129. case 'subject':
  130. // Remove the previous subject (if any).
  131. $smcFunc['db_query']('', '
  132. DELETE FROM {db_prefix}log_search_subjects
  133. WHERE id_topic = {int:id_topic}',
  134. array(
  135. 'id_topic' => (int) $parameter1,
  136. )
  137. );
  138. // Insert the new subject.
  139. if ($parameter2 !== null)
  140. {
  141. $parameter1 = (int) $parameter1;
  142. $parameter2 = text2words($parameter2);
  143. $inserts = array();
  144. foreach ($parameter2 as $word)
  145. $inserts[] = array($word, $parameter1);
  146. if (!empty($inserts))
  147. $smcFunc['db_insert']('ignore',
  148. '{db_prefix}log_search_subjects',
  149. array('word' => 'string', 'id_topic' => 'int'),
  150. $inserts,
  151. array('word', 'id_topic')
  152. );
  153. }
  154. break;
  155. case 'topic':
  156. if ($parameter1 === true)
  157. updateSettings(array('totalTopics' => true), true);
  158. else
  159. {
  160. // Get the number of topics - a SUM is better for InnoDB tables.
  161. // We also ignore the recycle bin here because there will probably be a bunch of one-post topics there.
  162. $result = $smcFunc['db_query']('', '
  163. SELECT SUM(num_topics + unapproved_topics) AS total_topics
  164. FROM {db_prefix}boards' . (!empty($modSettings['recycle_enable']) && $modSettings['recycle_board'] > 0 ? '
  165. WHERE id_board != {int:recycle_board}' : ''),
  166. array(
  167. 'recycle_board' => !empty($modSettings['recycle_board']) ? $modSettings['recycle_board'] : 0,
  168. )
  169. );
  170. $row = $smcFunc['db_fetch_assoc']($result);
  171. $smcFunc['db_free_result']($result);
  172. updateSettings(array('totalTopics' => $row['total_topics'] === null ? 0 : $row['total_topics']));
  173. }
  174. break;
  175. case 'postgroups':
  176. // Parameter two is the updated columns: we should check to see if we base groups off any of these.
  177. if ($parameter2 !== null && !in_array('posts', $parameter2))
  178. return;
  179. $postgroups = cache_get_data('updateStats:postgroups', 360);
  180. if ($postgroups === null || $parameter1 === null)
  181. {
  182. // Fetch the postgroups!
  183. $request = $smcFunc['db_query']('', '
  184. SELECT id_group, min_posts
  185. FROM {db_prefix}membergroups
  186. WHERE min_posts != {int:min_posts}',
  187. array(
  188. 'min_posts' => -1,
  189. )
  190. );
  191. $postgroups = array();
  192. while ($row = $smcFunc['db_fetch_assoc']($request))
  193. $postgroups[$row['id_group']] = $row['min_posts'];
  194. $smcFunc['db_free_result']($request);
  195. // Sort them this way because if it's done with MySQL it causes a filesort :(.
  196. arsort($postgroups);
  197. cache_put_data('updateStats:postgroups', $postgroups, 360);
  198. }
  199. // Oh great, they've screwed their post groups.
  200. if (empty($postgroups))
  201. return;
  202. // Set all membergroups from most posts to least posts.
  203. $conditions = '';
  204. $lastMin = 0;
  205. foreach ($postgroups as $id => $min_posts)
  206. {
  207. $conditions .= '
  208. WHEN posts >= ' . $min_posts . (!empty($lastMin) ? ' AND posts <= ' . $lastMin : '') . ' THEN ' . $id;
  209. $lastMin = $min_posts;
  210. }
  211. // A big fat CASE WHEN... END is faster than a zillion UPDATE's ;).
  212. $smcFunc['db_query']('', '
  213. UPDATE {db_prefix}members
  214. SET id_post_group = CASE ' . $conditions . '
  215. ELSE 0
  216. END' . ($parameter1 != null ? '
  217. WHERE ' . (is_array($parameter1) ? 'id_member IN ({array_int:members})' : 'id_member = {int:members}') : ''),
  218. array(
  219. 'members' => $parameter1,
  220. )
  221. );
  222. break;
  223. default:
  224. trigger_error('updateStats(): Invalid statistic type \'' . $type . '\'', E_USER_NOTICE);
  225. }
  226. }
  227. /**
  228. * Updates the columns in the members table.
  229. * Assumes the data has been htmlspecialchar'd.
  230. * this function should be used whenever member data needs to be
  231. * updated in place of an UPDATE query.
  232. *
  233. * id_member is either an int or an array of ints to be updated.
  234. *
  235. * data is an associative array of the columns to be updated and their respective values.
  236. * any string values updated should be quoted and slashed.
  237. *
  238. * the value of any column can be '+' or '-', which mean 'increment'
  239. * and decrement, respectively.
  240. *
  241. * if the member's post number is updated, updates their post groups.
  242. *
  243. * @param mixed $members An array of integers
  244. * @param array $data
  245. */
  246. function updateMemberData($members, $data)
  247. {
  248. global $modSettings, $user_info, $smcFunc;
  249. $parameters = array();
  250. if (is_array($members))
  251. {
  252. $condition = 'id_member IN ({array_int:members})';
  253. $parameters['members'] = $members;
  254. }
  255. elseif ($members === null)
  256. $condition = '1=1';
  257. else
  258. {
  259. $condition = 'id_member = {int:member}';
  260. $parameters['member'] = $members;
  261. }
  262. // Everything is assumed to be a string unless it's in the below.
  263. $knownInts = array(
  264. 'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages',
  265. 'new_pm', 'pm_prefs', 'gender', 'hide_email', 'show_online', 'pm_email_notify', 'pm_receive_from', 'karma_good', 'karma_bad',
  266. 'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types',
  267. 'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning',
  268. );
  269. $knownFloats = array(
  270. 'time_offset',
  271. );
  272. if (!empty($modSettings['integrate_change_member_data']))
  273. {
  274. // Only a few member variables are really interesting for integration.
  275. $integration_vars = array(
  276. 'member_name',
  277. 'real_name',
  278. 'email_address',
  279. 'id_group',
  280. 'gender',
  281. 'birthdate',
  282. 'website_title',
  283. 'website_url',
  284. 'location',
  285. 'hide_email',
  286. 'time_format',
  287. 'time_offset',
  288. 'avatar',
  289. 'lngfile',
  290. );
  291. $vars_to_integrate = array_intersect($integration_vars, array_keys($data));
  292. // Only proceed if there are any variables left to call the integration function.
  293. if (count($vars_to_integrate) != 0)
  294. {
  295. // Fetch a list of member_names if necessary
  296. if ((!is_array($members) && $members === $user_info['id']) || (is_array($members) && count($members) == 1 && in_array($user_info['id'], $members)))
  297. $member_names = array($user_info['username']);
  298. else
  299. {
  300. $member_names = array();
  301. $request = $smcFunc['db_query']('', '
  302. SELECT member_name
  303. FROM {db_prefix}members
  304. WHERE ' . $condition,
  305. $parameters
  306. );
  307. while ($row = $smcFunc['db_fetch_assoc']($request))
  308. $member_names[] = $row['member_name'];
  309. $smcFunc['db_free_result']($request);
  310. }
  311. if (!empty($member_names))
  312. foreach ($vars_to_integrate as $var)
  313. call_integration_hook('integrate_change_member_data', array($member_names, $var, $data[$var], $knownInts, $knownFloats));
  314. }
  315. }
  316. $setString = '';
  317. foreach ($data as $var => $val)
  318. {
  319. $type = 'string';
  320. if (in_array($var, $knownInts))
  321. $type = 'int';
  322. elseif (in_array($var, $knownFloats))
  323. $type = 'float';
  324. elseif ($var == 'birthdate')
  325. $type = 'date';
  326. // Doing an increment?
  327. if ($type == 'int' && ($val === '+' || $val === '-'))
  328. {
  329. $val = $var . ' ' . $val . ' 1';
  330. $type = 'raw';
  331. }
  332. // Ensure posts, instant_messages, and unread_messages don't overflow or underflow.
  333. if (in_array($var, array('posts', 'instant_messages', 'unread_messages')))
  334. {
  335. if (preg_match('~^' . $var . ' (\+ |- |\+ -)([\d]+)~', $val, $match))
  336. {
  337. if ($match[1] != '+ ')
  338. $val = 'CASE WHEN ' . $var . ' <= ' . abs($match[2]) . ' THEN 0 ELSE ' . $val . ' END';
  339. $type = 'raw';
  340. }
  341. }
  342. $setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},';
  343. $parameters['p_' . $var] = $val;
  344. }
  345. $smcFunc['db_query']('', '
  346. UPDATE {db_prefix}members
  347. SET' . substr($setString, 0, -1) . '
  348. WHERE ' . $condition,
  349. $parameters
  350. );
  351. updateStats('postgroups', $members, array_keys($data));
  352. // Clear any caching?
  353. if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && !empty($members))
  354. {
  355. if (!is_array($members))
  356. $members = array($members);
  357. foreach ($members as $member)
  358. {
  359. if ($modSettings['cache_enable'] >= 3)
  360. {
  361. cache_put_data('member_data-profile-' . $member, null, 120);
  362. cache_put_data('member_data-normal-' . $member, null, 120);
  363. cache_put_data('member_data-minimal-' . $member, null, 120);
  364. }
  365. cache_put_data('user_settings-' . $member, null, 60);
  366. }
  367. }
  368. }
  369. /**
  370. * Updates the settings table as well as $modSettings... only does one at a time if $update is true.
  371. *
  372. * - updates both the settings table and $modSettings array.
  373. * - all of changeArray's indexes and values are assumed to have escaped apostrophes (')!
  374. * - if a variable is already set to what you want to change it to, that
  375. * variable will be skipped over; it would be unnecessary to reset.
  376. * - When use_update is true, UPDATEs will be used instead of REPLACE.
  377. * - when use_update is true, the value can be true or false to increment
  378. * or decrement it, respectively.
  379. *
  380. * @param array $changeArray
  381. * @param bool $update = false
  382. * @param bool $debug = false
  383. */
  384. function updateSettings($changeArray, $update = false, $debug = false)
  385. {
  386. global $modSettings, $smcFunc;
  387. if (empty($changeArray) || !is_array($changeArray))
  388. return;
  389. // In some cases, this may be better and faster, but for large sets we don't want so many UPDATEs.
  390. if ($update)
  391. {
  392. foreach ($changeArray as $variable => $value)
  393. {
  394. $smcFunc['db_query']('', '
  395. UPDATE {db_prefix}settings
  396. SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value}
  397. WHERE variable = {string:variable}',
  398. array(
  399. 'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value),
  400. 'variable' => $variable,
  401. )
  402. );
  403. $modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value);
  404. }
  405. // Clean out the cache and make sure the cobwebs are gone too.
  406. cache_put_data('modSettings', null, 90);
  407. return;
  408. }
  409. $replaceArray = array();
  410. foreach ($changeArray as $variable => $value)
  411. {
  412. // Don't bother if it's already like that ;).
  413. if (isset($modSettings[$variable]) && $modSettings[$variable] == $value)
  414. continue;
  415. // If the variable isn't set, but would only be set to nothing'ness, then don't bother setting it.
  416. elseif (!isset($modSettings[$variable]) && empty($value))
  417. continue;
  418. $replaceArray[] = array($variable, $value);
  419. $modSettings[$variable] = $value;
  420. }
  421. if (empty($replaceArray))
  422. return;
  423. $smcFunc['db_insert']('replace',
  424. '{db_prefix}settings',
  425. array('variable' => 'string-255', 'value' => 'string-65534'),
  426. $replaceArray,
  427. array('variable')
  428. );
  429. // Kill the cache - it needs redoing now, but we won't bother ourselves with that here.
  430. cache_put_data('modSettings', null, 90);
  431. }
  432. /**
  433. * Constructs a page list.
  434. *
  435. * - builds the page list, e.g. 1 ... 6 7 [8] 9 10 ... 15.
  436. * - flexible_start causes it to use "url.page" instead of "url;start=page".
  437. * - handles any wireless settings (adding special things to URLs.)
  438. * - very importantly, cleans up the start value passed, and forces it to
  439. * be a multiple of num_per_page.
  440. * - checks that start is not more than max_value.
  441. * - base_url should be the URL without any start parameter on it.
  442. * - uses the compactTopicPagesEnable and compactTopicPagesContiguous
  443. * settings to decide how to display the menu.
  444. *
  445. * an example is available near the function definition.
  446. * $pageindex = constructPageIndex($scripturl . '?board=' . $board, $_REQUEST['start'], $num_messages, $maxindex, true);
  447. *
  448. * @param string $base_url
  449. * @param int $start
  450. * @param int $max_value
  451. * @param int $num_per_page
  452. * @param bool $flexible_start = false
  453. * @param bool $show_prevnext = true
  454. */
  455. function constructPageIndex($base_url, &$start, $max_value, $num_per_page, $flexible_start = false, $show_prevnext = true)
  456. {
  457. global $modSettings, $context, $txt;
  458. // Save whether $start was less than 0 or not.
  459. $start = (int) $start;
  460. $start_invalid = $start < 0;
  461. // Make sure $start is a proper variable - not less than 0.
  462. if ($start_invalid)
  463. $start = 0;
  464. // Not greater than the upper bound.
  465. elseif ($start >= $max_value)
  466. $start = max(0, (int) $max_value - (((int) $max_value % (int) $num_per_page) == 0 ? $num_per_page : ((int) $max_value % (int) $num_per_page)));
  467. // And it has to be a multiple of $num_per_page!
  468. else
  469. $start = max(0, (int) $start - ((int) $start % (int) $num_per_page));
  470. $context['current_page'] = $start / $num_per_page;
  471. $base_link = '<a class="navPages" href="' . ($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d') . '">%2$s</a> ';
  472. // Compact pages is off or on?
  473. if (empty($modSettings['compactTopicPagesEnable']))
  474. {
  475. // Show the left arrow.
  476. $pageindex = $start == 0 ? ' ' : sprintf($base_link, $start - $num_per_page, '<span class="previous_page">' . $txt['prev'] . '</span>');
  477. // Show all the pages.
  478. $display_page = 1;
  479. for ($counter = 0; $counter < $max_value; $counter += $num_per_page)
  480. $pageindex .= $start == $counter && !$start_invalid ? '<span class="current_page"><strong>' . $display_page++ . '</strong></span> ' : sprintf($base_link, $counter, $display_page++);
  481. // Show the right arrow.
  482. $display_page = ($start + $num_per_page) > $max_value ? $max_value : ($start + $num_per_page);
  483. if ($start != $counter - $max_value && !$start_invalid)
  484. $pageindex .= $display_page > $counter - $num_per_page ? ' ' : sprintf($base_link, $display_page, '<span class="next_page">' . $txt['next'] . '</span>');
  485. }
  486. else
  487. {
  488. // If they didn't enter an odd value, pretend they did.
  489. $PageContiguous = (int) ($modSettings['compactTopicPagesContiguous'] - ($modSettings['compactTopicPagesContiguous'] % 2)) / 2;
  490. // Show the "prev page" link. (>prev page< 1 ... 6 7 [8] 9 10 ... 15 next page)
  491. if (!empty($start) && $show_prevnext)
  492. $pageindex = sprintf($base_link, $start - $num_per_page, '<span class="previous_page">' . $txt['prev'] . '</span>');
  493. else
  494. $pageindex = '';
  495. // Show the first page. (prev page >1< ... 6 7 [8] 9 10 ... 15)
  496. if ($start > $num_per_page * $PageContiguous)
  497. $pageindex .= sprintf($base_link, 0, '1');
  498. // Show the ... after the first page. (prev page 1 >...< 6 7 [8] 9 10 ... 15 next page)
  499. if ($start > $num_per_page * ($PageContiguous + 1))
  500. $pageindex .= '<span class="expand_pages" onclick="' . htmlspecialchars('expandPages(this, ' . JavaScriptEscape(($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d')) . ', ' . $num_per_page . ', ' . ($start - $num_per_page * $PageContiguous) . ', ' . $num_per_page . ');') . '" onmouseover="this.style.cursor = \'pointer\';"><strong> ... </strong></span>';
  501. // Show the pages before the current one. (prev page 1 ... >6 7< [8] 9 10 ... 15 next page)
  502. for ($nCont = $PageContiguous; $nCont >= 1; $nCont--)
  503. if ($start >= $num_per_page * $nCont)
  504. {
  505. $tmpStart = $start - $num_per_page * $nCont;
  506. $pageindex.= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
  507. }
  508. // Show the current page. (prev page 1 ... 6 7 >[8]< 9 10 ... 15 next page)
  509. if (!$start_invalid)
  510. $pageindex .= '<span class="current_page"><strong>' . ($start / $num_per_page + 1) . '</strong></span>';
  511. else
  512. $pageindex .= sprintf($base_link, $start, $start / $num_per_page + 1);
  513. // Show the pages after the current one... (prev page 1 ... 6 7 [8] >9 10< ... 15 next page)
  514. $tmpMaxPages = (int) (($max_value - 1) / $num_per_page) * $num_per_page;
  515. for ($nCont = 1; $nCont <= $PageContiguous; $nCont++)
  516. if ($start + $num_per_page * $nCont <= $tmpMaxPages)
  517. {
  518. $tmpStart = $start + $num_per_page * $nCont;
  519. $pageindex .= sprintf($base_link, $tmpStart, $tmpStart / $num_per_page + 1);
  520. }
  521. // Show the '...' part near the end. (prev page 1 ... 6 7 [8] 9 10 >...< 15 next page)
  522. if ($start + $num_per_page * ($PageContiguous + 1) < $tmpMaxPages)
  523. $pageindex .= '<span class="expand_pages" onclick="' . htmlspecialchars('expandPages(this, ' . JavaScriptEscape(($flexible_start ? $base_url : strtr($base_url, array('%' => '%%')) . ';start=%1$d')) . ', ' . ($start + $num_per_page * ($PageContiguous + 1)) . ', ' . $tmpMaxPages . ', ' . $num_per_page . ');') . '" onmouseover="this.style.cursor=\'pointer\';"><strong> ... </strong></span>';
  524. // Show the last number in the list. (prev page 1 ... 6 7 [8] 9 10 ... >15< next page)
  525. if ($start + $num_per_page * $PageContiguous < $tmpMaxPages)
  526. $pageindex .= sprintf($base_link, $tmpMaxPages, $tmpMaxPages / $num_per_page + 1);
  527. // Show the "next page" link. (prev page 1 ... 6 7 [8] 9 10 ... 15 >next page<)
  528. if ($start != $tmpMaxPages && $show_prevnext)
  529. $pageindex .= sprintf($base_link, $start + $num_per_page, '<span class="next_page">' . $txt['next'] . '</span>');
  530. }
  531. return $pageindex;
  532. }
  533. /**
  534. * Formats a number.
  535. * - uses the format of number_format to decide how to format the number.
  536. * for example, it might display "1 234,50".
  537. * - caches the formatting data from the setting for optimization.
  538. *
  539. * @param float $number
  540. * @param bool $override_decimal_count = false
  541. */
  542. function comma_format($number, $override_decimal_count = false)
  543. {
  544. global $txt;
  545. static $thousands_separator = null, $decimal_separator = null, $decimal_count = null;
  546. // Cache these values...
  547. if ($decimal_separator === null)
  548. {
  549. // Not set for whatever reason?
  550. if (empty($txt['number_format']) || preg_match('~^1([^\d]*)?234([^\d]*)(0*?)$~', $txt['number_format'], $matches) != 1)
  551. return $number;
  552. // Cache these each load...
  553. $thousands_separator = $matches[1];
  554. $decimal_separator = $matches[2];
  555. $decimal_count = strlen($matches[3]);
  556. }
  557. // Format the string with our friend, number_format.
  558. return number_format($number, (float) $number === $number ? ($override_decimal_count === false ? $decimal_count : $override_decimal_count) : 0, $decimal_separator, $thousands_separator);
  559. }
  560. /**
  561. * Format a time to make it look purdy.
  562. *
  563. * - returns a pretty formated version of time based on the user's format in $user_info['time_format'].
  564. * - applies all necessary time offsets to the timestamp, unless offset_type is set.
  565. * - if todayMod is set and show_today was not not specified or true, an
  566. * alternate format string is used to show the date with something to show it is "today" or "yesterday".
  567. * - performs localization (more than just strftime would do alone.)
  568. *
  569. * @param int $log_time
  570. * @param bool $show_today = true
  571. * @param string $offset_type = false
  572. */
  573. function timeformat($log_time, $show_today = true, $offset_type = false)
  574. {
  575. global $context, $user_info, $txt, $modSettings, $smcFunc;
  576. static $non_twelve_hour;
  577. // Offset the time.
  578. if (!$offset_type)
  579. $time = $log_time + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600;
  580. // Just the forum offset?
  581. elseif ($offset_type == 'forum')
  582. $time = $log_time + $modSettings['time_offset'] * 3600;
  583. else
  584. $time = $log_time;
  585. // We can't have a negative date (on Windows, at least.)
  586. if ($log_time < 0)
  587. $log_time = 0;
  588. // Today and Yesterday?
  589. if ($modSettings['todayMod'] >= 1 && $show_today === true)
  590. {
  591. // Get the current time.
  592. $nowtime = forum_time();
  593. $then = @getdate($time);
  594. $now = @getdate($nowtime);
  595. // Try to make something of a time format string...
  596. $s = strpos($user_info['time_format'], '%S') === false ? '' : ':%S';
  597. if (strpos($user_info['time_format'], '%H') === false && strpos($user_info['time_format'], '%T') === false)
  598. {
  599. $h = strpos($user_info['time_format'], '%l') === false ? '%I' : '%l';
  600. $today_fmt = $h . ':%M' . $s . ' %p';
  601. }
  602. else
  603. $today_fmt = '%H:%M' . $s;
  604. // Same day of the year, same year.... Today!
  605. if ($then['yday'] == $now['yday'] && $then['year'] == $now['year'])
  606. return $txt['today'] . timeformat($log_time, $today_fmt, $offset_type);
  607. // Day-of-year is one less and same year, or it's the first of the year and that's the last of the year...
  608. if ($modSettings['todayMod'] == '2' && (($then['yday'] == $now['yday'] - 1 && $then['year'] == $now['year']) || ($now['yday'] == 0 && $then['year'] == $now['year'] - 1) && $then['mon'] == 12 && $then['mday'] == 31))
  609. return $txt['yesterday'] . timeformat($log_time, $today_fmt, $offset_type);
  610. }
  611. $str = !is_bool($show_today) ? $show_today : $user_info['time_format'];
  612. if (setlocale(LC_TIME, $txt['lang_locale']))
  613. {
  614. if (!isset($non_twelve_hour))
  615. $non_twelve_hour = trim(strftime('%p')) === '';
  616. if ($non_twelve_hour && strpos($str, '%p') !== false)
  617. $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
  618. foreach (array('%a', '%A', '%b', '%B') as $token)
  619. if (strpos($str, $token) !== false)
  620. $str = str_replace($token, !empty($txt['lang_capitalize_dates']) ? $smcFunc['ucwords'](strftime($token, $time)) : strftime($token, $time), $str);
  621. }
  622. else
  623. {
  624. // Do-it-yourself time localization. Fun.
  625. foreach (array('%a' => 'days_short', '%A' => 'days', '%b' => 'months_short', '%B' => 'months') as $token => $text_label)
  626. if (strpos($str, $token) !== false)
  627. $str = str_replace($token, $txt[$text_label][(int) strftime($token === '%a' || $token === '%A' ? '%w' : '%m', $time)], $str);
  628. if (strpos($str, '%p') !== false)
  629. $str = str_replace('%p', (strftime('%H', $time) < 12 ? $txt['time_am'] : $txt['time_pm']), $str);
  630. }
  631. // Windows doesn't support %e; on some versions, strftime fails altogether if used, so let's prevent that.
  632. if ($context['server']['is_windows'] && strpos($str, '%e') !== false)
  633. $str = str_replace('%e', ltrim(strftime('%d', $time), '0'), $str);
  634. // Format any other characters..
  635. return strftime($str, $time);
  636. }
  637. /**
  638. * Removes special entities from strings. Compatibility...
  639. * Faster than html_entity_decode
  640. *
  641. * - removes the base entities ( &amp; &quot; &#039; &lt; and &gt;. ) from text with htmlspecialchars_decode
  642. * - additionally converts &nbsp with str_replace
  643. *
  644. * @param string $string
  645. * @return the string without entities
  646. */
  647. function un_htmlspecialchars($string)
  648. {
  649. $string = htmlspecialchars_decode($string, ENT_QUOTES);
  650. $string = str_replace('&nbsp;', ' ', $string);
  651. return $string;
  652. }
  653. /**
  654. * Shorten a subject + internationalization concerns.
  655. *
  656. * - shortens a subject so that it is either shorter than length, or that length plus an ellipsis.
  657. * - respects internationalization characters and entities as one character.
  658. * - avoids trailing entities.
  659. * - returns the shortened string.
  660. *
  661. * @param string $subject
  662. * @param int $len
  663. */
  664. function shorten_subject($subject, $len)
  665. {
  666. global $smcFunc;
  667. // It was already short enough!
  668. if ($smcFunc['strlen']($subject) <= $len)
  669. return $subject;
  670. // Shorten it by the length it was too long, and strip off junk from the end.
  671. return $smcFunc['substr']($subject, 0, $len) . '...';
  672. }
  673. /**
  674. * Gets the current time with offset.
  675. *
  676. * - always applies the offset in the time_offset setting.
  677. *
  678. * @param bool $use_user_offset = true if use_user_offset is true, applies the user's offset as well
  679. * @param int $timestamp = null
  680. * @return int seconds since the unix epoch
  681. */
  682. function forum_time($use_user_offset = true, $timestamp = null)
  683. {
  684. global $user_info, $modSettings;
  685. if ($timestamp === null)
  686. $timestamp = time();
  687. elseif ($timestamp == 0)
  688. return 0;
  689. return $timestamp + ($modSettings['time_offset'] + ($use_user_offset ? $user_info['time_offset'] : 0)) * 3600;
  690. }
  691. /**
  692. * Calculates all the possible permutations (orders) of array.
  693. * should not be called on huge arrays (bigger than like 10 elements.)
  694. * returns an array containing each permutation.
  695. *
  696. * @param array $array
  697. * @return array
  698. */
  699. function permute($array)
  700. {
  701. $orders = array($array);
  702. $n = count($array);
  703. $p = range(0, $n);
  704. for ($i = 1; $i < $n; null)
  705. {
  706. $p[$i]--;
  707. $j = $i % 2 != 0 ? $p[$i] : 0;
  708. $temp = $array[$i];
  709. $array[$i] = $array[$j];
  710. $array[$j] = $temp;
  711. for ($i = 1; $p[$i] == 0; $i++)
  712. $p[$i] = 1;
  713. $orders[] = $array;
  714. }
  715. return $orders;
  716. }
  717. /**
  718. * Parse bulletin board code in a string, as well as smileys optionally.
  719. *
  720. * - only parses bbc tags which are not disabled in disabledBBC.
  721. * - handles basic HTML, if enablePostHTML is on.
  722. * - caches the from/to replace regular expressions so as not to reload them every time a string is parsed.
  723. * - only parses smileys if smileys is true.
  724. * - does nothing if the enableBBC setting is off.
  725. * - uses the cache_id as a unique identifier to facilitate any caching it may do.
  726. * -returns the modified message.
  727. *
  728. * @param string $message
  729. * @param bool $smileys = true
  730. * @param string $cache_id = ''
  731. * @param array $parse_tags = null
  732. * @return string
  733. */
  734. function parse_bbc($message, $smileys = true, $cache_id = '', $parse_tags = array())
  735. {
  736. global $txt, $scripturl, $context, $modSettings, $user_info, $smcFunc;
  737. static $bbc_codes = array(), $itemcodes = array(), $no_autolink_tags = array();
  738. static $disabled;
  739. // Don't waste cycles
  740. if ($message === '')
  741. return '';
  742. // Clean up any cut/paste issues we may have
  743. $message = sanitizeMSCutPaste($message);
  744. // If the load average is too high, don't parse the BBC.
  745. if (!empty($context['load_average']) && !empty($modSettings['bbc']) && $context['load_average'] >= $modSettings['bbc'])
  746. {
  747. $context['disabled_parse_bbc'] = true;
  748. return $message;
  749. }
  750. if ($smileys !== null && ($smileys == '1' || $smileys == '0'))
  751. $smileys = (bool) $smileys;
  752. if (empty($modSettings['enableBBC']) && $message !== false)
  753. {
  754. if ($smileys === true)
  755. parsesmileys($message);
  756. return $message;
  757. }
  758. // If we are not doing every tag then we don't cache this run.
  759. if (!empty($parse_tags) && !empty($bbc_codes))
  760. {
  761. $temp_bbc = $bbc_codes;
  762. $bbc_codes = array();
  763. }
  764. // Allow mods access before entering the main parse_bbc loop
  765. call_integration_hook('integrate_pre_parsebbc', array($message, $smileys, $cache_id, $parse_tags));
  766. // Sift out the bbc for a performance improvement.
  767. if (empty($bbc_codes) || $message === false || !empty($parse_tags))
  768. {
  769. if (!empty($modSettings['disabledBBC']))
  770. {
  771. $temp = explode(',', strtolower($modSettings['disabledBBC']));
  772. foreach ($temp as $tag)
  773. $disabled[trim($tag)] = true;
  774. }
  775. if (empty($modSettings['enableEmbeddedFlash']))
  776. $disabled['flash'] = true;
  777. /* The following bbc are formatted as an array, with keys as follows:
  778. tag: the tag's name - should be lowercase!
  779. type: one of...
  780. - (missing): [tag]parsed content[/tag]
  781. - unparsed_equals: [tag=xyz]parsed content[/tag]
  782. - parsed_equals: [tag=parsed data]parsed content[/tag]
  783. - unparsed_content: [tag]unparsed content[/tag]
  784. - closed: [tag], [tag/], [tag /]
  785. - unparsed_commas: [tag=1,2,3]parsed content[/tag]
  786. - unparsed_commas_content: [tag=1,2,3]unparsed content[/tag]
  787. - unparsed_equals_content: [tag=...]unparsed content[/tag]
  788. parameters: an optional array of parameters, for the form
  789. [tag abc=123]content[/tag]. The array is an associative array
  790. where the keys are the parameter names, and the values are an
  791. array which may contain the following:
  792. - match: a regular expression to validate and match the value.
  793. - quoted: true if the value should be quoted.
  794. - validate: callback to evaluate on the data, which is $data.
  795. - value: a string in which to replace $1 with the data.
  796. either it or validate may be used, not both.
  797. - optional: true if the parameter is optional.
  798. test: a regular expression to test immediately after the tag's
  799. '=', ' ' or ']'. Typically, should have a \] at the end.
  800. Optional.
  801. content: only available for unparsed_content, closed,
  802. unparsed_commas_content, and unparsed_equals_content.
  803. $1 is replaced with the content of the tag. Parameters
  804. are replaced in the form {param}. For unparsed_commas_content,
  805. $2, $3, ..., $n are replaced.
  806. before: only when content is not used, to go before any
  807. content. For unparsed_equals, $1 is replaced with the value.
  808. For unparsed_commas, $1, $2, ..., $n are replaced.
  809. after: similar to before in every way, except that it is used
  810. when the tag is closed.
  811. disabled_content: used in place of content when the tag is
  812. disabled. For closed, default is '', otherwise it is '$1' if
  813. block_level is false, '<div>$1</div>' elsewise.
  814. disabled_before: used in place of before when disabled. Defaults
  815. to '<div>' if block_level, '' if not.
  816. disabled_after: used in place of after when disabled. Defaults
  817. to '</div>' if block_level, '' if not.
  818. block_level: set to true the tag is a "block level" tag, similar
  819. to HTML. Block level tags cannot be nested inside tags that are
  820. not block level, and will not be implicitly closed as easily.
  821. One break following a block level tag may also be removed.
  822. trim: if set, and 'inside' whitespace after the begin tag will be
  823. removed. If set to 'outside', whitespace after the end tag will
  824. meet the same fate.
  825. validate: except when type is missing or 'closed', a callback to
  826. validate the data as $data. Depending on the tag's type, $data
  827. may be a string or an array of strings (corresponding to the
  828. replacement.)
  829. quoted: when type is 'unparsed_equals' or 'parsed_equals' only,
  830. may be not set, 'optional', or 'required' corresponding to if
  831. the content may be quoted. This allows the parser to read
  832. [tag="abc]def[esdf]"] properly.
  833. require_parents: an array of tag names, or not set. If set, the
  834. enclosing tag *must* be one of the listed tags, or parsing won't
  835. occur.
  836. require_children: similar to require_parents, if set children
  837. won't be parsed if they are not in the list.
  838. disallow_children: similar to, but very different from,
  839. require_children, if it is set the listed tags will not be
  840. parsed inside the tag.
  841. parsed_tags_allowed: an array restricting what BBC can be in the
  842. parsed_equals parameter, if desired.
  843. */
  844. $codes = array(
  845. array(
  846. 'tag' => 'abbr',
  847. 'type' => 'unparsed_equals',
  848. 'before' => '<abbr title="$1">',
  849. 'after' => '</abbr>',
  850. 'quoted' => 'optional',
  851. 'disabled_after' => ' ($1)',
  852. ),
  853. array(
  854. 'tag' => 'acronym',
  855. 'type' => 'unparsed_equals',
  856. 'before' => '<acronym title="$1">',
  857. 'after' => '</acronym>',
  858. 'quoted' => 'optional',
  859. 'disabled_after' => ' ($1)',
  860. ),
  861. array(
  862. 'tag' => 'anchor',
  863. 'type' => 'unparsed_equals',
  864. 'test' => '[#]?([A-Za-z][A-Za-z0-9_\-]*)\]',
  865. 'before' => '<span id="post_$1">',
  866. 'after' => '</span>',
  867. ),
  868. array(
  869. 'tag' => 'b',
  870. 'before' => '<strong class="bbc_strong">',
  871. 'after' => '</strong>',
  872. ),
  873. array(
  874. 'tag' => 'bdo',
  875. 'type' => 'unparsed_equals',
  876. 'before' => '<bdo dir="$1">',
  877. 'after' => '</bdo>',
  878. 'test' => '(rtl|ltr)\]',
  879. 'block_level' => true,
  880. ),
  881. array(
  882. 'tag' => 'black',
  883. 'before' => '<span style="color: black;" class="bbc_color">',
  884. 'after' => '</span>',
  885. ),
  886. array(
  887. 'tag' => 'blue',
  888. 'before' => '<span style="color: blue;" class="bbc_color">',
  889. 'after' => '</span>',
  890. ),
  891. array(
  892. 'tag' => 'br',
  893. 'type' => 'closed',
  894. 'content' => '<br />',
  895. ),
  896. array(
  897. 'tag' => 'center',
  898. 'before' => '<div align="center">',
  899. 'after' => '</div>',
  900. 'block_level' => true,
  901. ),
  902. array(
  903. 'tag' => 'code',
  904. 'type' => 'unparsed_content',
  905. 'content' => '<div class="codeheader">' . $txt['code'] . ': <a href="javascript:void(0);" onclick="return smfSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div><pre class="bbc_code">$1</pre>',
  906. // @todo Maybe this can be simplified?
  907. 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
  908. global $context;
  909. if (!isset($disabled[\'code\']))
  910. {
  911. $php_parts = preg_split(\'~(&lt;\?php|\?&gt;)~\', $data, -1, PREG_SPLIT_DELIM_CAPTURE);
  912. for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
  913. {
  914. // Do PHP code coloring?
  915. if ($php_parts[$php_i] != \'&lt;?php\')
  916. continue;
  917. $php_string = \'\';
  918. while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?&gt;\')
  919. {
  920. $php_string .= $php_parts[$php_i];
  921. $php_parts[$php_i++] = \'\';
  922. }
  923. $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
  924. }
  925. // Fix the PHP code stuff...
  926. $data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode(\'\', $php_parts));
  927. $data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);
  928. // Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
  929. if ($context[\'browser\'][\'is_opera\'])
  930. $data .= \'&nbsp;\';
  931. }'),
  932. 'block_level' => true,
  933. ),
  934. array(
  935. 'tag' => 'code',
  936. 'type' => 'unparsed_equals_content',
  937. 'content' => '<div class="codeheader">' . $txt['code'] . ': ($2) <a href="#" onclick="return smfSelectText(this);" class="codeoperation">' . $txt['code_select'] . '</a></div><pre class="bbc_code">$1</pre>',
  938. // @todo Maybe this can be simplified?
  939. 'validate' => isset($disabled['code']) ? null : create_function('&$tag, &$data, $disabled', '
  940. global $context;
  941. if (!isset($disabled[\'code\']))
  942. {
  943. $php_parts = preg_split(\'~(&lt;\?php|\?&gt;)~\', $data[0], -1, PREG_SPLIT_DELIM_CAPTURE);
  944. for ($php_i = 0, $php_n = count($php_parts); $php_i < $php_n; $php_i++)
  945. {
  946. // Do PHP code coloring?
  947. if ($php_parts[$php_i] != \'&lt;?php\')
  948. continue;
  949. $php_string = \'\';
  950. while ($php_i + 1 < count($php_parts) && $php_parts[$php_i] != \'?&gt;\')
  951. {
  952. $php_string .= $php_parts[$php_i];
  953. $php_parts[$php_i++] = \'\';
  954. }
  955. $php_parts[$php_i] = highlight_php_code($php_string . $php_parts[$php_i]);
  956. }
  957. // Fix the PHP code stuff...
  958. $data[0] = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", implode(\'\', $php_parts));
  959. $data[0] = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data[0]);
  960. // Recent Opera bug requiring temporary fix. &nsbp; is needed before </code> to avoid broken selection.
  961. if ($context[\'browser\'][\'is_opera\'])
  962. $data[0] .= \'&nbsp;\';
  963. }'),
  964. 'block_level' => true,
  965. ),
  966. array(
  967. 'tag' => 'color',
  968. 'type' => 'unparsed_equals',
  969. 'test' => '(#[\da-fA-F]{3}|#[\da-fA-F]{6}|[A-Za-z]{1,20}|rgb\(\d{1,3}, ?\d{1,3}, ?\d{1,3}\))\]',
  970. 'before' => '<span style="color: $1;" class="bbc_color">',
  971. 'after' => '</span>',
  972. ),
  973. array(
  974. 'tag' => 'email',
  975. 'type' => 'unparsed_content',
  976. 'content' => '<a href="mailto:$1" class="bbc_email">$1</a>',
  977. // @todo Should this respect guest_hideContacts?
  978. 'validate' => create_function('&$tag, &$data, $disabled', '$data = strtr($data, array(\'<br />\' => \'\'));'),
  979. ),
  980. array(
  981. 'tag' => 'email',
  982. 'type' => 'unparsed_equals',
  983. 'before' => '<a href="mailto:$1" class="bbc_email">',
  984. 'after' => '</a>',
  985. // @todo Should this respect guest_hideContacts?
  986. 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
  987. 'disabled_after' => ' ($1)',
  988. ),
  989. array(
  990. 'tag' => 'flash',
  991. 'type' => 'unparsed_commas_content',
  992. 'test' => '\d+,\d+\]',
  993. 'content' => (isBrowser('ie') ? '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="$2" height="$3"><param name="movie" value="$1" /><param name="play" value="true" /><param name="loop" value="true" /><param name="quality" value="high" /><param name="AllowScriptAccess" value="never" /><embed src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never" /><noembed><a href="$1" target="_blank" class="new_win">$1</a></noembed></object>' : '<embed type="application/x-shockwave-flash" src="$1" width="$2" height="$3" play="true" loop="true" quality="high" AllowScriptAccess="never" /><noembed><a href="$1" target="_blank" class="new_win">$1</a></noembed>'),
  994. 'validate' => create_function('&$tag, &$data, $disabled', '
  995. if (isset($disabled[\'url\']))
  996. $tag[\'content\'] = \'$1\';
  997. elseif (strpos($data[0], \'http://\') !== 0 && strpos($data[0], \'https://\') !== 0)
  998. $data[0] = \'http://\' . $data[0];
  999. '),
  1000. 'disabled_content' => '<a href="$1" target="_blank" class="new_win">$1</a>',
  1001. ),
  1002. array(
  1003. 'tag' => 'font',
  1004. 'type' => 'unparsed_equals',
  1005. 'test' => '[A-Za-z0-9_,\-\s]+?\]',
  1006. 'before' => '<span style="font-family: $1;" class="bbc_font">',
  1007. 'after' => '</span>',
  1008. ),
  1009. array(
  1010. 'tag' => 'ftp',
  1011. 'type' => 'unparsed_content',
  1012. 'content' => '<a href="$1" class="bbc_ftp new_win" target="_blank">$1</a>',
  1013. 'validate' => create_function('&$tag, &$data, $disabled', '
  1014. $data = strtr($data, array(\'<br />\' => \'\'));
  1015. if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0)
  1016. $data = \'ftp://\' . $data;
  1017. '),
  1018. ),
  1019. array(
  1020. 'tag' => 'ftp',
  1021. 'type' => 'unparsed_equals',
  1022. 'before' => '<a href="$1" class="bbc_ftp new_win" target="_blank">',
  1023. 'after' => '</a>',
  1024. 'validate' => create_function('&$tag, &$data, $disabled', '
  1025. if (strpos($data, \'ftp://\') !== 0 && strpos($data, \'ftps://\') !== 0)
  1026. $data = \'ftp://\' . $data;
  1027. '),
  1028. 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
  1029. 'disabled_after' => ' ($1)',
  1030. ),
  1031. array(
  1032. 'tag' => 'glow',
  1033. 'type' => 'unparsed_commas',
  1034. 'test' => '[#0-9a-zA-Z\-]{3,12},([012]\d{1,2}|\d{1,2})(,[^]]+)?\]',
  1035. 'before' => isBrowser('ie') ? '<table style="border-collapse: collapse; border-spacing: 0;display: inline; vertical-align: middle; font: inherit;"><tr><td style="filter: Glow(color=$1, strength=$2); font: inherit;">' : '<span style="text-shadow: $1 1px 1px 1px">',
  1036. 'after' => isBrowser('ie') ? '</td></tr></table> ' : '</span>',
  1037. ),
  1038. array(
  1039. 'tag' => 'green',
  1040. 'before' => '<span style="color: green;" class="bbc_color">',
  1041. 'after' => '</span>',
  1042. ),
  1043. array(
  1044. 'tag' => 'html',
  1045. 'type' => 'unparsed_content',
  1046. 'content' => '$1',
  1047. 'block_level' => true,
  1048. 'disabled_content' => '$1',
  1049. ),
  1050. array(
  1051. 'tag' => 'hr',
  1052. 'type' => 'closed',
  1053. 'content' => '<hr />',
  1054. 'block_level' => true,
  1055. ),
  1056. array(
  1057. 'tag' => 'i',
  1058. 'before' => '<em>',
  1059. 'after' => '</em>',
  1060. ),
  1061. array(
  1062. 'tag' => 'img',
  1063. 'type' => 'unparsed_content',
  1064. 'parameters' => array(
  1065. 'alt' => array('optional' => true),
  1066. 'width' => array('optional' => true, 'value' => ' width="$1"', 'match' => '(\d+)'),
  1067. 'height' => array('optional' => true, 'value' => ' height="$1"', 'match' => '(\d+)'),
  1068. ),
  1069. 'content' => '<img src="$1" alt="{alt}"{width}{height} class="bbc_img resized" />',
  1070. 'validate' => create_function('&$tag, &$data, $disabled', '
  1071. $data = strtr($data, array(\'<br />\' => \'\'));
  1072. if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
  1073. $data = \'http://\' . $data;
  1074. '),
  1075. 'disabled_content' => '($1)',
  1076. ),
  1077. array(
  1078. 'tag' => 'img',
  1079. 'type' => 'unparsed_content',
  1080. 'content' => '<img src="$1" alt="" class="bbc_img" />',
  1081. 'validate' => create_function('&$tag, &$data, $disabled', '
  1082. $data = strtr($data, array(\'<br />\' => \'\'));
  1083. if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
  1084. $data = \'http://\' . $data;
  1085. '),
  1086. 'disabled_content' => '($1)',
  1087. ),
  1088. array(
  1089. 'tag' => 'iurl',
  1090. 'type' => 'unparsed_content',
  1091. 'content' => '<a href="$1" class="bbc_link">$1</a>',
  1092. 'validate' => create_function('&$tag, &$data, $disabled', '
  1093. $data = strtr($data, array(\'<br />\' => \'\'));
  1094. if (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
  1095. $data = \'http://\' . $data;
  1096. '),
  1097. ),
  1098. array(
  1099. 'tag' => 'iurl',
  1100. 'type' => 'unparsed_equals',
  1101. 'before' => '<a href="$1" class="bbc_link">',
  1102. 'after' => '</a>',
  1103. 'validate' => create_function('&$tag, &$data, $disabled', '
  1104. if (substr($data, 0, 1) == \'#\')
  1105. $data = \'#post_\' . substr($data, 1);
  1106. elseif (strpos($data, \'http://\') !== 0 && strpos($data, \'https://\') !== 0)
  1107. $data = \'http://\' . $data;
  1108. '),
  1109. 'disallow_children' => array('email', 'ftp', 'url', 'iurl'),
  1110. 'disabled_after' => ' ($1)',
  1111. ),
  1112. array(
  1113. 'tag' => 'left',
  1114. 'before' => '<div style="text-align: left;">',
  1115. 'after' => '</div>',
  1116. 'block_level' => true,
  1117. ),
  1118. array(
  1119. 'tag' => 'li',
  1120. 'before' => '<li>',
  1121. 'after' => '</li>',
  1122. 'trim' => 'outside',
  1123. 'require_parents' => array('list'),
  1124. 'block_level' => true,
  1125. 'disabled_before' => '',
  1126. 'disabled_after' => '<br />',
  1127. ),
  1128. array(
  1129. 'tag' => 'list',
  1130. 'before' => '<ul class="bbc_list">',
  1131. 'after' => '</ul>',
  1132. 'trim' => 'inside',
  1133. 'require_children' => array('li', 'list'),
  1134. 'block_level' => true,
  1135. ),
  1136. array(
  1137. 'tag' => 'list',
  1138. 'parameters' => array(
  1139. 'type' => array('match' => '(none|disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-alpha|upper-alpha|lower-greek|lower-latin|upper-latin|hebrew|armenian|georgian|cjk-ideographic|hiragana|katakana|hiragana-iroha|katakana-iroha)'),
  1140. ),
  1141. 'before' => '<ul class="bbc_list" style="list-style-type: {type};">',
  1142. 'after' => '</ul>',
  1143. 'trim' => 'inside',
  1144. 'require_children' => array('li'),
  1145. 'block_level' => true,
  1146. ),
  1147. array(
  1148. 'tag' => 'ltr',
  1149. 'before' => '<div dir="ltr">',
  1150. 'after' => '</div>',
  1151. 'block_level' => true,
  1152. ),
  1153. array(
  1154. 'tag' => 'me',
  1155. 'type' => 'unparsed_equals',
  1156. 'before' => '<div class="meaction">* $1 ',
  1157. 'after' => '</div>',
  1158. 'quoted' => 'optional',
  1159. 'block_level' => true,
  1160. 'disabled_before' => '/me ',
  1161. 'disabled_after' => '<br />',
  1162. ),
  1163. array(
  1164. 'tag' => 'move',
  1165. 'before' => '<marquee>',
  1166. 'after' => '</marquee>',
  1167. 'block_level' => true,
  1168. 'disallow_children' => array('move'),
  1169. ),
  1170. array(
  1171. 'tag' => 'nobbc',
  1172. 'type' => 'unparsed_content',
  1173. 'content' => '$1',
  1174. ),
  1175. array(
  1176. 'tag' => 'php',
  1177. 'type' => 'unparsed_content',
  1178. 'content' => '<span class="phpcode">$1</span>',
  1179. 'validate' => isset($disabled['php']) ? null : create_function('&$tag, &$data, $disabled', '
  1180. if (!isset($disabled[\'php\']))
  1181. {
  1182. $add_begin = substr(trim($data), 0, 5) != \'&lt;?\';
  1183. $data = highlight_php_code($add_begin ? \'&lt;?php \' . $data . \'?&gt;\' : $data);
  1184. if ($add_begin)
  1185. $data = preg_replace(array(\'~^(.+?)&lt;\?.{0,40}?php(?:&nbsp;|\s)~\', \'~\?&gt;((?:</(font|span)>)*)$~\'), \'$1\', $data, 2);
  1186. // Fix the PHP code stuff...
  1187. $data = str_replace("<pre style=\"display: inline;\">\t</pre>", "\t", $data);
  1188. $data = str_replace("\t", "<span style=\"white-space: pre;\">\t</span>", $data);
  1189. }'),
  1190. 'block_level' => false,
  1191. 'disabled_content' => '$1',
  1192. ),
  1193. array(
  1194. 'tag' => 'pre',
  1195. 'before' => '<pre>',
  1196. 'after' => '</pre>',
  1197. ),
  1198. array(
  1199. 'tag' => 'quote',
  1200. 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote'] . '</div></div><blockquote>',
  1201. 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
  1202. 'block_level' => true,
  1203. ),
  1204. array(
  1205. 'tag' => 'quote',
  1206. 'parameters' => array(
  1207. 'author' => array('match' => '(.{1,192}?)', 'quoted' => true),
  1208. ),
  1209. 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': {author}</div></div><blockquote>',
  1210. 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
  1211. 'block_level' => true,
  1212. ),
  1213. array(
  1214. 'tag' => 'quote',
  1215. 'type' => 'parsed_equals',
  1216. 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': $1</div></div><blockquote>',
  1217. 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
  1218. 'quoted' => 'optional',
  1219. // Don't allow everything to be embedded with the author name.
  1220. 'parsed_tags_allowed' => array('url', 'iurl', 'ftp'),
  1221. 'block_level' => true,
  1222. ),
  1223. array(
  1224. 'tag' => 'quote',
  1225. 'parameters' => array(
  1226. 'author' => array('match' => '([^<>]{1,192}?)'),
  1227. 'link' => array('match' => '(?:board=\d+;)?((?:topic|threadid)=[\dmsg#\./]{1,40}(?:;start=[\dmsg#\./]{1,40})?|action=profile;u=\d+)'),
  1228. 'date' => array('match' => '(\d+)', 'validate' => 'timeformat'),
  1229. ),
  1230. 'before' => '<div class="quoteheader"><div class="topslice_quote"><a href="' . $scripturl . '?{link}">' . $txt['quote_from'] . ': {author} ' . $txt['search_on'] . ' {date}</a></div></div><blockquote>',
  1231. 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
  1232. 'block_level' => true,
  1233. ),
  1234. array(
  1235. 'tag' => 'quote',
  1236. 'parameters' => array(
  1237. 'author' => array('match' => '(.{1,192}?)'),
  1238. ),
  1239. 'before' => '<div class="quoteheader"><div class="topslice_quote">' . $txt['quote_from'] . ': {author}</div></div><blockquote>',
  1240. 'after' => '</blockquote><div class="quotefooter"><div class="botslice_quote"></div></div>',
  1241. 'block_level' => true,
  1242. ),
  1243. array(
  1244. 'tag' => 'red',
  1245. 'before' => '<span style="color: red;" class="bbc_color">',
  1246. 'after' => '</span>',
  1247. ),
  1248. array(
  1249. 'tag' => 'right',
  1250. 'before' => '<div style="text-align: right;">',
  1251. 'after' => '</div>',
  1252. 'block_level' => true,
  1253. ),
  1254. array(
  1255. 'tag' => 'rtl',
  1256. 'before' => '<div dir="rtl">',
  1257. 'after' => '</div>',
  1258. 'block_level' => true,
  1259. ),
  1260. array(
  1261. 'tag' => 's',
  1262. 'before' => '<del>',
  1263. 'after' => '</del>',
  1264. ),
  1265. array(
  1266. 'tag' => 'shadow',
  1267. 'type' => 'unparsed_commas',
  1268. 'test' => '[#0-9a-zA-Z\-]{3,12},(left|right|top|bottom|[012…

Large files files are truncated, but you can click here to view the full file