PageRenderTime 80ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/phpBB/includes/functions.php

http://github.com/phpbb/phpbb3
PHP | 4370 lines | 3099 code | 504 blank | 767 comment | 435 complexity | 3f9c5c3c1d37fdfa22144f6c87bea30f MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * This file is part of the phpBB Forum Software package.
  5. *
  6. * @copyright (c) phpBB Limited <https://www.phpbb.com>
  7. * @license GNU General Public License, version 2 (GPL-2.0)
  8. *
  9. * For full copyright and license information, please see
  10. * the docs/CREDITS.txt file.
  11. *
  12. */
  13. /**
  14. * @ignore
  15. */
  16. if (!defined('IN_PHPBB'))
  17. {
  18. exit;
  19. }
  20. // Common global functions
  21. /**
  22. * Generates an alphanumeric random string of given length
  23. *
  24. * @param int $num_chars Length of random string, defaults to 8.
  25. * This number should be less or equal than 64.
  26. *
  27. * @return string
  28. */
  29. function gen_rand_string($num_chars = 8)
  30. {
  31. $range = array_merge(range('A', 'Z'), range(0, 9));
  32. $size = count($range);
  33. $output = '';
  34. for ($i = 0; $i < $num_chars; $i++)
  35. {
  36. $rand = random_int(0, $size-1);
  37. $output .= $range[$rand];
  38. }
  39. return $output;
  40. }
  41. /**
  42. * Generates a user-friendly alphanumeric random string of given length
  43. * We remove 0 and O so users cannot confuse those in passwords etc.
  44. *
  45. * @param int $num_chars Length of random string, defaults to 8.
  46. * This number should be less or equal than 64.
  47. *
  48. * @return string
  49. */
  50. function gen_rand_string_friendly($num_chars = 8)
  51. {
  52. $range = array_merge(range('A', 'N'), range('P', 'Z'), range(1, 9));
  53. $size = count($range);
  54. $output = '';
  55. for ($i = 0; $i < $num_chars; $i++)
  56. {
  57. $rand = random_int(0, $size-1);
  58. $output .= $range[$rand];
  59. }
  60. return $output;
  61. }
  62. /**
  63. * Return unique id
  64. */
  65. function unique_id()
  66. {
  67. return strtolower(gen_rand_string(16));
  68. }
  69. /**
  70. * Wrapper for mt_rand() which allows swapping $min and $max parameters.
  71. *
  72. * PHP does not allow us to swap the order of the arguments for mt_rand() anymore.
  73. * (since PHP 5.3.4, see http://bugs.php.net/46587)
  74. *
  75. * @param int $min Lowest value to be returned
  76. * @param int $max Highest value to be returned
  77. *
  78. * @return int Random integer between $min and $max (or $max and $min)
  79. */
  80. function phpbb_mt_rand($min, $max)
  81. {
  82. return ($min > $max) ? mt_rand($max, $min) : mt_rand($min, $max);
  83. }
  84. /**
  85. * Wrapper for getdate() which returns the equivalent array for UTC timestamps.
  86. *
  87. * @param int $time Unix timestamp (optional)
  88. *
  89. * @return array Returns an associative array of information related to the timestamp.
  90. * See http://www.php.net/manual/en/function.getdate.php
  91. */
  92. function phpbb_gmgetdate($time = false)
  93. {
  94. if ($time === false)
  95. {
  96. $time = time();
  97. }
  98. // getdate() interprets timestamps in local time.
  99. // What follows uses the fact that getdate() and
  100. // date('Z') balance each other out.
  101. return getdate($time - date('Z'));
  102. }
  103. /**
  104. * Return formatted string for filesizes
  105. *
  106. * @param mixed $value filesize in bytes
  107. * (non-negative number; int, float or string)
  108. * @param bool $string_only true if language string should be returned
  109. * @param array $allowed_units only allow these units (data array indexes)
  110. *
  111. * @return mixed data array if $string_only is false
  112. */
  113. function get_formatted_filesize($value, $string_only = true, $allowed_units = false)
  114. {
  115. global $user;
  116. $available_units = array(
  117. 'tb' => array(
  118. 'min' => 1099511627776, // pow(2, 40)
  119. 'index' => 4,
  120. 'si_unit' => 'TB',
  121. 'iec_unit' => 'TIB',
  122. ),
  123. 'gb' => array(
  124. 'min' => 1073741824, // pow(2, 30)
  125. 'index' => 3,
  126. 'si_unit' => 'GB',
  127. 'iec_unit' => 'GIB',
  128. ),
  129. 'mb' => array(
  130. 'min' => 1048576, // pow(2, 20)
  131. 'index' => 2,
  132. 'si_unit' => 'MB',
  133. 'iec_unit' => 'MIB',
  134. ),
  135. 'kb' => array(
  136. 'min' => 1024, // pow(2, 10)
  137. 'index' => 1,
  138. 'si_unit' => 'KB',
  139. 'iec_unit' => 'KIB',
  140. ),
  141. 'b' => array(
  142. 'min' => 0,
  143. 'index' => 0,
  144. 'si_unit' => 'BYTES', // Language index
  145. 'iec_unit' => 'BYTES', // Language index
  146. ),
  147. );
  148. foreach ($available_units as $si_identifier => $unit_info)
  149. {
  150. if (!empty($allowed_units) && $si_identifier != 'b' && !in_array($si_identifier, $allowed_units))
  151. {
  152. continue;
  153. }
  154. if ($value >= $unit_info['min'])
  155. {
  156. $unit_info['si_identifier'] = $si_identifier;
  157. break;
  158. }
  159. }
  160. unset($available_units);
  161. for ($i = 0; $i < $unit_info['index']; $i++)
  162. {
  163. $value /= 1024;
  164. }
  165. $value = round($value, 2);
  166. // Lookup units in language dictionary
  167. $unit_info['si_unit'] = (isset($user->lang[$unit_info['si_unit']])) ? $user->lang[$unit_info['si_unit']] : $unit_info['si_unit'];
  168. $unit_info['iec_unit'] = (isset($user->lang[$unit_info['iec_unit']])) ? $user->lang[$unit_info['iec_unit']] : $unit_info['iec_unit'];
  169. // Default to IEC
  170. $unit_info['unit'] = $unit_info['iec_unit'];
  171. if (!$string_only)
  172. {
  173. $unit_info['value'] = $value;
  174. return $unit_info;
  175. }
  176. return $value . ' ' . $unit_info['unit'];
  177. }
  178. /**
  179. * Determine whether we are approaching the maximum execution time. Should be called once
  180. * at the beginning of the script in which it's used.
  181. * @return bool Either true if the maximum execution time is nearly reached, or false
  182. * if some time is still left.
  183. */
  184. function still_on_time($extra_time = 15)
  185. {
  186. static $max_execution_time, $start_time;
  187. $current_time = microtime(true);
  188. if (empty($max_execution_time))
  189. {
  190. $max_execution_time = (function_exists('ini_get')) ? (int) @ini_get('max_execution_time') : (int) @get_cfg_var('max_execution_time');
  191. // If zero, then set to something higher to not let the user catch the ten seconds barrier.
  192. if ($max_execution_time === 0)
  193. {
  194. $max_execution_time = 50 + $extra_time;
  195. }
  196. $max_execution_time = min(max(10, ($max_execution_time - $extra_time)), 50);
  197. // For debugging purposes
  198. // $max_execution_time = 10;
  199. global $starttime;
  200. $start_time = (empty($starttime)) ? $current_time : $starttime;
  201. }
  202. return (ceil($current_time - $start_time) < $max_execution_time) ? true : false;
  203. }
  204. /**
  205. * Wrapper for version_compare() that allows using uppercase A and B
  206. * for alpha and beta releases.
  207. *
  208. * See http://www.php.net/manual/en/function.version-compare.php
  209. *
  210. * @param string $version1 First version number
  211. * @param string $version2 Second version number
  212. * @param string $operator Comparison operator (optional)
  213. *
  214. * @return mixed Boolean (true, false) if comparison operator is specified.
  215. * Integer (-1, 0, 1) otherwise.
  216. */
  217. function phpbb_version_compare($version1, $version2, $operator = null)
  218. {
  219. $version1 = strtolower($version1);
  220. $version2 = strtolower($version2);
  221. if (is_null($operator))
  222. {
  223. return version_compare($version1, $version2);
  224. }
  225. else
  226. {
  227. return version_compare($version1, $version2, $operator);
  228. }
  229. }
  230. // functions used for building option fields
  231. /**
  232. * Pick a language, any language ...
  233. */
  234. function language_select($default = '')
  235. {
  236. global $db;
  237. $sql = 'SELECT lang_iso, lang_local_name
  238. FROM ' . LANG_TABLE . '
  239. ORDER BY lang_english_name';
  240. $result = $db->sql_query($sql);
  241. $lang_options = '';
  242. while ($row = $db->sql_fetchrow($result))
  243. {
  244. $selected = ($row['lang_iso'] == $default) ? ' selected="selected"' : '';
  245. $lang_options .= '<option value="' . $row['lang_iso'] . '"' . $selected . '>' . $row['lang_local_name'] . '</option>';
  246. }
  247. $db->sql_freeresult($result);
  248. return $lang_options;
  249. }
  250. /**
  251. * Pick a template/theme combo,
  252. */
  253. function style_select($default = '', $all = false)
  254. {
  255. global $db;
  256. $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
  257. $sql = 'SELECT style_id, style_name
  258. FROM ' . STYLES_TABLE . "
  259. $sql_where
  260. ORDER BY style_name";
  261. $result = $db->sql_query($sql);
  262. $style_options = '';
  263. while ($row = $db->sql_fetchrow($result))
  264. {
  265. $selected = ($row['style_id'] == $default) ? ' selected="selected"' : '';
  266. $style_options .= '<option value="' . $row['style_id'] . '"' . $selected . '>' . $row['style_name'] . '</option>';
  267. }
  268. $db->sql_freeresult($result);
  269. return $style_options;
  270. }
  271. /**
  272. * Format the timezone offset with hours and minutes
  273. *
  274. * @param int $tz_offset Timezone offset in seconds
  275. * @param bool $show_null Whether null offsets should be shown
  276. * @return string Normalized offset string: -7200 => -02:00
  277. * 16200 => +04:30
  278. */
  279. function phpbb_format_timezone_offset($tz_offset, $show_null = false)
  280. {
  281. $sign = ($tz_offset < 0) ? '-' : '+';
  282. $time_offset = abs($tz_offset);
  283. if ($time_offset == 0 && $show_null == false)
  284. {
  285. return '';
  286. }
  287. $offset_seconds = $time_offset % 3600;
  288. $offset_minutes = $offset_seconds / 60;
  289. $offset_hours = ($time_offset - $offset_seconds) / 3600;
  290. $offset_string = sprintf("%s%02d:%02d", $sign, $offset_hours, $offset_minutes);
  291. return $offset_string;
  292. }
  293. /**
  294. * Compares two time zone labels.
  295. * Arranges them in increasing order by timezone offset.
  296. * Places UTC before other timezones in the same offset.
  297. */
  298. function phpbb_tz_select_compare($a, $b)
  299. {
  300. $a_sign = $a[3];
  301. $b_sign = $b[3];
  302. if ($a_sign != $b_sign)
  303. {
  304. return $a_sign == '-' ? -1 : 1;
  305. }
  306. $a_offset = substr($a, 4, 5);
  307. $b_offset = substr($b, 4, 5);
  308. if ($a_offset == $b_offset)
  309. {
  310. $a_name = substr($a, 12);
  311. $b_name = substr($b, 12);
  312. if ($a_name == $b_name)
  313. {
  314. return 0;
  315. }
  316. else if ($a_name == 'UTC')
  317. {
  318. return -1;
  319. }
  320. else if ($b_name == 'UTC')
  321. {
  322. return 1;
  323. }
  324. else
  325. {
  326. return $a_name < $b_name ? -1 : 1;
  327. }
  328. }
  329. else
  330. {
  331. if ($a_sign == '-')
  332. {
  333. return $a_offset > $b_offset ? -1 : 1;
  334. }
  335. else
  336. {
  337. return $a_offset < $b_offset ? -1 : 1;
  338. }
  339. }
  340. }
  341. /**
  342. * Return list of timezone identifiers
  343. * We also add the selected timezone if we can create an object with it.
  344. * DateTimeZone::listIdentifiers seems to not add all identifiers to the list,
  345. * because some are only kept for backward compatible reasons. If the user has
  346. * a deprecated value, we add it here, so it can still be kept. Once the user
  347. * changed his value, there is no way back to deprecated values.
  348. *
  349. * @param string $selected_timezone Additional timezone that shall
  350. * be added to the list of identiers
  351. * @return array DateTimeZone::listIdentifiers and additional
  352. * selected_timezone if it is a valid timezone.
  353. */
  354. function phpbb_get_timezone_identifiers($selected_timezone)
  355. {
  356. $timezones = DateTimeZone::listIdentifiers();
  357. if (!in_array($selected_timezone, $timezones))
  358. {
  359. try
  360. {
  361. // Add valid timezones that are currently selected but not returned
  362. // by DateTimeZone::listIdentifiers
  363. $validate_timezone = new DateTimeZone($selected_timezone);
  364. $timezones[] = $selected_timezone;
  365. }
  366. catch (\Exception $e)
  367. {
  368. }
  369. }
  370. return $timezones;
  371. }
  372. /**
  373. * Options to pick a timezone and date/time
  374. *
  375. * @param \phpbb\template\template $template phpBB template object
  376. * @param \phpbb\user $user Object of the current user
  377. * @param string $default A timezone to select
  378. * @param boolean $truncate Shall we truncate the options text
  379. *
  380. * @return array Returns an array containing the options for the time selector.
  381. */
  382. function phpbb_timezone_select($template, $user, $default = '', $truncate = false)
  383. {
  384. static $timezones;
  385. $default_offset = '';
  386. if (!isset($timezones))
  387. {
  388. $unsorted_timezones = phpbb_get_timezone_identifiers($default);
  389. $timezones = array();
  390. foreach ($unsorted_timezones as $timezone)
  391. {
  392. $tz = new DateTimeZone($timezone);
  393. $dt = $user->create_datetime('now', $tz);
  394. $offset = $dt->getOffset();
  395. $current_time = $dt->format($user->lang['DATETIME_FORMAT'], true);
  396. $offset_string = phpbb_format_timezone_offset($offset, true);
  397. $timezones['UTC' . $offset_string . ' - ' . $timezone] = array(
  398. 'tz' => $timezone,
  399. 'offset' => $offset_string,
  400. 'current' => $current_time,
  401. );
  402. if ($timezone === $default)
  403. {
  404. $default_offset = 'UTC' . $offset_string;
  405. }
  406. }
  407. unset($unsorted_timezones);
  408. uksort($timezones, 'phpbb_tz_select_compare');
  409. }
  410. $tz_select = $opt_group = '';
  411. foreach ($timezones as $key => $timezone)
  412. {
  413. if ($opt_group != $timezone['offset'])
  414. {
  415. // Generate tz_select for backwards compatibility
  416. $tz_select .= ($opt_group) ? '</optgroup>' : '';
  417. $tz_select .= '<optgroup label="' . $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']) . '">';
  418. $opt_group = $timezone['offset'];
  419. $template->assign_block_vars('timezone_select', array(
  420. 'LABEL' => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']),
  421. 'VALUE' => $key . ' - ' . $timezone['current'],
  422. ));
  423. $selected = (!empty($default_offset) && strpos($key, $default_offset) !== false) ? ' selected="selected"' : '';
  424. $template->assign_block_vars('timezone_date', array(
  425. 'VALUE' => $key . ' - ' . $timezone['current'],
  426. 'SELECTED' => !empty($selected),
  427. 'TITLE' => $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $timezone['current']),
  428. ));
  429. }
  430. $label = $timezone['tz'];
  431. if (isset($user->lang['timezones'][$label]))
  432. {
  433. $label = $user->lang['timezones'][$label];
  434. }
  435. $title = $user->lang(array('timezones', 'UTC_OFFSET_CURRENT'), $timezone['offset'], $label);
  436. if ($truncate)
  437. {
  438. $label = truncate_string($label, 50, 255, false, '...');
  439. }
  440. // Also generate timezone_select for backwards compatibility
  441. $selected = ($timezone['tz'] === $default) ? ' selected="selected"' : '';
  442. $tz_select .= '<option title="' . $title . '" value="' . $timezone['tz'] . '"' . $selected . '>' . $label . '</option>';
  443. $template->assign_block_vars('timezone_select.timezone_options', array(
  444. 'TITLE' => $title,
  445. 'VALUE' => $timezone['tz'],
  446. 'SELECTED' => !empty($selected),
  447. 'LABEL' => $label,
  448. ));
  449. }
  450. $tz_select .= '</optgroup>';
  451. return $tz_select;
  452. }
  453. // Functions handling topic/post tracking/marking
  454. /**
  455. * Marks a topic/forum as read
  456. * Marks a topic as posted to
  457. *
  458. * @param string $mode (all, topics, topic, post)
  459. * @param int|bool $forum_id Used in all, topics, and topic mode
  460. * @param int|bool $topic_id Used in topic and post mode
  461. * @param int $post_time 0 means current time(), otherwise to set a specific mark time
  462. * @param int $user_id can only be used with $mode == 'post'
  463. */
  464. function markread($mode, $forum_id = false, $topic_id = false, $post_time = 0, $user_id = 0)
  465. {
  466. global $db, $user, $config;
  467. global $request, $phpbb_container, $phpbb_dispatcher;
  468. $post_time = ($post_time === 0 || $post_time > time()) ? time() : (int) $post_time;
  469. $should_markread = true;
  470. /**
  471. * This event is used for performing actions directly before marking forums,
  472. * topics or posts as read.
  473. *
  474. * It is also possible to prevent the marking. For that, the $should_markread parameter
  475. * should be set to FALSE.
  476. *
  477. * @event core.markread_before
  478. * @var string mode Variable containing marking mode value
  479. * @var mixed forum_id Variable containing forum id, or false
  480. * @var mixed topic_id Variable containing topic id, or false
  481. * @var int post_time Variable containing post time
  482. * @var int user_id Variable containing the user id
  483. * @var bool should_markread Flag indicating if the markread should be done or not.
  484. * @since 3.1.4-RC1
  485. */
  486. $vars = array(
  487. 'mode',
  488. 'forum_id',
  489. 'topic_id',
  490. 'post_time',
  491. 'user_id',
  492. 'should_markread',
  493. );
  494. extract($phpbb_dispatcher->trigger_event('core.markread_before', compact($vars)));
  495. if (!$should_markread)
  496. {
  497. return;
  498. }
  499. if ($mode == 'all')
  500. {
  501. if (empty($forum_id))
  502. {
  503. // Mark all forums read (index page)
  504. /* @var $phpbb_notifications \phpbb\notification\manager */
  505. $phpbb_notifications = $phpbb_container->get('notification_manager');
  506. // Mark all topic notifications read for this user
  507. $phpbb_notifications->mark_notifications(array(
  508. 'notification.type.topic',
  509. 'notification.type.quote',
  510. 'notification.type.bookmark',
  511. 'notification.type.post',
  512. 'notification.type.approve_topic',
  513. 'notification.type.approve_post',
  514. ), false, $user->data['user_id'], $post_time);
  515. if ($config['load_db_lastread'] && $user->data['is_registered'])
  516. {
  517. // Mark all forums read (index page)
  518. $tables = array(TOPICS_TRACK_TABLE, FORUMS_TRACK_TABLE);
  519. foreach ($tables as $table)
  520. {
  521. $sql = 'DELETE FROM ' . $table . "
  522. WHERE user_id = {$user->data['user_id']}
  523. AND mark_time < $post_time";
  524. $db->sql_query($sql);
  525. }
  526. $sql = 'UPDATE ' . USERS_TABLE . "
  527. SET user_lastmark = $post_time
  528. WHERE user_id = {$user->data['user_id']}
  529. AND user_lastmark < $post_time";
  530. $db->sql_query($sql);
  531. }
  532. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  533. {
  534. $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
  535. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  536. unset($tracking_topics['tf']);
  537. unset($tracking_topics['t']);
  538. unset($tracking_topics['f']);
  539. $tracking_topics['l'] = base_convert($post_time - $config['board_startdate'], 10, 36);
  540. $user->set_cookie('track', tracking_serialize($tracking_topics), $post_time + 31536000);
  541. $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking_topics), \phpbb\request\request_interface::COOKIE);
  542. unset($tracking_topics);
  543. if ($user->data['is_registered'])
  544. {
  545. $sql = 'UPDATE ' . USERS_TABLE . "
  546. SET user_lastmark = $post_time
  547. WHERE user_id = {$user->data['user_id']}
  548. AND user_lastmark < $post_time";
  549. $db->sql_query($sql);
  550. }
  551. }
  552. }
  553. }
  554. else if ($mode == 'topics')
  555. {
  556. // Mark all topics in forums read
  557. if (!is_array($forum_id))
  558. {
  559. $forum_id = array($forum_id);
  560. }
  561. else
  562. {
  563. $forum_id = array_unique($forum_id);
  564. }
  565. /* @var $phpbb_notifications \phpbb\notification\manager */
  566. $phpbb_notifications = $phpbb_container->get('notification_manager');
  567. $phpbb_notifications->mark_notifications_by_parent(array(
  568. 'notification.type.topic',
  569. 'notification.type.approve_topic',
  570. ), $forum_id, $user->data['user_id'], $post_time);
  571. // Mark all post/quote notifications read for this user in this forum
  572. $topic_ids = array();
  573. $sql = 'SELECT topic_id
  574. FROM ' . TOPICS_TABLE . '
  575. WHERE ' . $db->sql_in_set('forum_id', $forum_id);
  576. $result = $db->sql_query($sql);
  577. while ($row = $db->sql_fetchrow($result))
  578. {
  579. $topic_ids[] = $row['topic_id'];
  580. }
  581. $db->sql_freeresult($result);
  582. $phpbb_notifications->mark_notifications_by_parent(array(
  583. 'notification.type.quote',
  584. 'notification.type.bookmark',
  585. 'notification.type.post',
  586. 'notification.type.approve_post',
  587. ), $topic_ids, $user->data['user_id'], $post_time);
  588. // Add 0 to forums array to mark global announcements correctly
  589. // $forum_id[] = 0;
  590. if ($config['load_db_lastread'] && $user->data['is_registered'])
  591. {
  592. $sql = 'DELETE FROM ' . TOPICS_TRACK_TABLE . "
  593. WHERE user_id = {$user->data['user_id']}
  594. AND mark_time < $post_time
  595. AND " . $db->sql_in_set('forum_id', $forum_id);
  596. $db->sql_query($sql);
  597. $sql = 'SELECT forum_id
  598. FROM ' . FORUMS_TRACK_TABLE . "
  599. WHERE user_id = {$user->data['user_id']}
  600. AND " . $db->sql_in_set('forum_id', $forum_id);
  601. $result = $db->sql_query($sql);
  602. $sql_update = array();
  603. while ($row = $db->sql_fetchrow($result))
  604. {
  605. $sql_update[] = (int) $row['forum_id'];
  606. }
  607. $db->sql_freeresult($result);
  608. if (count($sql_update))
  609. {
  610. $sql = 'UPDATE ' . FORUMS_TRACK_TABLE . "
  611. SET mark_time = $post_time
  612. WHERE user_id = {$user->data['user_id']}
  613. AND mark_time < $post_time
  614. AND " . $db->sql_in_set('forum_id', $sql_update);
  615. $db->sql_query($sql);
  616. }
  617. if ($sql_insert = array_diff($forum_id, $sql_update))
  618. {
  619. $sql_ary = array();
  620. foreach ($sql_insert as $f_id)
  621. {
  622. $sql_ary[] = array(
  623. 'user_id' => (int) $user->data['user_id'],
  624. 'forum_id' => (int) $f_id,
  625. 'mark_time' => $post_time,
  626. );
  627. }
  628. $db->sql_multi_insert(FORUMS_TRACK_TABLE, $sql_ary);
  629. }
  630. }
  631. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  632. {
  633. $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
  634. $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
  635. foreach ($forum_id as $f_id)
  636. {
  637. $topic_ids36 = (isset($tracking['tf'][$f_id])) ? $tracking['tf'][$f_id] : array();
  638. if (isset($tracking['tf'][$f_id]))
  639. {
  640. unset($tracking['tf'][$f_id]);
  641. }
  642. foreach ($topic_ids36 as $topic_id36)
  643. {
  644. unset($tracking['t'][$topic_id36]);
  645. }
  646. if (isset($tracking['f'][$f_id]))
  647. {
  648. unset($tracking['f'][$f_id]);
  649. }
  650. $tracking['f'][$f_id] = base_convert($post_time - $config['board_startdate'], 10, 36);
  651. }
  652. if (isset($tracking['tf']) && empty($tracking['tf']))
  653. {
  654. unset($tracking['tf']);
  655. }
  656. $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);
  657. $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE);
  658. unset($tracking);
  659. }
  660. }
  661. else if ($mode == 'topic')
  662. {
  663. if ($topic_id === false || $forum_id === false)
  664. {
  665. return;
  666. }
  667. /* @var $phpbb_notifications \phpbb\notification\manager */
  668. $phpbb_notifications = $phpbb_container->get('notification_manager');
  669. // Mark post notifications read for this user in this topic
  670. $phpbb_notifications->mark_notifications(array(
  671. 'notification.type.topic',
  672. 'notification.type.approve_topic',
  673. ), $topic_id, $user->data['user_id'], $post_time);
  674. $phpbb_notifications->mark_notifications_by_parent(array(
  675. 'notification.type.quote',
  676. 'notification.type.bookmark',
  677. 'notification.type.post',
  678. 'notification.type.approve_post',
  679. ), $topic_id, $user->data['user_id'], $post_time);
  680. if ($config['load_db_lastread'] && $user->data['is_registered'])
  681. {
  682. $sql = 'UPDATE ' . TOPICS_TRACK_TABLE . "
  683. SET mark_time = $post_time
  684. WHERE user_id = {$user->data['user_id']}
  685. AND mark_time < $post_time
  686. AND topic_id = $topic_id";
  687. $db->sql_query($sql);
  688. // insert row
  689. if (!$db->sql_affectedrows())
  690. {
  691. $db->sql_return_on_error(true);
  692. $sql_ary = array(
  693. 'user_id' => (int) $user->data['user_id'],
  694. 'topic_id' => (int) $topic_id,
  695. 'forum_id' => (int) $forum_id,
  696. 'mark_time' => $post_time,
  697. );
  698. $db->sql_query('INSERT INTO ' . TOPICS_TRACK_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
  699. $db->sql_return_on_error(false);
  700. }
  701. }
  702. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  703. {
  704. $tracking = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
  705. $tracking = ($tracking) ? tracking_unserialize($tracking) : array();
  706. $topic_id36 = base_convert($topic_id, 10, 36);
  707. if (!isset($tracking['t'][$topic_id36]))
  708. {
  709. $tracking['tf'][$forum_id][$topic_id36] = true;
  710. }
  711. $tracking['t'][$topic_id36] = base_convert($post_time - (int) $config['board_startdate'], 10, 36);
  712. // If the cookie grows larger than 10000 characters we will remove the smallest value
  713. // This can result in old topics being unread - but most of the time it should be accurate...
  714. if (strlen($request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE)) > 10000)
  715. {
  716. //echo 'Cookie grown too large' . print_r($tracking, true);
  717. // We get the ten most minimum stored time offsets and its associated topic ids
  718. $time_keys = array();
  719. for ($i = 0; $i < 10 && count($tracking['t']); $i++)
  720. {
  721. $min_value = min($tracking['t']);
  722. $m_tkey = array_search($min_value, $tracking['t']);
  723. unset($tracking['t'][$m_tkey]);
  724. $time_keys[$m_tkey] = $min_value;
  725. }
  726. // Now remove the topic ids from the array...
  727. foreach ($tracking['tf'] as $f_id => $topic_id_ary)
  728. {
  729. foreach ($time_keys as $m_tkey => $min_value)
  730. {
  731. if (isset($topic_id_ary[$m_tkey]))
  732. {
  733. $tracking['f'][$f_id] = $min_value;
  734. unset($tracking['tf'][$f_id][$m_tkey]);
  735. }
  736. }
  737. }
  738. if ($user->data['is_registered'])
  739. {
  740. $user->data['user_lastmark'] = intval(base_convert(max($time_keys) + $config['board_startdate'], 36, 10));
  741. $sql = 'UPDATE ' . USERS_TABLE . "
  742. SET user_lastmark = $post_time
  743. WHERE user_id = {$user->data['user_id']}
  744. AND mark_time < $post_time";
  745. $db->sql_query($sql);
  746. }
  747. else
  748. {
  749. $tracking['l'] = max($time_keys);
  750. }
  751. }
  752. $user->set_cookie('track', tracking_serialize($tracking), $post_time + 31536000);
  753. $request->overwrite($config['cookie_name'] . '_track', tracking_serialize($tracking), \phpbb\request\request_interface::COOKIE);
  754. }
  755. }
  756. else if ($mode == 'post')
  757. {
  758. if ($topic_id === false)
  759. {
  760. return;
  761. }
  762. $use_user_id = (!$user_id) ? $user->data['user_id'] : $user_id;
  763. if ($config['load_db_track'] && $use_user_id != ANONYMOUS)
  764. {
  765. $db->sql_return_on_error(true);
  766. $sql_ary = array(
  767. 'user_id' => (int) $use_user_id,
  768. 'topic_id' => (int) $topic_id,
  769. 'topic_posted' => 1,
  770. );
  771. $db->sql_query('INSERT INTO ' . TOPICS_POSTED_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
  772. $db->sql_return_on_error(false);
  773. }
  774. }
  775. /**
  776. * This event is used for performing actions directly after forums,
  777. * topics or posts have been marked as read.
  778. *
  779. * @event core.markread_after
  780. * @var string mode Variable containing marking mode value
  781. * @var mixed forum_id Variable containing forum id, or false
  782. * @var mixed topic_id Variable containing topic id, or false
  783. * @var int post_time Variable containing post time
  784. * @var int user_id Variable containing the user id
  785. * @since 3.2.6-RC1
  786. */
  787. $vars = array(
  788. 'mode',
  789. 'forum_id',
  790. 'topic_id',
  791. 'post_time',
  792. 'user_id',
  793. );
  794. extract($phpbb_dispatcher->trigger_event('core.markread_after', compact($vars)));
  795. }
  796. /**
  797. * Get topic tracking info by using already fetched info
  798. */
  799. function get_topic_tracking($forum_id, $topic_ids, &$rowset, $forum_mark_time, $global_announce_list = false)
  800. {
  801. global $user;
  802. $last_read = array();
  803. if (!is_array($topic_ids))
  804. {
  805. $topic_ids = array($topic_ids);
  806. }
  807. foreach ($topic_ids as $topic_id)
  808. {
  809. if (!empty($rowset[$topic_id]['mark_time']))
  810. {
  811. $last_read[$topic_id] = $rowset[$topic_id]['mark_time'];
  812. }
  813. }
  814. $topic_ids = array_diff($topic_ids, array_keys($last_read));
  815. if (count($topic_ids))
  816. {
  817. $mark_time = array();
  818. if (!empty($forum_mark_time[$forum_id]) && $forum_mark_time[$forum_id] !== false)
  819. {
  820. $mark_time[$forum_id] = $forum_mark_time[$forum_id];
  821. }
  822. $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
  823. foreach ($topic_ids as $topic_id)
  824. {
  825. $last_read[$topic_id] = $user_lastmark;
  826. }
  827. }
  828. return $last_read;
  829. }
  830. /**
  831. * Get topic tracking info from db (for cookie based tracking only this function is used)
  832. */
  833. function get_complete_topic_tracking($forum_id, $topic_ids, $global_announce_list = false)
  834. {
  835. global $config, $user, $request;
  836. $last_read = array();
  837. if (!is_array($topic_ids))
  838. {
  839. $topic_ids = array($topic_ids);
  840. }
  841. if ($config['load_db_lastread'] && $user->data['is_registered'])
  842. {
  843. global $db;
  844. $sql = 'SELECT topic_id, mark_time
  845. FROM ' . TOPICS_TRACK_TABLE . "
  846. WHERE user_id = {$user->data['user_id']}
  847. AND " . $db->sql_in_set('topic_id', $topic_ids);
  848. $result = $db->sql_query($sql);
  849. while ($row = $db->sql_fetchrow($result))
  850. {
  851. $last_read[$row['topic_id']] = $row['mark_time'];
  852. }
  853. $db->sql_freeresult($result);
  854. $topic_ids = array_diff($topic_ids, array_keys($last_read));
  855. if (count($topic_ids))
  856. {
  857. $sql = 'SELECT forum_id, mark_time
  858. FROM ' . FORUMS_TRACK_TABLE . "
  859. WHERE user_id = {$user->data['user_id']}
  860. AND forum_id = $forum_id";
  861. $result = $db->sql_query($sql);
  862. $mark_time = array();
  863. while ($row = $db->sql_fetchrow($result))
  864. {
  865. $mark_time[$row['forum_id']] = $row['mark_time'];
  866. }
  867. $db->sql_freeresult($result);
  868. $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user->data['user_lastmark'];
  869. foreach ($topic_ids as $topic_id)
  870. {
  871. $last_read[$topic_id] = $user_lastmark;
  872. }
  873. }
  874. }
  875. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  876. {
  877. global $tracking_topics;
  878. if (!isset($tracking_topics) || !count($tracking_topics))
  879. {
  880. $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
  881. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  882. }
  883. if (!$user->data['is_registered'])
  884. {
  885. $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
  886. }
  887. else
  888. {
  889. $user_lastmark = $user->data['user_lastmark'];
  890. }
  891. foreach ($topic_ids as $topic_id)
  892. {
  893. $topic_id36 = base_convert($topic_id, 10, 36);
  894. if (isset($tracking_topics['t'][$topic_id36]))
  895. {
  896. $last_read[$topic_id] = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
  897. }
  898. }
  899. $topic_ids = array_diff($topic_ids, array_keys($last_read));
  900. if (count($topic_ids))
  901. {
  902. $mark_time = array();
  903. if (isset($tracking_topics['f'][$forum_id]))
  904. {
  905. $mark_time[$forum_id] = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
  906. }
  907. $user_lastmark = (isset($mark_time[$forum_id])) ? $mark_time[$forum_id] : $user_lastmark;
  908. foreach ($topic_ids as $topic_id)
  909. {
  910. $last_read[$topic_id] = $user_lastmark;
  911. }
  912. }
  913. }
  914. return $last_read;
  915. }
  916. /**
  917. * Get list of unread topics
  918. *
  919. * @param int $user_id User ID (or false for current user)
  920. * @param string $sql_extra Extra WHERE SQL statement
  921. * @param string $sql_sort ORDER BY SQL sorting statement
  922. * @param string $sql_limit Limits the size of unread topics list, 0 for unlimited query
  923. * @param string $sql_limit_offset Sets the offset of the first row to search, 0 to search from the start
  924. *
  925. * @return array[int][int] Topic ids as keys, mark_time of topic as value
  926. */
  927. function get_unread_topics($user_id = false, $sql_extra = '', $sql_sort = '', $sql_limit = 1001, $sql_limit_offset = 0)
  928. {
  929. global $config, $db, $user, $request;
  930. global $phpbb_dispatcher;
  931. $user_id = ($user_id === false) ? (int) $user->data['user_id'] : (int) $user_id;
  932. // Data array we're going to return
  933. $unread_topics = array();
  934. if (empty($sql_sort))
  935. {
  936. $sql_sort = 'ORDER BY t.topic_last_post_time DESC, t.topic_last_post_id DESC';
  937. }
  938. if ($config['load_db_lastread'] && $user->data['is_registered'])
  939. {
  940. // Get list of the unread topics
  941. $last_mark = (int) $user->data['user_lastmark'];
  942. $sql_array = array(
  943. 'SELECT' => 't.topic_id, t.topic_last_post_time, tt.mark_time as topic_mark_time, ft.mark_time as forum_mark_time',
  944. 'FROM' => array(TOPICS_TABLE => 't'),
  945. 'LEFT_JOIN' => array(
  946. array(
  947. 'FROM' => array(TOPICS_TRACK_TABLE => 'tt'),
  948. 'ON' => "tt.user_id = $user_id AND t.topic_id = tt.topic_id",
  949. ),
  950. array(
  951. 'FROM' => array(FORUMS_TRACK_TABLE => 'ft'),
  952. 'ON' => "ft.user_id = $user_id AND t.forum_id = ft.forum_id",
  953. ),
  954. ),
  955. 'WHERE' => "
  956. t.topic_last_post_time > $last_mark AND
  957. (
  958. (tt.mark_time IS NOT NULL AND t.topic_last_post_time > tt.mark_time) OR
  959. (tt.mark_time IS NULL AND ft.mark_time IS NOT NULL AND t.topic_last_post_time > ft.mark_time) OR
  960. (tt.mark_time IS NULL AND ft.mark_time IS NULL)
  961. )
  962. $sql_extra
  963. $sql_sort",
  964. );
  965. /**
  966. * Change SQL query for fetching unread topics data
  967. *
  968. * @event core.get_unread_topics_modify_sql
  969. * @var array sql_array Fully assembled SQL query with keys SELECT, FROM, LEFT_JOIN, WHERE
  970. * @var int last_mark User's last_mark time
  971. * @var string sql_extra Extra WHERE SQL statement
  972. * @var string sql_sort ORDER BY SQL sorting statement
  973. * @since 3.1.4-RC1
  974. */
  975. $vars = array(
  976. 'sql_array',
  977. 'last_mark',
  978. 'sql_extra',
  979. 'sql_sort',
  980. );
  981. extract($phpbb_dispatcher->trigger_event('core.get_unread_topics_modify_sql', compact($vars)));
  982. $sql = $db->sql_build_query('SELECT', $sql_array);
  983. $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
  984. while ($row = $db->sql_fetchrow($result))
  985. {
  986. $topic_id = (int) $row['topic_id'];
  987. $unread_topics[$topic_id] = ($row['topic_mark_time']) ? (int) $row['topic_mark_time'] : (($row['forum_mark_time']) ? (int) $row['forum_mark_time'] : $last_mark);
  988. }
  989. $db->sql_freeresult($result);
  990. }
  991. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  992. {
  993. global $tracking_topics;
  994. if (empty($tracking_topics))
  995. {
  996. $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', false, \phpbb\request\request_interface::COOKIE);
  997. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  998. }
  999. if (!$user->data['is_registered'])
  1000. {
  1001. $user_lastmark = (isset($tracking_topics['l'])) ? base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate'] : 0;
  1002. }
  1003. else
  1004. {
  1005. $user_lastmark = (int) $user->data['user_lastmark'];
  1006. }
  1007. $sql = 'SELECT t.topic_id, t.forum_id, t.topic_last_post_time
  1008. FROM ' . TOPICS_TABLE . ' t
  1009. WHERE t.topic_last_post_time > ' . $user_lastmark . "
  1010. $sql_extra
  1011. $sql_sort";
  1012. $result = $db->sql_query_limit($sql, $sql_limit, $sql_limit_offset);
  1013. while ($row = $db->sql_fetchrow($result))
  1014. {
  1015. $forum_id = (int) $row['forum_id'];
  1016. $topic_id = (int) $row['topic_id'];
  1017. $topic_id36 = base_convert($topic_id, 10, 36);
  1018. if (isset($tracking_topics['t'][$topic_id36]))
  1019. {
  1020. $last_read = base_convert($tracking_topics['t'][$topic_id36], 36, 10) + $config['board_startdate'];
  1021. if ($row['topic_last_post_time'] > $last_read)
  1022. {
  1023. $unread_topics[$topic_id] = $last_read;
  1024. }
  1025. }
  1026. else if (isset($tracking_topics['f'][$forum_id]))
  1027. {
  1028. $mark_time = base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate'];
  1029. if ($row['topic_last_post_time'] > $mark_time)
  1030. {
  1031. $unread_topics[$topic_id] = $mark_time;
  1032. }
  1033. }
  1034. else
  1035. {
  1036. $unread_topics[$topic_id] = $user_lastmark;
  1037. }
  1038. }
  1039. $db->sql_freeresult($result);
  1040. }
  1041. return $unread_topics;
  1042. }
  1043. /**
  1044. * Check for read forums and update topic tracking info accordingly
  1045. *
  1046. * @param int $forum_id the forum id to check
  1047. * @param int $forum_last_post_time the forums last post time
  1048. * @param int $f_mark_time the forums last mark time if user is registered and load_db_lastread enabled
  1049. * @param int $mark_time_forum false if the mark time needs to be obtained, else the last users forum mark time
  1050. *
  1051. * @return true if complete forum got marked read, else false.
  1052. */
  1053. function update_forum_tracking_info($forum_id, $forum_last_post_time, $f_mark_time = false, $mark_time_forum = false)
  1054. {
  1055. global $db, $tracking_topics, $user, $config, $request, $phpbb_container;
  1056. // Determine the users last forum mark time if not given.
  1057. if ($mark_time_forum === false)
  1058. {
  1059. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1060. {
  1061. $mark_time_forum = (!empty($f_mark_time)) ? $f_mark_time : $user->data['user_lastmark'];
  1062. }
  1063. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1064. {
  1065. $tracking_topics = $request->variable($config['cookie_name'] . '_track', '', true, \phpbb\request\request_interface::COOKIE);
  1066. $tracking_topics = ($tracking_topics) ? tracking_unserialize($tracking_topics) : array();
  1067. if (!$user->data['is_registered'])
  1068. {
  1069. $user->data['user_lastmark'] = (isset($tracking_topics['l'])) ? (int) (base_convert($tracking_topics['l'], 36, 10) + $config['board_startdate']) : 0;
  1070. }
  1071. $mark_time_forum = (isset($tracking_topics['f'][$forum_id])) ? (int) (base_convert($tracking_topics['f'][$forum_id], 36, 10) + $config['board_startdate']) : $user->data['user_lastmark'];
  1072. }
  1073. }
  1074. // Handle update of unapproved topics info.
  1075. // Only update for moderators having m_approve permission for the forum.
  1076. /* @var $phpbb_content_visibility \phpbb\content_visibility */
  1077. $phpbb_content_visibility = $phpbb_container->get('content.visibility');
  1078. // Check the forum for any left unread topics.
  1079. // If there are none, we mark the forum as read.
  1080. if ($config['load_db_lastread'] && $user->data['is_registered'])
  1081. {
  1082. if ($mark_time_forum >= $forum_last_post_time)
  1083. {
  1084. // We do not need to mark read, this happened before. Therefore setting this to true
  1085. $row = true;
  1086. }
  1087. else
  1088. {
  1089. $sql = 'SELECT t.forum_id
  1090. FROM ' . TOPICS_TABLE . ' t
  1091. LEFT JOIN ' . TOPICS_TRACK_TABLE . ' tt
  1092. ON (tt.topic_id = t.topic_id
  1093. AND tt.user_id = ' . $user->data['user_id'] . ')
  1094. WHERE t.forum_id = ' . $forum_id . '
  1095. AND t.topic_last_post_time > ' . $mark_time_forum . '
  1096. AND t.topic_moved_id = 0
  1097. AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.') . '
  1098. AND (tt.topic_id IS NULL
  1099. OR tt.mark_time < t.topic_last_post_time)';
  1100. $result = $db->sql_query_limit($sql, 1);
  1101. $row = $db->sql_fetchrow($result);
  1102. $db->sql_freeresult($result);
  1103. }
  1104. }
  1105. else if ($config['load_anon_lastread'] || $user->data['is_registered'])
  1106. {
  1107. // Get information from cookie
  1108. if (!isset($tracking_topics['tf'][$forum_id]))
  1109. {
  1110. // We do not need to mark read, this happened before. Therefore setting this to true
  1111. $row = true;
  1112. }
  1113. else
  1114. {
  1115. $sql = 'SELECT t.topic_id
  1116. FROM ' . TOPICS_TABLE . ' t
  1117. WHERE t.forum_id = ' . $forum_id . '
  1118. AND t.topic_last_post_time > ' . $mark_time_forum . '
  1119. AND t.topic_moved_id = 0
  1120. AND ' . $phpbb_content_visibility->get_visibility_sql('topic', $forum_id, 't.');
  1121. $result = $db->sql_query($sql);
  1122. $check_forum = $tracking_topics['tf'][$forum_id];
  1123. $unread = false;
  1124. while ($row = $db->sql_fetchrow($result))
  1125. {
  1126. if (!isset($check_forum[base_convert($row['topic_id'], 10, 36)]))
  1127. {
  1128. $unread = true;
  1129. break;
  1130. }
  1131. }
  1132. $db->sql_freeresult($result);
  1133. $row = $unread;
  1134. }
  1135. }
  1136. else
  1137. {
  1138. $row = true;
  1139. }
  1140. if (!$row)
  1141. {
  1142. markread('topics', $forum_id);
  1143. return true;
  1144. }
  1145. return false;
  1146. }
  1147. /**
  1148. * Transform an array into a serialized format
  1149. */
  1150. function tracking_serialize($input)
  1151. {
  1152. $out = '';
  1153. foreach ($input as $key => $value)
  1154. {
  1155. if (is_array($value))
  1156. {
  1157. $out .= $key . ':(' . tracking_serialize($value) . ');';
  1158. }
  1159. else
  1160. {
  1161. $out .= $key . ':' . $value . ';';
  1162. }
  1163. }
  1164. return $out;
  1165. }
  1166. /**
  1167. * Transform a serialized array into an actual array
  1168. */
  1169. function tracking_unserialize($string, $max_depth = 3)
  1170. {
  1171. $n = strlen($string);
  1172. if ($n > 10010)
  1173. {
  1174. die('Invalid data supplied');
  1175. }
  1176. $data = $stack = array();
  1177. $key = '';
  1178. $mode = 0;
  1179. $level = &$data;
  1180. for ($i = 0; $i < $n; ++$i)
  1181. {
  1182. switch ($mode)
  1183. {
  1184. case 0:
  1185. switch ($string[$i])
  1186. {
  1187. case ':':
  1188. $level[$key] = 0;
  1189. $mode = 1;
  1190. break;
  1191. case ')':
  1192. unset($level);
  1193. $level = array_pop($stack);
  1194. $mode = 3;
  1195. break;
  1196. default:
  1197. $key .= $string[$i];
  1198. }
  1199. break;
  1200. case 1:
  1201. switch ($string[$i])
  1202. {
  1203. case '(':
  1204. if (count($stack) >= $max_depth)
  1205. {
  1206. die('Invalid data supplied');
  1207. }
  1208. $stack[] = &$level;
  1209. $level[$key] = array();
  1210. $level = &$level[$key];
  1211. $key = '';
  1212. $mode = 0;
  1213. break;
  1214. default:
  1215. $level[$key] = $string[$i];
  1216. $mode = 2;
  1217. break;
  1218. }
  1219. break;
  1220. case 2:
  1221. switch ($string[$i])
  1222. {
  1223. case ')':
  1224. unset($level);
  1225. $level = array_pop($stack);
  1226. $mode = 3;
  1227. break;
  1228. case ';':
  1229. $key = '';
  1230. $mode = 0;
  1231. break;
  1232. default:
  1233. $level[$key] .= $string[$i];
  1234. break;
  1235. }
  1236. break;
  1237. case 3:
  1238. switch ($string[$i])
  1239. {
  1240. case ')':
  1241. unset($level);
  1242. $level = array_pop($stack);
  1243. break;
  1244. case ';':
  1245. $key = '';
  1246. $mode = 0;
  1247. break;
  1248. default:
  1249. die('Invalid data supplied');
  1250. break;
  1251. }
  1252. break;
  1253. }
  1254. }
  1255. if (count($stack) != 0 || ($mode != 0 && $mode != 3))
  1256. {
  1257. die('Invalid data supplied');
  1258. }
  1259. return $level;
  1260. }
  1261. // Server functions (building urls, redirecting...)
  1262. /**
  1263. * Append session id to url.
  1264. *
  1265. * @param string $url The url the session id needs to be appended to (can have params)
  1266. * @param mixed $params String or array of additional url parameters
  1267. * @param bool $is_amp Is url using &amp; (true) or & (false)
  1268. * @param string $session_id Possibility to use a custom session id instead of the global one
  1269. * @param bool $is_route Is url generated by a route.
  1270. *
  1271. * @return string The corrected url.
  1272. *
  1273. * Examples:
  1274. * <code>
  1275. * append_sid("{$phpbb_root_path}viewtopic.$phpEx?t=1&amp;f=2");
  1276. * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&amp;f=2');
  1277. * append_sid("{$phpbb_root_path}viewtopic.$phpEx", 't=1&f=2', false);
  1278. * append_sid("{$phpbb_root_path}viewtopic.$phpEx", array('t' => 1, 'f' => 2));
  1279. * </code>
  1280. *
  1281. */
  1282. function append_sid($url, $params = false, $is_amp = true, $session_id = false, $is_route = false)
  1283. {
  1284. global $_SID, $_EXTRA_URL, $phpbb_path_helper;
  1285. global $phpbb_dispatcher;
  1286. if ($params === '' || (is_array($params) && empty($params)))
  1287. {
  1288. // Do not append the ? if the param-list is empty anyway.
  1289. $params = false;
  1290. }
  1291. // Update the root path with the correct relative web path
  1292. if (!$is_route && $phpbb_path_helper instanceof \phpbb\path_helper)
  1293. {
  1294. $url = $phpbb_path_helper->update_web_root_path($url);
  1295. }
  1296. $append_sid_overwrite = false;
  1297. /**
  1298. * This event can either supplement or override the append_sid() function
  1299. *
  1300. * To override this function, the event must set $append_sid_overwrite to
  1301. * the new URL value, which will be returned following the event
  1302. *
  1303. * @event core.append_sid
  1304. * @var string url The url the session id needs
  1305. * to be appended to (can have
  1306. * params)
  1307. * @var mixed params String or array of additional
  1308. * url parameters
  1309. * @var bool is_amp Is url using &amp; (true) or
  1310. * & (false)
  1311. * @var bool|string session_id Possibility to use a custom
  1312. * session id (string) instead of
  1313. * the global one (false)
  1314. * @var bool|string append_sid_overwrite Overwrite function (string
  1315. * URL) or not (false)
  1316. * @var bool is_route Is url generated by a route.
  1317. * @since 3.1.0-a1
  1318. */
  1319. $vars = array('url', 'params', 'is_amp', 'session_id', 'append_sid_overwrite', 'is_route');
  1320. extract($phpbb_dispatcher->trigger_event('core.append_sid', compact($vars)));
  1321. if ($append_sid_overwrite)
  1322. {
  1323. return $append_sid_overwrite;
  1324. }
  1325. $params_is_array = is_array($params);
  1326. // Get anchor
  1327. $anchor = '';
  1328. if (strpos($url, '#') !== false)
  1329. {
  1330. list($url, $anchor) = explode('#', $url, 2);
  1331. $anchor = '#' . $anchor;
  1332. }
  1333. else if (!$params_is_array && strpos($params, '#') !== false)
  1334. {
  1335. list($params, $anchor) = explode('#', $params, 2);
  1336. $anchor = '#' . $anchor;
  1337. }
  1338. // Handle really simple cases quickly
  1339. if ($_SID == '' && $session_id === false && empty($_EXTRA_URL) && !$params_is_array && !$anchor)
  1340. {
  1341. if ($params === false)
  1342. {
  1343. return $url;
  1344. }
  1345. $url_delim = (strpos($url, '?') === false) ? '?' : (($is_amp) ? '&amp;' : '&');
  1346. return $url . ($params !== false ? $url_delim. $params : '');
  1347. }
  1348. // Assign sid if session id is not specified
  1349. if ($session_id === false)
  1350. {
  1351. $session_id = $_SID;
  1352. }
  1353. $amp_delim = ($is_amp) ? '&amp;' : '&';
  1354. $url_delim = (strpos($url, '?') === false) ? '?' : $amp_delim;
  1355. // Appending custom url parameter?
  1356. $append_url = (!empty($_EXTRA_URL)) ? implode($amp_delim, $_EXTRA_URL) : '';
  1357. // Use the short variant if possible ;)
  1358. if ($params === false)
  1359. {
  1360. // Append session id
  1361. if (!$session_id)
  1362. {
  1363. return $url . (($append_url) ? $url_delim . $append_url : '') . $anchor;
  1364. }
  1365. else
  1366. {
  1367. return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . 'sid=' . $session_id . $anchor;
  1368. }
  1369. }
  1370. // Build string if parameters are specified as array
  1371. if (is_array($params))
  1372. {
  1373. $output = array();
  1374. foreach ($params as $key => $item)
  1375. {
  1376. if ($item === NULL)
  1377. {
  1378. continue;
  1379. }
  1380. if ($key == '#')
  1381. {
  1382. $anchor = '#' . $item;
  1383. continue;
  1384. }
  1385. $output[] = $key . '=' . $item;
  1386. }
  1387. $params = implode($amp_delim, $output);
  1388. }
  1389. // Append session id and parameters (even if they are empty)
  1390. // If parameters are empty, the developer can still append his/her parameters without caring about the delimiter
  1391. return $url . (($append_url) ? $url_delim . $append_url . $amp_delim : $url_delim) . $params . ((!$session_id) ? '' : $amp_delim . 'sid=' . $session_id) . $anchor;
  1392. }
  1393. /**
  1394. * Generate board url (example: http://www.example.com/phpBB)
  1395. *
  1396. * @param bool $without_script_path if set to true the script path gets not appended (example: http://www.example.com)
  1397. *
  1398. * @return string the generated board url
  1399. */
  1400. function generate_board_url($without_script_path = false)
  1401. {
  1402. global $config, $user, $request, $symfony_request;
  1403. $server_name = $user->host;
  1404. // Forcing server vars is the only way to specify/override the protocol
  1405. if ($config['force_server_vars'] || !$server_name)
  1406. {
  1407. $server_protocol = ($config['server_protocol']) ? $config['server_protocol'] : (($config['cookie_secure']) ? 'https://' : 'http://');
  1408. $server_name = $config['server_name'];
  1409. $server_port = (int) $config['server_port'];
  1410. $script_path = $config['script_path'];
  1411. $url = $server_protocol . $server_name;
  1412. $cookie_secure = $config['cookie_secure'];
  1413. }
  1414. else
  1415. {
  1416. $server_port = (int) $symfony_request->getPort();
  1417. $forwarded_proto = $request->server('HTTP_X_FORWARDED_PROTO');
  1418. if (!empty($forwarded_proto) && $forwarded_proto === 'https')
  1419. {
  1420. $server_port = 443;
  1421. }
  1422. // Do not rely on cookie_secure, users seem to think that it means a secured cookie instead of an encrypted connection
  1423. $cookie_secure = $request->is_secure() ? 1 : 0;
  1424. $url = (($cookie_secure) ? 'https://' : 'http://') . $server_name;
  1425. $script_path = $user->page['root_script_path'];
  1426. }
  1427. if ($server_port && (($cookie_secure && $server_port <> 443) || (!$cookie_secure && $server_port <> 80)))
  1428. {
  1429. // HTTP HOST can carry a port number (we fetch $user->host, but for old versions this may be true)
  1430. if (strpos($server_name, ':') === false)
  1431. {
  1432. $url .= ':' . $server_port;
  1433. }
  1434. }
  1435. if (!$without_script_path)
  1436. {
  1437. $url .= $script_path;
  1438. }
  1439. // Strip / from the end
  1440. if (substr($url, -1, 1) == '/')
  1441. {
  1442. $url = substr($url, 0, -1);
  1443. }
  1444. return $url;
  1445. }
  1446. /**
  1447. * Redirects the user to another page then exits the script nicely
  1448. * This function is intended for urls within the board. It's not meant to redirect to cross-domains.
  1449. *
  1450. * @param string $url The url to redirect to
  1451. * @param bool $return If true, do not redirect but return the sanitized URL. Default is no return.
  1452. * @param bool $disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
  1453. */
  1454. function redirect($url, $return = false, $disable_cd_check = false)
  1455. {
  1456. global $user, $phpbb_path_helper, $phpbb_dispatcher;
  1457. if (!$user->is_setup())
  1458. {
  1459. $user->add_lang('common');
  1460. }
  1461. // Make sure no &amp;'s are in, this will break the redirect
  1462. $url = str_replace('&amp;', '&', $url);
  1463. // Determine which type of redirect we need to handle...
  1464. $url_parts = @parse_url($url);
  1465. if ($url_parts === false)
  1466. {
  1467. // Malformed url
  1468. trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
  1469. }
  1470. else if (!empty($url_parts['scheme']) && !empty($url_parts['host']))
  1471. {
  1472. // Attention: only able to redirect within the same domain if $disable_cd_check is false (yourdomain.com -> www.yourdomain.com will not work)
  1473. if (!$disable_cd_check && $url_parts['host'] !== $user->host)
  1474. {
  1475. trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
  1476. }
  1477. }
  1478. else if ($url[0] == '/')
  1479. {
  1480. // Absolute uri, prepend direct url...
  1481. $url = generate_board_url(true) . $url;
  1482. }
  1483. else
  1484. {
  1485. // Relative uri
  1486. $pathinfo = pathinfo($url);
  1487. // Is the uri pointing to the current directory?
  1488. if ($pathinfo['dirname'] == '.')
  1489. {
  1490. $url = str_replace('./', '', $url);
  1491. // Strip / from the beginning
  1492. if ($url && substr($url, 0, 1) == '/')
  1493. {
  1494. $url = substr($url, 1);
  1495. }
  1496. }
  1497. $url = $phpbb_path_helper->remove_web_root_path($url);
  1498. if ($user->page['page_dir'])
  1499. {
  1500. $url = $user->page['page_dir'] . '/' . $url;
  1501. }
  1502. $url = generate_board_url() . '/' . $url;
  1503. }
  1504. // Clean URL and check if we go outside the forum directory
  1505. $url = $phpbb_path_helper->clean_url($url);
  1506. if (!$disable_cd_check && strpos($url, generate_board_url(true) . '/') !== 0)
  1507. {
  1508. trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
  1509. }
  1510. // Make sure no linebreaks are there... to prevent http response splitting for PHP < 4.4.2
  1511. if (strpos(urldecode($url), "\n") !== false || strpos(urldecode($url), "\r") !== false || strpos($url, ';') !== false)
  1512. {
  1513. trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
  1514. }
  1515. // Now, also check the protocol and for a valid url the last time...
  1516. $allowed_protocols = array('http', 'https', 'ftp', 'ftps');
  1517. $url_parts = parse_url($url);
  1518. if ($url_parts === false || empty($url_parts['scheme']) || !in_array($url_parts['scheme'], $allowed_protocols))
  1519. {
  1520. trigger_error('INSECURE_REDIRECT', E_USER_WARNING);
  1521. }
  1522. /**
  1523. * Execute code and/or overwrite redirect()
  1524. *
  1525. * @event core.functions.redirect
  1526. * @var string url The url
  1527. * @var bool return If true, do not redirect but return the sanitized URL.
  1528. * @var bool disable_cd_check If true, redirect() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain.
  1529. * @since 3.1.0-RC3
  1530. */
  1531. $vars = array('url', 'return', 'disable_cd_check');
  1532. extract($phpbb_dispatcher->trigger_event('core.functions.redirect', compact($vars)));
  1533. if ($return)
  1534. {
  1535. return $url;
  1536. }
  1537. else
  1538. {
  1539. garbage_collection();
  1540. }
  1541. // Behave as per HTTP/1.1 spec for others
  1542. header('Location: ' . $url);
  1543. exit;
  1544. }
  1545. /**
  1546. * Re-Apply session id after page reloads
  1547. */
  1548. function reapply_sid($url, $is_route = false)
  1549. {
  1550. global $phpEx, $phpbb_root_path;
  1551. if ($url === "index.$phpEx")
  1552. {
  1553. return append_sid("index.$phpEx");
  1554. }
  1555. else if ($url === "{$phpbb_root_path}index.$phpEx")
  1556. {
  1557. return append_sid("{$phpbb_root_path}index.$phpEx");
  1558. }
  1559. // Remove previously added sid
  1560. if (strpos($url, 'sid=') !== false)
  1561. {
  1562. // All kind of links
  1563. $url = preg_replace('/(\?)?(&amp;|&)?sid=[a-z0-9]+/', '', $url);
  1564. // if the sid was the first param, make the old second as first ones
  1565. $url = preg_replace("/$phpEx(&amp;|&)+?/", "$phpEx?", $url);
  1566. }
  1567. return append_sid($url, false, true, false, $is_route);
  1568. }
  1569. /**
  1570. * Returns url from the session/current page with an re-appended SID with optionally stripping vars from the url
  1571. */
  1572. function build_url($strip_vars = false)
  1573. {
  1574. global $config, $user, $phpbb_path_helper;
  1575. $page = $phpbb_path_helper->get_valid_page($user->page['page'], $config['enable_mod_rewrite']);
  1576. // Append SID
  1577. $redirect = append_sid($page, false, false);
  1578. if ($strip_vars !== false)
  1579. {
  1580. $redirect = $phpbb_path_helper->strip_url_params($redirect, $strip_vars, false);
  1581. }
  1582. else
  1583. {
  1584. $redirect = str_replace('&', '&amp;', $redirect);
  1585. }
  1586. return $redirect . ((strpos($redirect, '?') === false) ? '?' : '');
  1587. }
  1588. /**
  1589. * Meta refresh assignment
  1590. * Adds META template variable with meta http tag.
  1591. *
  1592. * @param int $time Time in seconds for meta refresh tag
  1593. * @param string $url URL to redirect to. The url will go through redirect() first before the template variable is assigned
  1594. * @param bool $disable_cd_check If true, meta_refresh() will redirect to an external domain. If false, the redirect point to the boards url if it does not match the current domain. Default is false.
  1595. */
  1596. function meta_refresh($time, $url, $disable_cd_check = false)
  1597. {
  1598. global $template, $refresh_data, $request;
  1599. $url = redirect($url, true, $disable_cd_check);
  1600. if ($request->is_ajax())
  1601. {
  1602. $refresh_data = array(
  1603. 'time' => $time,
  1604. 'url' => $url,
  1605. );
  1606. }
  1607. else
  1608. {
  1609. // For XHTML compatibility we change back & to &amp;
  1610. $url = str_replace('&', '&amp;', $url);
  1611. $template->assign_vars(array(
  1612. 'META' => '<meta http-equiv="refresh" content="' . $time . '; url=' . $url . '" />')
  1613. );
  1614. }
  1615. return $url;
  1616. }
  1617. /**
  1618. * Outputs correct status line header.
  1619. *
  1620. * Depending on php sapi one of the two following forms is used:
  1621. *
  1622. * Status: 404 Not Found
  1623. *
  1624. * HTTP/1.x 404 Not Found
  1625. *
  1626. * HTTP version is taken from HTTP_VERSION environment variable,
  1627. * and defaults to 1.0.
  1628. *
  1629. * Sample usage:
  1630. *
  1631. * send_status_line(404, 'Not Found');
  1632. *
  1633. * @param int $code HTTP status code
  1634. * @param string $message Message for the status code
  1635. * @return null
  1636. */
  1637. function send_status_line($code, $message)
  1638. {
  1639. if (substr(strtolower(@php_sapi_name()), 0, 3) === 'cgi')
  1640. {
  1641. // in theory, we shouldn't need that due to php doing it. Reality offers a differing opinion, though
  1642. header("Status: $code $message", true, $code);
  1643. }
  1644. else
  1645. {
  1646. $version = phpbb_request_http_version();
  1647. header("$version $code $message", true, $code);
  1648. }
  1649. }
  1650. /**
  1651. * Returns the HTTP version used in the current request.
  1652. *
  1653. * Handles the case of being called before $request is present,
  1654. * in which case it falls back to the $_SERVER superglobal.
  1655. *
  1656. * @return string HTTP version
  1657. */
  1658. function phpbb_request_http_version()
  1659. {
  1660. global $request;
  1661. $version = '';
  1662. if ($request && $request->server('SERVER_PROTOCOL'))
  1663. {
  1664. $version = $request->server('SERVER_PROTOCOL');
  1665. }
  1666. else if (isset($_SERVER['SERVER_PROTOCOL']))
  1667. {
  1668. $version = $_SERVER['SERVER_PROTOCOL'];
  1669. }
  1670. if (!empty($version) && is_string($version) && preg_match('#^HTTP/[0-9]\.[0-9]$#', $version))
  1671. {
  1672. return $version;
  1673. }
  1674. return 'HTTP/1.0';
  1675. }
  1676. //Form validation
  1677. /**
  1678. * Add a secret hash for use in links/GET requests
  1679. * @param string $link_name The name of the link; has to match the name used in check_link_hash, otherwise no restrictions apply
  1680. * @return string the hash
  1681. */
  1682. function generate_link_hash($link_name)
  1683. {
  1684. global $user;
  1685. if (!isset($user->data["hash_$link_name"]))
  1686. {
  1687. $user->data["hash_$link_name"] = substr(sha1($user->data['user_form_salt'] . $link_name), 0, 8);
  1688. }
  1689. return $user->data["hash_$link_name"];
  1690. }
  1691. /**
  1692. * checks a link hash - for GET requests
  1693. * @param string $token the submitted token
  1694. * @param string $link_name The name of the link
  1695. * @return boolean true if all is fine
  1696. */
  1697. function check_link_hash($token, $link_name)
  1698. {
  1699. return $token === generate_link_hash($link_name);
  1700. }
  1701. /**
  1702. * Add a secret token to the form (requires the S_FORM_TOKEN template variable)
  1703. * @param string $form_name The name of the form; has to match the name used in check_form_key, otherwise no restrictions apply
  1704. * @param string $template_variable_suffix A string that is appended to the name of the template variable to which the form elements are assigned
  1705. */
  1706. function add_form_key($form_name, $template_variable_suffix = '')
  1707. {
  1708. global $config, $template, $user, $phpbb_dispatcher;
  1709. $now = time();
  1710. $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
  1711. $token = sha1($now . $user->data['user_form_salt'] . $form_name . $token_sid);
  1712. $s_fields = build_hidden_fields(array(
  1713. 'creation_time' => $now,
  1714. 'form_token' => $token,
  1715. ));
  1716. /**
  1717. * Perform additional actions on creation of the form token
  1718. *
  1719. * @event core.add_form_key
  1720. * @var string form_name The form name
  1721. * @var int now Current time timestamp
  1722. * @var string s_fields Generated hidden fields
  1723. * @var string token Form token
  1724. * @var string token_sid User session ID
  1725. * @var string template_variable_suffix The string that is appended to template variable name
  1726. *
  1727. * @since 3.1.0-RC3
  1728. * @changed 3.1.11-RC1 Added template_variable_suffix
  1729. */
  1730. $vars = array(
  1731. 'form_name',
  1732. 'now',
  1733. 's_fields',
  1734. 'token',
  1735. 'token_sid',
  1736. 'template_variable_suffix',
  1737. );
  1738. extract($phpbb_dispatcher->trigger_event('core.add_form_key', compact($vars)));
  1739. $template->assign_var('S_FORM_TOKEN' . $template_variable_suffix, $s_fields);
  1740. }
  1741. /**
  1742. * Check the form key. Required for all altering actions not secured by confirm_box
  1743. *
  1744. * @param string $form_name The name of the form; has to match the name used
  1745. * in add_form_key, otherwise no restrictions apply
  1746. * @param int $timespan The maximum acceptable age for a submitted form
  1747. * in seconds. Defaults to the config setting.
  1748. * @return bool True, if the form key was valid, false otherwise
  1749. */
  1750. function check_form_key($form_name, $timespan = false)
  1751. {
  1752. global $config, $request, $user;
  1753. if ($timespan === false)
  1754. {
  1755. // we enforce a minimum value of half a minute here.
  1756. $timespan = ($config['form_token_lifetime'] == -1) ? -1 : max(30, $config['form_token_lifetime']);
  1757. }
  1758. if ($request->is_set_post('creation_time') && $request->is_set_post('form_token'))
  1759. {
  1760. $creation_time = abs($request->variable('creation_time', 0));
  1761. $token = $request->variable('form_token', '');
  1762. $diff = time() - $creation_time;
  1763. // If creation_time and the time() now is zero we can assume it was not a human doing this (the check for if ($diff)...
  1764. if (defined('DEBUG_TEST') || $diff && ($diff <= $timespan || $timespan === -1))
  1765. {
  1766. $token_sid = ($user->data['user_id'] == ANONYMOUS && !empty($config['form_token_sid_guests'])) ? $user->session_id : '';
  1767. $key = sha1($creation_time . $user->data['user_form_salt'] . $form_name . $token_sid);
  1768. if ($key === $token)
  1769. {
  1770. return true;
  1771. }
  1772. }
  1773. }
  1774. return false;
  1775. }
  1776. // Message/Login boxes
  1777. /**
  1778. * Build Confirm box
  1779. * @param boolean $check True for checking if confirmed (without any additional parameters) and false for displaying the confirm box
  1780. * @param string|array $title Title/Message used for confirm box.
  1781. * message text is _CONFIRM appended to title.
  1782. * If title cannot be found in user->lang a default one is displayed
  1783. * If title_CONFIRM cannot be found in user->lang the text given is used.
  1784. * If title is an array, the first array value is used as explained per above,
  1785. * all other array values are sent as parameters to the language function.
  1786. * @param string $hidden Hidden variables
  1787. * @param string $html_body Template used for confirm box
  1788. * @param string $u_action Custom form action
  1789. *
  1790. * @return bool True if confirmation was successful, false if not
  1791. */
  1792. function confirm_box($check, $title = '', $hidden = '', $html_body = 'confirm_body.html', $u_action = '')
  1793. {
  1794. global $user, $template, $db, $request;
  1795. global $config, $language, $phpbb_path_helper, $phpbb_dispatcher;
  1796. if (isset($_POST['cancel']))
  1797. {
  1798. return false;
  1799. }
  1800. $confirm = ($language->lang('YES') === $request->variable('confirm', '', true, \phpbb\request\request_interface::POST));
  1801. if ($check && $confirm)
  1802. {
  1803. $user_id = $request->variable('confirm_uid', 0);
  1804. $session_id = $request->variable('sess', '');
  1805. $confirm_key = $request->variable('confirm_key', '');
  1806. if ($user_id != $user->data['user_id'] || $session_id != $user->session_id || !$confirm_key || !$user->data['user_last_confirm_key'] || $confirm_key != $user->data['user_last_confirm_key'])
  1807. {
  1808. return false;
  1809. }
  1810. // Reset user_last_confirm_key
  1811. $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = ''
  1812. WHERE user_id = " . $user->data['user_id'];
  1813. $db->sql_query($sql);
  1814. return true;
  1815. }
  1816. else if ($check)
  1817. {
  1818. return false;
  1819. }
  1820. $s_hidden_fields = build_hidden_fields(array(
  1821. 'confirm_uid' => $user->data['user_id'],
  1822. 'sess' => $user->session_id,
  1823. 'sid' => $user->session_id,
  1824. ));
  1825. // generate activation key
  1826. $confirm_key = gen_rand_string(10);
  1827. // generate language strings
  1828. if (is_array($title))
  1829. {
  1830. $key = array_shift($title);
  1831. $count = array_shift($title);
  1832. $confirm_title = $language->is_set($key) ? $language->lang($key, $count, $title) : $language->lang('CONFIRM');
  1833. $confirm_text = $language->is_set($key . '_CONFIRM') ? $language->lang($key . '_CONFIRM', $count, $title) : $key;
  1834. }
  1835. else
  1836. {
  1837. $confirm_title = $language->is_set($title) ? $language->lang($title) : $language->lang('CONFIRM');
  1838. $confirm_text = $language->is_set($title . '_CONFIRM') ? $language->lang($title . '_CONFIRM') : $title;
  1839. }
  1840. if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
  1841. {
  1842. adm_page_header($confirm_title);
  1843. }
  1844. else
  1845. {
  1846. page_header($confirm_title);
  1847. }
  1848. $template->set_filenames(array(
  1849. 'body' => $html_body)
  1850. );
  1851. // If activation key already exist, we better do not re-use the key (something very strange is going on...)
  1852. if ($request->variable('confirm_key', ''))
  1853. {
  1854. // This should not occur, therefore we cancel the operation to safe the user
  1855. return false;
  1856. }
  1857. // re-add sid / transform & to &amp; for user->page (user->page is always using &)
  1858. $use_page = ($u_action) ? $u_action : str_replace('&', '&amp;', $user->page['page']);
  1859. $u_action = reapply_sid($phpbb_path_helper->get_valid_page($use_page, $config['enable_mod_rewrite']));
  1860. $u_action .= ((strpos($u_action, '?') === false) ? '?' : '&amp;') . 'confirm_key=' . $confirm_key;
  1861. $template->assign_vars(array(
  1862. 'MESSAGE_TITLE' => $confirm_title,
  1863. 'MESSAGE_TEXT' => $confirm_text,
  1864. 'YES_VALUE' => $language->lang('YES'),
  1865. 'S_CONFIRM_ACTION' => $u_action,
  1866. 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields,
  1867. 'S_AJAX_REQUEST' => $request->is_ajax(),
  1868. ));
  1869. $sql = 'UPDATE ' . USERS_TABLE . " SET user_last_confirm_key = '" . $db->sql_escape($confirm_key) . "'
  1870. WHERE user_id = " . $user->data['user_id'];
  1871. $db->sql_query($sql);
  1872. if ($request->is_ajax())
  1873. {
  1874. $u_action .= '&confirm_uid=' . $user->data['user_id'] . '&sess=' . $user->session_id . '&sid=' . $user->session_id;
  1875. $data = array(
  1876. 'MESSAGE_BODY' => $template->assign_display('body'),
  1877. 'MESSAGE_TITLE' => $confirm_title,
  1878. 'MESSAGE_TEXT' => $confirm_text,
  1879. 'YES_VALUE' => $language->lang('YES'),
  1880. 'S_CONFIRM_ACTION' => str_replace('&amp;', '&', $u_action), //inefficient, rewrite whole function
  1881. 'S_HIDDEN_FIELDS' => $hidden . $s_hidden_fields
  1882. );
  1883. /**
  1884. * This event allows an extension to modify the ajax output of confirm box.
  1885. *
  1886. * @event core.confirm_box_ajax_before
  1887. * @var string u_action Action of the form
  1888. * @var array data Data to be sent
  1889. * @var string hidden Hidden fields generated by caller
  1890. * @var string s_hidden_fields Hidden fields generated by this function
  1891. * @since 3.2.8-RC1
  1892. */
  1893. $vars = array(
  1894. 'u_action',
  1895. 'data',
  1896. 'hidden',
  1897. 's_hidden_fields',
  1898. );
  1899. extract($phpbb_dispatcher->trigger_event('core.confirm_box_ajax_before', compact($vars)));
  1900. $json_response = new \phpbb\json_response;
  1901. $json_response->send($data);
  1902. }
  1903. if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
  1904. {
  1905. adm_page_footer();
  1906. }
  1907. else
  1908. {
  1909. page_footer();
  1910. }
  1911. exit; // unreachable, page_footer() above will call exit()
  1912. }
  1913. /**
  1914. * Generate login box or verify password
  1915. */
  1916. function login_box($redirect = '', $l_explain = '', $l_success = '', $admin = false, $s_display = true)
  1917. {
  1918. global $user, $template, $auth, $phpEx, $phpbb_root_path, $config;
  1919. global $request, $phpbb_container, $phpbb_dispatcher, $phpbb_log;
  1920. $err = '';
  1921. $form_name = 'login';
  1922. $username = $autologin = false;
  1923. // Make sure user->setup() has been called
  1924. if (!$user->is_setup())
  1925. {
  1926. $user->setup();
  1927. }
  1928. /**
  1929. * This event allows an extension to modify the login process
  1930. *
  1931. * @event core.login_box_before
  1932. * @var string redirect Redirect string
  1933. * @var string l_explain Explain language string
  1934. * @var string l_success Success language string
  1935. * @var bool admin Is admin?
  1936. * @var bool s_display Display full login form?
  1937. * @var string err Error string
  1938. * @since 3.1.9-RC1
  1939. */
  1940. $vars = array('redirect', 'l_explain', 'l_success', 'admin', 's_display', 'err');
  1941. extract($phpbb_dispatcher->trigger_event('core.login_box_before', compact($vars)));
  1942. // Print out error if user tries to authenticate as an administrator without having the privileges...
  1943. if ($admin && !$auth->acl_get('a_'))
  1944. {
  1945. // Not authd
  1946. // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
  1947. if ($user->data['is_registered'])
  1948. {
  1949. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
  1950. }
  1951. send_status_line(403, 'Forbidden');
  1952. trigger_error('NO_AUTH_ADMIN');
  1953. }
  1954. if (empty($err) && ($request->is_set_post('login') || ($request->is_set('login') && $request->variable('login', '') == 'external')))
  1955. {
  1956. // Get credential
  1957. if ($admin)
  1958. {
  1959. $credential = $request->variable('credential', '');
  1960. if (strspn($credential, 'abcdef0123456789') !== strlen($credential) || strlen($credential) != 32)
  1961. {
  1962. if ($user->data['is_registered'])
  1963. {
  1964. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
  1965. }
  1966. send_status_line(403, 'Forbidden');
  1967. trigger_error('NO_AUTH_ADMIN');
  1968. }
  1969. $password = $request->untrimmed_variable('password_' . $credential, '', true);
  1970. }
  1971. else
  1972. {
  1973. $password = $request->untrimmed_variable('password', '', true);
  1974. }
  1975. $username = $request->variable('username', '', true);
  1976. $autologin = $request->is_set_post('autologin');
  1977. $viewonline = (int) !$request->is_set_post('viewonline');
  1978. $admin = ($admin) ? 1 : 0;
  1979. $viewonline = ($admin) ? $user->data['session_viewonline'] : $viewonline;
  1980. // Check if the supplied username is equal to the one stored within the database if re-authenticating
  1981. if ($admin && utf8_clean_string($username) != utf8_clean_string($user->data['username']))
  1982. {
  1983. // We log the attempt to use a different username...
  1984. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
  1985. send_status_line(403, 'Forbidden');
  1986. trigger_error('NO_AUTH_ADMIN_USER_DIFFER');
  1987. }
  1988. // Check form key
  1989. if ($password && !defined('IN_CHECK_BAN') && !check_form_key($form_name))
  1990. {
  1991. $result = array(
  1992. 'status' => false,
  1993. 'error_msg' => 'FORM_INVALID',
  1994. );
  1995. }
  1996. else
  1997. {
  1998. // If authentication is successful we redirect user to previous page
  1999. $result = $auth->login($username, $password, $autologin, $viewonline, $admin);
  2000. }
  2001. // If admin authentication and login, we will log if it was a success or not...
  2002. // We also break the operation on the first non-success login - it could be argued that the user already knows
  2003. if ($admin)
  2004. {
  2005. if ($result['status'] == LOGIN_SUCCESS)
  2006. {
  2007. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_SUCCESS');
  2008. }
  2009. else
  2010. {
  2011. // Only log the failed attempt if a real user tried to.
  2012. // anonymous/inactive users are never able to go to the ACP even if they have the relevant permissions
  2013. if ($user->data['is_registered'])
  2014. {
  2015. $phpbb_log->add('admin', $user->data['user_id'], $user->ip, 'LOG_ADMIN_AUTH_FAIL');
  2016. }
  2017. }
  2018. }
  2019. // The result parameter is always an array, holding the relevant information...
  2020. if ($result['status'] == LOGIN_SUCCESS)
  2021. {
  2022. $redirect = $request->variable('redirect', "{$phpbb_root_path}index.$phpEx");
  2023. /**
  2024. * This event allows an extension to modify the redirection when a user successfully logs in
  2025. *
  2026. * @event core.login_box_redirect
  2027. * @var string redirect Redirect string
  2028. * @var bool admin Is admin?
  2029. * @var array result Result from auth provider
  2030. * @since 3.1.0-RC5
  2031. * @changed 3.1.9-RC1 Removed undefined return variable
  2032. * @changed 3.2.4-RC1 Added result
  2033. */
  2034. $vars = array('redirect', 'admin', 'result');
  2035. extract($phpbb_dispatcher->trigger_event('core.login_box_redirect', compact($vars)));
  2036. // append/replace SID (may change during the session for AOL users)
  2037. $redirect = reapply_sid($redirect);
  2038. // Special case... the user is effectively banned, but we allow founders to login
  2039. if (defined('IN_CHECK_BAN') && $result['user_row']['user_type'] != USER_FOUNDER)
  2040. {
  2041. return;
  2042. }
  2043. redirect($redirect);
  2044. }
  2045. // Something failed, determine what...
  2046. if ($result['status'] == LOGIN_BREAK)
  2047. {
  2048. trigger_error($result['error_msg']);
  2049. }
  2050. // Special cases... determine
  2051. switch ($result['status'])
  2052. {
  2053. case LOGIN_ERROR_PASSWORD_CONVERT:
  2054. $err = sprintf(
  2055. $user->lang[$result['error_msg']],
  2056. ($config['email_enable']) ? '<a href="' . append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=sendpassword') . '">' : '',
  2057. ($config['email_enable']) ? '</a>' : '',
  2058. '<a href="' . phpbb_get_board_contact_link($config, $phpbb_root_path, $phpEx) . '">',
  2059. '</a>'
  2060. );
  2061. break;
  2062. case LOGIN_ERROR_ATTEMPTS:
  2063. $captcha = $phpbb_container->get('captcha.factory')->get_instance($config['captcha_plugin']);
  2064. $captcha->init(CONFIRM_LOGIN);
  2065. // $captcha->reset();
  2066. $template->assign_vars(array(
  2067. 'CAPTCHA_TEMPLATE' => $captcha->get_template(),
  2068. ));
  2069. // no break;
  2070. // Username, password, etc...
  2071. default:
  2072. $err = $user->lang[$result['error_msg']];
  2073. // Assign admin contact to some error messages
  2074. if ($result['error_msg'] == 'LOGIN_ERROR_USERNAME' || $result['error_msg'] == 'LOGIN_ERROR_PASSWORD')
  2075. {
  2076. $err = sprintf($user->lang[$result['error_msg']], '<a href="' . append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') . '">', '</a>');
  2077. }
  2078. break;
  2079. }
  2080. /**
  2081. * This event allows an extension to process when a user fails a login attempt
  2082. *
  2083. * @event core.login_box_failed
  2084. * @var array result Login result data
  2085. * @var string username User name used to login
  2086. * @var string password Password used to login
  2087. * @var string err Error message
  2088. * @since 3.1.3-RC1
  2089. */
  2090. $vars = array('result', 'username', 'password', 'err');
  2091. extract($phpbb_dispatcher->trigger_event('core.login_box_failed', compact($vars)));
  2092. }
  2093. // Assign credential for username/password pair
  2094. $credential = ($admin) ? md5(unique_id()) : false;
  2095. $s_hidden_fields = array(
  2096. 'sid' => $user->session_id,
  2097. );
  2098. if ($redirect)
  2099. {
  2100. $s_hidden_fields['redirect'] = $redirect;
  2101. }
  2102. if ($admin)
  2103. {
  2104. $s_hidden_fields['credential'] = $credential;
  2105. }
  2106. /* @var $provider_collection \phpbb\auth\provider_collection */
  2107. $provider_collection = $phpbb_container->get('auth.provider_collection');
  2108. $auth_provider = $provider_collection->get_provider();
  2109. $auth_provider_data = $auth_provider->get_login_data();
  2110. if ($auth_provider_data)
  2111. {
  2112. if (isset($auth_provider_data['VARS']))
  2113. {
  2114. $template->assign_vars($auth_provider_data['VARS']);
  2115. }
  2116. if (isset($auth_provider_data['BLOCK_VAR_NAME']))
  2117. {
  2118. foreach ($auth_provider_data['BLOCK_VARS'] as $block_vars)
  2119. {
  2120. $template->assign_block_vars($auth_provider_data['BLOCK_VAR_NAME'], $block_vars);
  2121. }
  2122. }
  2123. $template->assign_vars(array(
  2124. 'PROVIDER_TEMPLATE_FILE' => $auth_provider_data['TEMPLATE_FILE'],
  2125. ));
  2126. }
  2127. $s_hidden_fields = build_hidden_fields($s_hidden_fields);
  2128. /** @var \phpbb\controller\helper $controller_helper */
  2129. $controller_helper = $phpbb_container->get('controller.helper');
  2130. $login_box_template_data = array(
  2131. 'LOGIN_ERROR' => $err,
  2132. 'LOGIN_EXPLAIN' => $l_explain,
  2133. 'U_SEND_PASSWORD' => ($config['email_enable']) ? $controller_helper->route('phpbb_ucp_forgot_password_controller') : '',
  2134. 'U_RESEND_ACTIVATION' => ($config['require_activation'] == USER_ACTIVATION_SELF && $config['email_enable']) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=resend_act') : '',
  2135. 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
  2136. 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
  2137. 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')),
  2138. 'S_DISPLAY_FULL_LOGIN' => ($s_display) ? true : false,
  2139. 'S_HIDDEN_FIELDS' => $s_hidden_fields,
  2140. 'S_ADMIN_AUTH' => $admin,
  2141. 'USERNAME' => ($admin) ? $user->data['username'] : '',
  2142. 'USERNAME_CREDENTIAL' => 'username',
  2143. 'PASSWORD_CREDENTIAL' => ($admin) ? 'password_' . $credential : 'password',
  2144. );
  2145. /**
  2146. * Event to add/modify login box template data
  2147. *
  2148. * @event core.login_box_modify_template_data
  2149. * @var int admin Flag whether user is admin
  2150. * @var string username User name
  2151. * @var int autologin Flag whether autologin is enabled
  2152. * @var string redirect Redirect URL
  2153. * @var array login_box_template_data Array with the login box template data
  2154. * @since 3.2.3-RC2
  2155. */
  2156. $vars = array(
  2157. 'admin',
  2158. 'username',
  2159. 'autologin',
  2160. 'redirect',
  2161. 'login_box_template_data',
  2162. );
  2163. extract($phpbb_dispatcher->trigger_event('core.login_box_modify_template_data', compact($vars)));
  2164. $template->assign_vars($login_box_template_data);
  2165. page_header($user->lang['LOGIN']);
  2166. $template->set_filenames(array(
  2167. 'body' => 'login_body.html')
  2168. );
  2169. make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"));
  2170. page_footer();
  2171. }
  2172. /**
  2173. * Generate forum login box
  2174. */
  2175. function login_forum_box($forum_data)
  2176. {
  2177. global $db, $phpbb_container, $request, $template, $user, $phpbb_dispatcher, $phpbb_root_path, $phpEx;
  2178. $password = $request->variable('password', '', true);
  2179. $sql = 'SELECT forum_id
  2180. FROM ' . FORUMS_ACCESS_TABLE . '
  2181. WHERE forum_id = ' . $forum_data['forum_id'] . '
  2182. AND user_id = ' . $user->data['user_id'] . "
  2183. AND session_id = '" . $db->sql_escape($user->session_id) . "'";
  2184. $result = $db->sql_query($sql);
  2185. $row = $db->sql_fetchrow($result);
  2186. $db->sql_freeresult($result);
  2187. if ($row)
  2188. {
  2189. return true;
  2190. }
  2191. if ($password)
  2192. {
  2193. // Remove expired authorised sessions
  2194. $sql = 'SELECT f.session_id
  2195. FROM ' . FORUMS_ACCESS_TABLE . ' f
  2196. LEFT JOIN ' . SESSIONS_TABLE . ' s ON (f.session_id = s.session_id)
  2197. WHERE s.session_id IS NULL';
  2198. $result = $db->sql_query($sql);
  2199. if ($row = $db->sql_fetchrow($result))
  2200. {
  2201. $sql_in = array();
  2202. do
  2203. {
  2204. $sql_in[] = (string) $row['session_id'];
  2205. }
  2206. while ($row = $db->sql_fetchrow($result));
  2207. // Remove expired sessions
  2208. $sql = 'DELETE FROM ' . FORUMS_ACCESS_TABLE . '
  2209. WHERE ' . $db->sql_in_set('session_id', $sql_in);
  2210. $db->sql_query($sql);
  2211. }
  2212. $db->sql_freeresult($result);
  2213. /* @var $passwords_manager \phpbb\passwords\manager */
  2214. $passwords_manager = $phpbb_container->get('passwords.manager');
  2215. if ($passwords_manager->check($password, $forum_data['forum_password']))
  2216. {
  2217. $sql_ary = array(
  2218. 'forum_id' => (int) $forum_data['forum_id'],
  2219. 'user_id' => (int) $user->data['user_id'],
  2220. 'session_id' => (string) $user->session_id,
  2221. );
  2222. $db->sql_query('INSERT INTO ' . FORUMS_ACCESS_TABLE . ' ' . $db->sql_build_array('INSERT', $sql_ary));
  2223. return true;
  2224. }
  2225. $template->assign_var('LOGIN_ERROR', $user->lang['WRONG_PASSWORD']);
  2226. }
  2227. /**
  2228. * Performing additional actions, load additional data on forum login
  2229. *
  2230. * @event core.login_forum_box
  2231. * @var array forum_data Array with forum data
  2232. * @var string password Password entered
  2233. * @since 3.1.0-RC3
  2234. */
  2235. $vars = array('forum_data', 'password');
  2236. extract($phpbb_dispatcher->trigger_event('core.login_forum_box', compact($vars)));
  2237. page_header($user->lang['LOGIN']);
  2238. $template->assign_vars(array(
  2239. 'FORUM_NAME' => isset($forum_data['forum_name']) ? $forum_data['forum_name'] : '',
  2240. 'S_LOGIN_ACTION' => build_url(array('f')),
  2241. 'S_HIDDEN_FIELDS' => build_hidden_fields(array('f' => $forum_data['forum_id'])))
  2242. );
  2243. $template->set_filenames(array(
  2244. 'body' => 'login_forum.html')
  2245. );
  2246. make_jumpbox(append_sid("{$phpbb_root_path}viewforum.$phpEx"), $forum_data['forum_id']);
  2247. page_footer();
  2248. }
  2249. // Little helpers
  2250. /**
  2251. * Little helper for the build_hidden_fields function
  2252. */
  2253. function _build_hidden_fields($key, $value, $specialchar, $stripslashes)
  2254. {
  2255. $hidden_fields = '';
  2256. if (!is_array($value))
  2257. {
  2258. $value = ($stripslashes) ? stripslashes($value) : $value;
  2259. $value = ($specialchar) ? htmlspecialchars($value, ENT_COMPAT, 'UTF-8') : $value;
  2260. $hidden_fields .= '<input type="hidden" name="' . $key . '" value="' . $value . '" />' . "\n";
  2261. }
  2262. else
  2263. {
  2264. foreach ($value as $_key => $_value)
  2265. {
  2266. $_key = ($stripslashes) ? stripslashes($_key) : $_key;
  2267. $_key = ($specialchar) ? htmlspecialchars($_key, ENT_COMPAT, 'UTF-8') : $_key;
  2268. $hidden_fields .= _build_hidden_fields($key . '[' . $_key . ']', $_value, $specialchar, $stripslashes);
  2269. }
  2270. }
  2271. return $hidden_fields;
  2272. }
  2273. /**
  2274. * Build simple hidden fields from array
  2275. *
  2276. * @param array $field_ary an array of values to build the hidden field from
  2277. * @param bool $specialchar if true, keys and values get specialchared
  2278. * @param bool $stripslashes if true, keys and values get stripslashed
  2279. *
  2280. * @return string the hidden fields
  2281. */
  2282. function build_hidden_fields($field_ary, $specialchar = false, $stripslashes = false)
  2283. {
  2284. $s_hidden_fields = '';
  2285. foreach ($field_ary as $name => $vars)
  2286. {
  2287. $name = ($stripslashes) ? stripslashes($name) : $name;
  2288. $name = ($specialchar) ? htmlspecialchars($name, ENT_COMPAT, 'UTF-8') : $name;
  2289. $s_hidden_fields .= _build_hidden_fields($name, $vars, $specialchar, $stripslashes);
  2290. }
  2291. return $s_hidden_fields;
  2292. }
  2293. /**
  2294. * Parse cfg file
  2295. */
  2296. function parse_cfg_file($filename, $lines = false)
  2297. {
  2298. $parsed_items = array();
  2299. if ($lines === false)
  2300. {
  2301. $lines = file($filename);
  2302. }
  2303. foreach ($lines as $line)
  2304. {
  2305. $line = trim($line);
  2306. if (!$line || $line[0] == '#' || ($delim_pos = strpos($line, '=')) === false)
  2307. {
  2308. continue;
  2309. }
  2310. // Determine first occurrence, since in values the equal sign is allowed
  2311. $key = htmlspecialchars(strtolower(trim(substr($line, 0, $delim_pos))));
  2312. $value = trim(substr($line, $delim_pos + 1));
  2313. if (in_array($value, array('off', 'false', '0')))
  2314. {
  2315. $value = false;
  2316. }
  2317. else if (in_array($value, array('on', 'true', '1')))
  2318. {
  2319. $value = true;
  2320. }
  2321. else if (!trim($value))
  2322. {
  2323. $value = '';
  2324. }
  2325. else if (($value[0] == "'" && $value[strlen($value) - 1] == "'") || ($value[0] == '"' && $value[strlen($value) - 1] == '"'))
  2326. {
  2327. $value = htmlspecialchars(substr($value, 1, strlen($value)-2));
  2328. }
  2329. else
  2330. {
  2331. $value = htmlspecialchars($value);
  2332. }
  2333. $parsed_items[$key] = $value;
  2334. }
  2335. if (isset($parsed_items['parent']) && isset($parsed_items['name']) && $parsed_items['parent'] == $parsed_items['name'])
  2336. {
  2337. unset($parsed_items['parent']);
  2338. }
  2339. return $parsed_items;
  2340. }
  2341. /**
  2342. * Return a nicely formatted backtrace.
  2343. *
  2344. * Turns the array returned by debug_backtrace() into HTML markup.
  2345. * Also filters out absolute paths to phpBB root.
  2346. *
  2347. * @return string HTML markup
  2348. */
  2349. function get_backtrace()
  2350. {
  2351. $output = '<div style="font-family: monospace;">';
  2352. $backtrace = debug_backtrace();
  2353. // We skip the first one, because it only shows this file/function
  2354. unset($backtrace[0]);
  2355. foreach ($backtrace as $trace)
  2356. {
  2357. // Strip the current directory from path
  2358. $trace['file'] = (empty($trace['file'])) ? '(not given by php)' : htmlspecialchars(phpbb_filter_root_path($trace['file']));
  2359. $trace['line'] = (empty($trace['line'])) ? '(not given by php)' : $trace['line'];
  2360. // Only show function arguments for include etc.
  2361. // Other parameters may contain sensible information
  2362. $argument = '';
  2363. if (!empty($trace['args'][0]) && in_array($trace['function'], array('include', 'require', 'include_once', 'require_once')))
  2364. {
  2365. $argument = htmlspecialchars(phpbb_filter_root_path($trace['args'][0]));
  2366. }
  2367. $trace['class'] = (!isset($trace['class'])) ? '' : $trace['class'];
  2368. $trace['type'] = (!isset($trace['type'])) ? '' : $trace['type'];
  2369. $output .= '<br />';
  2370. $output .= '<b>FILE:</b> ' . $trace['file'] . '<br />';
  2371. $output .= '<b>LINE:</b> ' . ((!empty($trace['line'])) ? $trace['line'] : '') . '<br />';
  2372. $output .= '<b>CALL:</b> ' . htmlspecialchars($trace['class'] . $trace['type'] . $trace['function']);
  2373. $output .= '(' . (($argument !== '') ? "'$argument'" : '') . ')<br />';
  2374. }
  2375. $output .= '</div>';
  2376. return $output;
  2377. }
  2378. /**
  2379. * This function returns a regular expression pattern for commonly used expressions
  2380. * Use with / as delimiter for email mode and # for url modes
  2381. * mode can be: email|bbcode_htm|url|url_inline|www_url|www_url_inline|relative_url|relative_url_inline|ipv4|ipv6
  2382. */
  2383. function get_preg_expression($mode)
  2384. {
  2385. switch ($mode)
  2386. {
  2387. case 'email':
  2388. // Regex written by James Watts and Francisco Jose Martin Moreno
  2389. // http://fightingforalostcause.net/misc/2006/compare-email-regex.php
  2390. return '((?:[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*(?:[\w\!\#$\%\'\*\+\-\/\=\?\^\`{\|\}\~]|&amp;)+)@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,63})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)';
  2391. break;
  2392. case 'bbcode_htm':
  2393. return array(
  2394. '#<!\-\- e \-\-><a href="mailto:(.*?)">.*?</a><!\-\- e \-\->#',
  2395. '#<!\-\- l \-\-><a (?:class="[\w-]+" )?href="(.*?)(?:(&amp;|\?)sid=[0-9a-f]{32})?">.*?</a><!\-\- l \-\->#',
  2396. '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="http://(.*?)">\2</a><!\-\- \1 \-\->#',
  2397. '#<!\-\- ([mw]) \-\-><a (?:class="[\w-]+" )?href="(.*?)">.*?</a><!\-\- \1 \-\->#',
  2398. '#<!\-\- s(.*?) \-\-><img src="\{SMILIES_PATH\}\/.*? \/><!\-\- s\1 \-\->#',
  2399. '#<!\-\- .*? \-\->#s',
  2400. '#<.*?>#s',
  2401. );
  2402. break;
  2403. // Whoa these look impressive!
  2404. // The code to generate the following two regular expressions which match valid IPv4/IPv6 addresses
  2405. // can be found in the develop directory
  2406. // @deprecated
  2407. case 'ipv4':
  2408. return '#^(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$#';
  2409. break;
  2410. // @deprecated
  2411. case 'ipv6':
  2412. return '#^(?:(?:(?:[\dA-F]{1,4}:){6}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:::(?:[\dA-F]{1,4}:){0,5}(?:[\dA-F]{1,4}(?::[\dA-F]{1,4})?|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:):(?:[\dA-F]{1,4}:){4}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,2}:(?:[\dA-F]{1,4}:){3}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,3}:(?:[\dA-F]{1,4}:){2}(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,4}:(?:[\dA-F]{1,4}:)(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,5}:(?:[\dA-F]{1,4}:[\dA-F]{1,4}|(?:(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])))|(?:(?:[\dA-F]{1,4}:){1,6}:[\dA-F]{1,4})|(?:(?:[\dA-F]{1,4}:){1,7}:)|(?:::))$#i';
  2413. break;
  2414. case 'url':
  2415. // generated with regex_idn.php file in the develop folder
  2416. return "[a-z][a-z\d+\-.]*(?<!javascript):/{2}(?:(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  2417. break;
  2418. case 'url_http':
  2419. // generated with regex_idn.php file in the develop folder
  2420. return "http[s]?:/{2}(?:(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  2421. break;
  2422. case 'url_inline':
  2423. // generated with regex_idn.php file in the develop folder
  2424. return "[a-z][a-z\d+]*(?<!javascript):/{2}(?:(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})+|[0-9.]+|\[[a-z0-9.]+:[a-z0-9.]+:[a-z0-9.:]+\])(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  2425. break;
  2426. case 'www_url':
  2427. // generated with regex_idn.php file in the develop folder
  2428. return "www\.(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})+(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  2429. break;
  2430. case 'www_url_inline':
  2431. // generated with regex_idn.php file in the develop folder
  2432. return "www\.(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})+(?::\d*)?(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  2433. break;
  2434. case 'relative_url':
  2435. // generated with regex_idn.php file in the develop folder
  2436. return "(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'()*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  2437. break;
  2438. case 'relative_url_inline':
  2439. // generated with regex_idn.php file in the develop folder
  2440. return "(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*(?:/(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@|]+|%[\dA-F]{2})*)*(?:\?(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?(?:\#(?:[^\p{C}\p{Z}\p{S}\p{P}\p{Nl}\p{No}\p{Me}\x{1100}-\x{115F}\x{A960}-\x{A97C}\x{1160}-\x{11A7}\x{D7B0}-\x{D7C6}\x{20D0}-\x{20FF}\x{1D100}-\x{1D1FF}\x{1D200}-\x{1D24F}\x{0640}\x{07FA}\x{302E}\x{302F}\x{3031}-\x{3035}\x{303B}]*[\x{00B7}\x{0375}\x{05F3}\x{05F4}\x{30FB}\x{002D}\x{06FD}\x{06FE}\x{0F0B}\x{3007}\x{00DF}\x{03C2}\x{200C}\x{200D}\pL0-9\-._~!$&'(*+,;=:@/?|]+|%[\dA-F]{2})*)?";
  2441. break;
  2442. case 'table_prefix':
  2443. return '#^[a-zA-Z][a-zA-Z0-9_]*$#';
  2444. break;
  2445. // Matches the predecing dot
  2446. case 'path_remove_dot_trailing_slash':
  2447. return '#^(?:(\.)?)+(?:(.+)?)+(?:([\\/\\\])$)#';
  2448. break;
  2449. case 'semantic_version':
  2450. // Regular expression to match semantic versions by http://rgxdb.com/
  2451. return '/(?<=^[Vv]|^)(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?<prerelease>(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:0|[1-9](?:(?:0|[1-9])+)*)))*))?(?:[+](?<build>(?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+))(?:[.](?:(?:(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?|(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)(?:[A-Za-z]|-)(?:(?:(?:0|[1-9])|(?:[A-Za-z]|-))+)?)|(?:(?:0|[1-9])+)))*))?)$/';
  2452. break;
  2453. }
  2454. return '';
  2455. }
  2456. /**
  2457. * Generate regexp for naughty words censoring
  2458. * Depends on whether installed PHP version supports unicode properties
  2459. *
  2460. * @param string $word word template to be replaced
  2461. *
  2462. * @return string $preg_expr regex to use with word censor
  2463. */
  2464. function get_censor_preg_expression($word)
  2465. {
  2466. // Unescape the asterisk to simplify further conversions
  2467. $word = str_replace('\*', '*', preg_quote($word, '#'));
  2468. // Replace asterisk(s) inside the pattern, at the start and at the end of it with regexes
  2469. $word = preg_replace(array('#(?<=[\p{Nd}\p{L}_])\*+(?=[\p{Nd}\p{L}_])#iu', '#^\*+#', '#\*+$#'), array('([\x20]*?|[\p{Nd}\p{L}_-]*?)', '[\p{Nd}\p{L}_-]*?', '[\p{Nd}\p{L}_-]*?'), $word);
  2470. // Generate the final substitution
  2471. $preg_expr = '#(?<![\p{Nd}\p{L}_-])(' . $word . ')(?![\p{Nd}\p{L}_-])#iu';
  2472. return $preg_expr;
  2473. }
  2474. /**
  2475. * Returns the first block of the specified IPv6 address and as many additional
  2476. * ones as specified in the length paramater.
  2477. * If length is zero, then an empty string is returned.
  2478. * If length is greater than 3 the complete IP will be returned
  2479. */
  2480. function short_ipv6($ip, $length)
  2481. {
  2482. if ($length < 1)
  2483. {
  2484. return '';
  2485. }
  2486. // extend IPv6 addresses
  2487. $blocks = substr_count($ip, ':') + 1;
  2488. if ($blocks < 9)
  2489. {
  2490. $ip = str_replace('::', ':' . str_repeat('0000:', 9 - $blocks), $ip);
  2491. }
  2492. if ($ip[0] == ':')
  2493. {
  2494. $ip = '0000' . $ip;
  2495. }
  2496. if ($length < 4)
  2497. {
  2498. $ip = implode(':', array_slice(explode(':', $ip), 0, 1 + $length));
  2499. }
  2500. return $ip;
  2501. }
  2502. /**
  2503. * Normalises an internet protocol address,
  2504. * also checks whether the specified address is valid.
  2505. *
  2506. * IPv4 addresses are returned 'as is'.
  2507. *
  2508. * IPv6 addresses are normalised according to
  2509. * A Recommendation for IPv6 Address Text Representation
  2510. * http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-07
  2511. *
  2512. * @param string $address IP address
  2513. *
  2514. * @return mixed false if specified address is not valid,
  2515. * string otherwise
  2516. */
  2517. function phpbb_ip_normalise(string $address)
  2518. {
  2519. $ip_normalised = false;
  2520. if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
  2521. {
  2522. $ip_normalised = $address;
  2523. }
  2524. else if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
  2525. {
  2526. $ip_normalised = inet_ntop(inet_pton($address));
  2527. // If is ipv4
  2528. if (stripos($ip_normalised, '::ffff:') === 0)
  2529. {
  2530. $ip_normalised = substr($ip_normalised, 7);
  2531. }
  2532. }
  2533. return $ip_normalised;
  2534. }
  2535. // Handler, header and footer
  2536. /**
  2537. * Error and message handler, call with trigger_error if read
  2538. */
  2539. function msg_handler($errno, $msg_text, $errfile, $errline)
  2540. {
  2541. global $cache, $db, $auth, $template, $config, $user, $request;
  2542. global $phpbb_root_path, $msg_title, $msg_long_text, $phpbb_log;
  2543. global $phpbb_container;
  2544. // Do not display notices if we suppress them via @
  2545. if (error_reporting() == 0 && $errno != E_USER_ERROR && $errno != E_USER_WARNING && $errno != E_USER_NOTICE)
  2546. {
  2547. return;
  2548. }
  2549. // Message handler is stripping text. In case we need it, we are possible to define long text...
  2550. if (isset($msg_long_text) && $msg_long_text && !$msg_text)
  2551. {
  2552. $msg_text = $msg_long_text;
  2553. }
  2554. switch ($errno)
  2555. {
  2556. case E_NOTICE:
  2557. case E_WARNING:
  2558. // Check the error reporting level and return if the error level does not match
  2559. // If DEBUG is defined the default level is E_ALL
  2560. if (($errno & ($phpbb_container->getParameter('debug.show_errors') ? E_ALL : error_reporting())) == 0)
  2561. {
  2562. return;
  2563. }
  2564. if (strpos($errfile, 'cache') === false && strpos($errfile, 'template.') === false)
  2565. {
  2566. $errfile = phpbb_filter_root_path($errfile);
  2567. $msg_text = phpbb_filter_root_path($msg_text);
  2568. $error_name = ($errno === E_WARNING) ? 'PHP Warning' : 'PHP Notice';
  2569. echo '<b>[phpBB Debug] ' . $error_name . '</b>: in file <b>' . $errfile . '</b> on line <b>' . $errline . '</b>: <b>' . $msg_text . '</b><br />' . "\n";
  2570. // we are writing an image - the user won't see the debug, so let's place it in the log
  2571. if (defined('IMAGE_OUTPUT') || defined('IN_CRON'))
  2572. {
  2573. $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_IMAGE_GENERATION_ERROR', false, array($errfile, $errline, $msg_text));
  2574. }
  2575. // echo '<br /><br />BACKTRACE<br />' . get_backtrace() . '<br />' . "\n";
  2576. }
  2577. return;
  2578. break;
  2579. case E_USER_ERROR:
  2580. if (!empty($user) && $user->is_setup())
  2581. {
  2582. $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
  2583. $msg_title = (!isset($msg_title)) ? $user->lang['GENERAL_ERROR'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
  2584. $l_return_index = sprintf($user->lang['RETURN_INDEX'], '<a href="' . $phpbb_root_path . '">', '</a>');
  2585. $l_notify = '';
  2586. if (!empty($config['board_contact']))
  2587. {
  2588. $l_notify = '<p>' . sprintf($user->lang['NOTIFY_ADMIN_EMAIL'], $config['board_contact']) . '</p>';
  2589. }
  2590. }
  2591. else
  2592. {
  2593. $msg_title = 'General Error';
  2594. $l_return_index = '<a href="' . $phpbb_root_path . '">Return to index page</a>';
  2595. $l_notify = '';
  2596. if (!empty($config['board_contact']))
  2597. {
  2598. $l_notify = '<p>Please notify the board administrator or webmaster: <a href="mailto:' . $config['board_contact'] . '">' . $config['board_contact'] . '</a></p>';
  2599. }
  2600. }
  2601. $log_text = $msg_text;
  2602. $backtrace = get_backtrace();
  2603. if ($backtrace)
  2604. {
  2605. $log_text .= '<br /><br />BACKTRACE<br />' . $backtrace;
  2606. }
  2607. if (defined('IN_INSTALL') || ($phpbb_container != null && $phpbb_container->getParameter('debug.show_errors')) || isset($auth) && $auth->acl_get('a_'))
  2608. {
  2609. $msg_text = $log_text;
  2610. // If this is defined there already was some output
  2611. // So let's not break it
  2612. if (defined('IN_DB_UPDATE'))
  2613. {
  2614. echo '<div class="errorbox">' . $msg_text . '</div>';
  2615. $db->sql_return_on_error(true);
  2616. phpbb_end_update($cache, $config);
  2617. }
  2618. }
  2619. if ((defined('IN_CRON') || defined('IMAGE_OUTPUT')) && isset($db))
  2620. {
  2621. // let's avoid loops
  2622. $db->sql_return_on_error(true);
  2623. $phpbb_log->add('critical', $user->data['user_id'], $user->ip, 'LOG_GENERAL_ERROR', false, array($msg_title, $log_text));
  2624. $db->sql_return_on_error(false);
  2625. }
  2626. // Do not send 200 OK, but service unavailable on errors
  2627. send_status_line(503, 'Service Unavailable');
  2628. garbage_collection();
  2629. // Try to not call the adm page data...
  2630. echo '<!DOCTYPE html>';
  2631. echo '<html dir="ltr">';
  2632. echo '<head>';
  2633. echo '<meta charset="utf-8">';
  2634. echo '<meta http-equiv="X-UA-Compatible" content="IE=edge">';
  2635. echo '<title>' . $msg_title . '</title>';
  2636. echo '<style type="text/css">' . "\n" . '/* <![CDATA[ */' . "\n";
  2637. echo '* { margin: 0; padding: 0; } html { font-size: 100%; height: 100%; margin-bottom: 1px; background-color: #E4EDF0; } body { font-family: "Lucida Grande", Verdana, Helvetica, Arial, sans-serif; color: #536482; background: #E4EDF0; font-size: 62.5%; margin: 0; } ';
  2638. echo 'a:link, a:active, a:visited { color: #006699; text-decoration: none; } a:hover { color: #DD6900; text-decoration: underline; } ';
  2639. echo '#wrap { padding: 0 20px 15px 20px; min-width: 615px; } #page-header { text-align: right; height: 40px; } #page-footer { clear: both; font-size: 1em; text-align: center; } ';
  2640. echo '.panel { margin: 4px 0; background-color: #FFFFFF; border: solid 1px #A9B8C2; } ';
  2641. echo '#errorpage #page-header a { font-weight: bold; line-height: 6em; } #errorpage #content { padding: 10px; } #errorpage #content h1 { line-height: 1.2em; margin-bottom: 0; color: #DF075C; } ';
  2642. echo '#errorpage #content div { margin-top: 20px; margin-bottom: 5px; border-bottom: 1px solid #CCCCCC; padding-bottom: 5px; color: #333333; font: bold 1.2em "Lucida Grande", Arial, Helvetica, sans-serif; text-decoration: none; line-height: 120%; text-align: left; } ';
  2643. echo "\n" . '/* ]]> */' . "\n";
  2644. echo '</style>';
  2645. echo '</head>';
  2646. echo '<body id="errorpage">';
  2647. echo '<div id="wrap">';
  2648. echo ' <div id="page-header">';
  2649. echo ' ' . $l_return_index;
  2650. echo ' </div>';
  2651. echo ' <div id="acp">';
  2652. echo ' <div class="panel">';
  2653. echo ' <div id="content">';
  2654. echo ' <h1>' . $msg_title . '</h1>';
  2655. echo ' <div>' . $msg_text . '</div>';
  2656. echo $l_notify;
  2657. echo ' </div>';
  2658. echo ' </div>';
  2659. echo ' </div>';
  2660. echo ' <div id="page-footer">';
  2661. echo ' Powered by <a href="https://www.phpbb.com/">phpBB</a>&reg; Forum Software &copy; phpBB Limited';
  2662. echo ' </div>';
  2663. echo '</div>';
  2664. echo '</body>';
  2665. echo '</html>';
  2666. exit_handler();
  2667. // On a fatal error (and E_USER_ERROR *is* fatal) we never want other scripts to continue and force an exit here.
  2668. exit;
  2669. break;
  2670. case E_USER_WARNING:
  2671. case E_USER_NOTICE:
  2672. define('IN_ERROR_HANDLER', true);
  2673. if (empty($user->data))
  2674. {
  2675. $user->session_begin();
  2676. }
  2677. // We re-init the auth array to get correct results on login/logout
  2678. $auth->acl($user->data);
  2679. if (!$user->is_setup())
  2680. {
  2681. $user->setup();
  2682. }
  2683. if ($msg_text == 'ERROR_NO_ATTACHMENT' || $msg_text == 'NO_FORUM' || $msg_text == 'NO_TOPIC' || $msg_text == 'NO_USER')
  2684. {
  2685. send_status_line(404, 'Not Found');
  2686. }
  2687. $msg_text = (!empty($user->lang[$msg_text])) ? $user->lang[$msg_text] : $msg_text;
  2688. $msg_title = (!isset($msg_title)) ? $user->lang['INFORMATION'] : ((!empty($user->lang[$msg_title])) ? $user->lang[$msg_title] : $msg_title);
  2689. if (!defined('HEADER_INC'))
  2690. {
  2691. if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
  2692. {
  2693. adm_page_header($msg_title);
  2694. }
  2695. else
  2696. {
  2697. page_header($msg_title);
  2698. }
  2699. }
  2700. $template->set_filenames(array(
  2701. 'body' => 'message_body.html')
  2702. );
  2703. $template->assign_vars(array(
  2704. 'MESSAGE_TITLE' => $msg_title,
  2705. 'MESSAGE_TEXT' => $msg_text,
  2706. 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false,
  2707. 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false)
  2708. );
  2709. if ($request->is_ajax())
  2710. {
  2711. global $refresh_data;
  2712. $json_response = new \phpbb\json_response;
  2713. $json_response->send(array(
  2714. 'MESSAGE_TITLE' => $msg_title,
  2715. 'MESSAGE_TEXT' => $msg_text,
  2716. 'S_USER_WARNING' => ($errno == E_USER_WARNING) ? true : false,
  2717. 'S_USER_NOTICE' => ($errno == E_USER_NOTICE) ? true : false,
  2718. 'REFRESH_DATA' => (!empty($refresh_data)) ? $refresh_data : null
  2719. ));
  2720. }
  2721. // We do not want the cron script to be called on error messages
  2722. define('IN_CRON', true);
  2723. if (defined('IN_ADMIN') && isset($user->data['session_admin']) && $user->data['session_admin'])
  2724. {
  2725. adm_page_footer();
  2726. }
  2727. else
  2728. {
  2729. page_footer();
  2730. }
  2731. exit_handler();
  2732. break;
  2733. // PHP4 compatibility
  2734. case E_DEPRECATED:
  2735. return true;
  2736. break;
  2737. }
  2738. // If we notice an error not handled here we pass this back to PHP by returning false
  2739. // This may not work for all php versions
  2740. return false;
  2741. }
  2742. /**
  2743. * Removes absolute path to phpBB root directory from error messages
  2744. * and converts backslashes to forward slashes.
  2745. *
  2746. * @param string $errfile Absolute file path
  2747. * (e.g. /var/www/phpbb3/phpBB/includes/functions.php)
  2748. * Please note that if $errfile is outside of the phpBB root,
  2749. * the root path will not be found and can not be filtered.
  2750. * @return string Relative file path
  2751. * (e.g. /includes/functions.php)
  2752. */
  2753. function phpbb_filter_root_path($errfile)
  2754. {
  2755. global $phpbb_filesystem;
  2756. static $root_path;
  2757. if (empty($root_path))
  2758. {
  2759. $root_path = \phpbb\filesystem\helper::realpath(dirname(__FILE__) . '/../');
  2760. }
  2761. return str_replace(array($root_path, '\\'), array('[ROOT]', '/'), $errfile);
  2762. }
  2763. /**
  2764. * Queries the session table to get information about online guests
  2765. * @param int $item_id Limits the search to the item with this id
  2766. * @param string $item The name of the item which is stored in the session table as session_{$item}_id
  2767. * @return int The number of active distinct guest sessions
  2768. */
  2769. function obtain_guest_count($item_id = 0, $item = 'forum')
  2770. {
  2771. global $db, $config;
  2772. if ($item_id)
  2773. {
  2774. $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
  2775. }
  2776. else
  2777. {
  2778. $reading_sql = '';
  2779. }
  2780. $time = (time() - (intval($config['load_online_time']) * 60));
  2781. // Get number of online guests
  2782. if ($db->get_sql_layer() === 'sqlite3')
  2783. {
  2784. $sql = 'SELECT COUNT(session_ip) as num_guests
  2785. FROM (
  2786. SELECT DISTINCT s.session_ip
  2787. FROM ' . SESSIONS_TABLE . ' s
  2788. WHERE s.session_user_id = ' . ANONYMOUS . '
  2789. AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
  2790. $reading_sql .
  2791. ')';
  2792. }
  2793. else
  2794. {
  2795. $sql = 'SELECT COUNT(DISTINCT s.session_ip) as num_guests
  2796. FROM ' . SESSIONS_TABLE . ' s
  2797. WHERE s.session_user_id = ' . ANONYMOUS . '
  2798. AND s.session_time >= ' . ($time - ((int) ($time % 60))) .
  2799. $reading_sql;
  2800. }
  2801. $result = $db->sql_query($sql);
  2802. $guests_online = (int) $db->sql_fetchfield('num_guests');
  2803. $db->sql_freeresult($result);
  2804. return $guests_online;
  2805. }
  2806. /**
  2807. * Queries the session table to get information about online users
  2808. * @param int $item_id Limits the search to the item with this id
  2809. * @param string $item The name of the item which is stored in the session table as session_{$item}_id
  2810. * @return array An array containing the ids of online, hidden and visible users, as well as statistical info
  2811. */
  2812. function obtain_users_online($item_id = 0, $item = 'forum')
  2813. {
  2814. global $db, $config;
  2815. $reading_sql = '';
  2816. if ($item_id !== 0)
  2817. {
  2818. $reading_sql = ' AND s.session_' . $item . '_id = ' . (int) $item_id;
  2819. }
  2820. $online_users = array(
  2821. 'online_users' => array(),
  2822. 'hidden_users' => array(),
  2823. 'total_online' => 0,
  2824. 'visible_online' => 0,
  2825. 'hidden_online' => 0,
  2826. 'guests_online' => 0,
  2827. );
  2828. if ($config['load_online_guests'])
  2829. {
  2830. $online_users['guests_online'] = obtain_guest_count($item_id, $item);
  2831. }
  2832. // a little discrete magic to cache this for 30 seconds
  2833. $time = (time() - (intval($config['load_online_time']) * 60));
  2834. $sql = 'SELECT s.session_user_id, s.session_ip, s.session_viewonline
  2835. FROM ' . SESSIONS_TABLE . ' s
  2836. WHERE s.session_time >= ' . ($time - ((int) ($time % 30))) .
  2837. $reading_sql .
  2838. ' AND s.session_user_id <> ' . ANONYMOUS;
  2839. $result = $db->sql_query($sql);
  2840. while ($row = $db->sql_fetchrow($result))
  2841. {
  2842. // Skip multiple sessions for one user
  2843. if (!isset($online_users['online_users'][$row['session_user_id']]))
  2844. {
  2845. $online_users['online_users'][$row['session_user_id']] = (int) $row['session_user_id'];
  2846. if ($row['session_viewonline'])
  2847. {
  2848. $online_users['visible_online']++;
  2849. }
  2850. else
  2851. {
  2852. $online_users['hidden_users'][$row['session_user_id']] = (int) $row['session_user_id'];
  2853. $online_users['hidden_online']++;
  2854. }
  2855. }
  2856. }
  2857. $online_users['total_online'] = $online_users['guests_online'] + $online_users['visible_online'] + $online_users['hidden_online'];
  2858. $db->sql_freeresult($result);
  2859. return $online_users;
  2860. }
  2861. /**
  2862. * Uses the result of obtain_users_online to generate a localized, readable representation.
  2863. * @param mixed $online_users result of obtain_users_online - array with user_id lists for total, hidden and visible users, and statistics
  2864. * @param int $item_id Indicate that the data is limited to one item and not global
  2865. * @param string $item The name of the item which is stored in the session table as session_{$item}_id
  2866. * @return array An array containing the string for output to the template
  2867. */
  2868. function obtain_users_online_string($online_users, $item_id = 0, $item = 'forum')
  2869. {
  2870. global $config, $db, $user, $auth, $phpbb_dispatcher;
  2871. $user_online_link = $rowset = array();
  2872. // Need caps version of $item for language-strings
  2873. $item_caps = strtoupper($item);
  2874. if (count($online_users['online_users']))
  2875. {
  2876. $sql_ary = array(
  2877. 'SELECT' => 'u.username, u.username_clean, u.user_id, u.user_type, u.user_allow_viewonline, u.user_colour',
  2878. 'FROM' => array(
  2879. USERS_TABLE => 'u',
  2880. ),
  2881. 'WHERE' => $db->sql_in_set('u.user_id', $online_users['online_users']),
  2882. 'ORDER_BY' => 'u.username_clean ASC',
  2883. );
  2884. /**
  2885. * Modify SQL query to obtain online users data
  2886. *
  2887. * @event core.obtain_users_online_string_sql
  2888. * @var array online_users Array with online users data
  2889. * from obtain_users_online()
  2890. * @var int item_id Restrict online users to item id
  2891. * @var string item Restrict online users to a certain
  2892. * session item, e.g. forum for
  2893. * session_forum_id
  2894. * @var array sql_ary SQL query array to obtain users online data
  2895. * @since 3.1.4-RC1
  2896. * @changed 3.1.7-RC1 Change sql query into array and adjust var accordingly. Allows extension authors the ability to adjust the sql_ary.
  2897. */
  2898. $vars = array('online_users', 'item_id', 'item', 'sql_ary');
  2899. extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_sql', compact($vars)));
  2900. $result = $db->sql_query($db->sql_build_query('SELECT', $sql_ary));
  2901. $rowset = $db->sql_fetchrowset($result);
  2902. $db->sql_freeresult($result);
  2903. foreach ($rowset as $row)
  2904. {
  2905. // User is logged in and therefore not a guest
  2906. if ($row['user_id'] != ANONYMOUS)
  2907. {
  2908. if (isset($online_users['hidden_users'][$row['user_id']]))
  2909. {
  2910. $row['username'] = '<em>' . $row['username'] . '</em>';
  2911. }
  2912. if (!isset($online_users['hidden_users'][$row['user_id']]) || $auth->acl_get('u_viewonline') || $row['user_id'] === $user->data['user_id'])
  2913. {
  2914. $user_online_link[$row['user_id']] = get_username_string(($row['user_type'] <> USER_IGNORE) ? 'full' : 'no_profile', $row['user_id'], $row['username'], $row['user_colour']);
  2915. }
  2916. }
  2917. }
  2918. }
  2919. /**
  2920. * Modify online userlist data
  2921. *
  2922. * @event core.obtain_users_online_string_before_modify
  2923. * @var array online_users Array with online users data
  2924. * from obtain_users_online()
  2925. * @var int item_id Restrict online users to item id
  2926. * @var string item Restrict online users to a certain
  2927. * session item, e.g. forum for
  2928. * session_forum_id
  2929. * @var array rowset Array with online users data
  2930. * @var array user_online_link Array with online users items (usernames)
  2931. * @since 3.1.10-RC1
  2932. */
  2933. $vars = array(
  2934. 'online_users',
  2935. 'item_id',
  2936. 'item',
  2937. 'rowset',
  2938. 'user_online_link',
  2939. );
  2940. extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_before_modify', compact($vars)));
  2941. $online_userlist = implode(', ', $user_online_link);
  2942. if (!$online_userlist)
  2943. {
  2944. $online_userlist = $user->lang['NO_ONLINE_USERS'];
  2945. }
  2946. if ($item_id === 0)
  2947. {
  2948. $online_userlist = $user->lang['REGISTERED_USERS'] . ' ' . $online_userlist;
  2949. }
  2950. else if ($config['load_online_guests'])
  2951. {
  2952. $online_userlist = $user->lang('BROWSING_' . $item_caps . '_GUESTS', $online_users['guests_online'], $online_userlist);
  2953. }
  2954. else
  2955. {
  2956. $online_userlist = sprintf($user->lang['BROWSING_' . $item_caps], $online_userlist);
  2957. }
  2958. // Build online listing
  2959. $visible_online = $user->lang('REG_USERS_TOTAL', (int) $online_users['visible_online']);
  2960. $hidden_online = $user->lang('HIDDEN_USERS_TOTAL', (int) $online_users['hidden_online']);
  2961. if ($config['load_online_guests'])
  2962. {
  2963. $guests_online = $user->lang('GUEST_USERS_TOTAL', (int) $online_users['guests_online']);
  2964. $l_online_users = $user->lang('ONLINE_USERS_TOTAL_GUESTS', (int) $online_users['total_online'], $visible_online, $hidden_online, $guests_online);
  2965. }
  2966. else
  2967. {
  2968. $l_online_users = $user->lang('ONLINE_USERS_TOTAL', (int) $online_users['total_online'], $visible_online, $hidden_online);
  2969. }
  2970. /**
  2971. * Modify online userlist data
  2972. *
  2973. * @event core.obtain_users_online_string_modify
  2974. * @var array online_users Array with online users data
  2975. * from obtain_users_online()
  2976. * @var int item_id Restrict online users to item id
  2977. * @var string item Restrict online users to a certain
  2978. * session item, e.g. forum for
  2979. * session_forum_id
  2980. * @var array rowset Array with online users data
  2981. * @var array user_online_link Array with online users items (usernames)
  2982. * @var string online_userlist String containing users online list
  2983. * @var string l_online_users String with total online users count info
  2984. * @since 3.1.4-RC1
  2985. */
  2986. $vars = array(
  2987. 'online_users',
  2988. 'item_id',
  2989. 'item',
  2990. 'rowset',
  2991. 'user_online_link',
  2992. 'online_userlist',
  2993. 'l_online_users',
  2994. );
  2995. extract($phpbb_dispatcher->trigger_event('core.obtain_users_online_string_modify', compact($vars)));
  2996. return array(
  2997. 'online_userlist' => $online_userlist,
  2998. 'l_online_users' => $l_online_users,
  2999. );
  3000. }
  3001. /**
  3002. * Get option bitfield from custom data
  3003. *
  3004. * @param int $bit The bit/value to get
  3005. * @param int $data Current bitfield to check
  3006. * @return bool Returns true if value of constant is set in bitfield, else false
  3007. */
  3008. function phpbb_optionget($bit, $data)
  3009. {
  3010. return ($data & 1 << (int) $bit) ? true : false;
  3011. }
  3012. /**
  3013. * Set option bitfield
  3014. *
  3015. * @param int $bit The bit/value to set/unset
  3016. * @param bool $set True if option should be set, false if option should be unset.
  3017. * @param int $data Current bitfield to change
  3018. *
  3019. * @return int The new bitfield
  3020. */
  3021. function phpbb_optionset($bit, $set, $data)
  3022. {
  3023. if ($set && !($data & 1 << $bit))
  3024. {
  3025. $data += 1 << $bit;
  3026. }
  3027. else if (!$set && ($data & 1 << $bit))
  3028. {
  3029. $data -= 1 << $bit;
  3030. }
  3031. return $data;
  3032. }
  3033. /**
  3034. * Escapes and quotes a string for use as an HTML/XML attribute value.
  3035. *
  3036. * This is a port of Python xml.sax.saxutils quoteattr.
  3037. *
  3038. * The function will attempt to choose a quote character in such a way as to
  3039. * avoid escaping quotes in the string. If this is not possible the string will
  3040. * be wrapped in double quotes and double quotes will be escaped.
  3041. *
  3042. * @param string $data The string to be escaped
  3043. * @param array $entities Associative array of additional entities to be escaped
  3044. * @return string Escaped and quoted string
  3045. */
  3046. function phpbb_quoteattr($data, $entities = null)
  3047. {
  3048. $data = str_replace('&', '&amp;', $data);
  3049. $data = str_replace('>', '&gt;', $data);
  3050. $data = str_replace('<', '&lt;', $data);
  3051. $data = str_replace("\n", '&#10;', $data);
  3052. $data = str_replace("\r", '&#13;', $data);
  3053. $data = str_replace("\t", '&#9;', $data);
  3054. if (!empty($entities))
  3055. {
  3056. $data = str_replace(array_keys($entities), array_values($entities), $data);
  3057. }
  3058. if (strpos($data, '"') !== false)
  3059. {
  3060. if (strpos($data, "'") !== false)
  3061. {
  3062. $data = '"' . str_replace('"', '&quot;', $data) . '"';
  3063. }
  3064. else
  3065. {
  3066. $data = "'" . $data . "'";
  3067. }
  3068. }
  3069. else
  3070. {
  3071. $data = '"' . $data . '"';
  3072. }
  3073. return $data;
  3074. }
  3075. /**
  3076. * Get user avatar
  3077. *
  3078. * @param array $user_row Row from the users table
  3079. * @param string $alt Optional language string for alt tag within image, can be a language key or text
  3080. * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
  3081. * @param bool $lazy If true, will be lazy loaded (requires JS)
  3082. *
  3083. * @return string Avatar html
  3084. */
  3085. function phpbb_get_user_avatar($user_row, $alt = 'USER_AVATAR', $ignore_config = false, $lazy = false)
  3086. {
  3087. $row = \phpbb\avatar\manager::clean_row($user_row, 'user');
  3088. return phpbb_get_avatar($row, $alt, $ignore_config, $lazy);
  3089. }
  3090. /**
  3091. * Get group avatar
  3092. *
  3093. * @param array $group_row Row from the groups table
  3094. * @param string $alt Optional language string for alt tag within image, can be a language key or text
  3095. * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
  3096. * @param bool $lazy If true, will be lazy loaded (requires JS)
  3097. *
  3098. * @return string Avatar html
  3099. */
  3100. function phpbb_get_group_avatar($group_row, $alt = 'GROUP_AVATAR', $ignore_config = false, $lazy = false)
  3101. {
  3102. $row = \phpbb\avatar\manager::clean_row($group_row, 'group');
  3103. return phpbb_get_avatar($row, $alt, $ignore_config, $lazy);
  3104. }
  3105. /**
  3106. * Get avatar
  3107. *
  3108. * @param array $row Row cleaned by \phpbb\avatar\manager::clean_row
  3109. * @param string $alt Optional language string for alt tag within image, can be a language key or text
  3110. * @param bool $ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
  3111. * @param bool $lazy If true, will be lazy loaded (requires JS)
  3112. *
  3113. * @return string Avatar html
  3114. */
  3115. function phpbb_get_avatar($row, $alt, $ignore_config = false, $lazy = false)
  3116. {
  3117. global $user, $config;
  3118. global $phpbb_container, $phpbb_dispatcher;
  3119. if (!$config['allow_avatar'] && !$ignore_config)
  3120. {
  3121. return '';
  3122. }
  3123. $avatar_data = array(
  3124. 'src' => $row['avatar'],
  3125. 'width' => $row['avatar_width'],
  3126. 'height' => $row['avatar_height'],
  3127. );
  3128. /* @var $phpbb_avatar_manager \phpbb\avatar\manager */
  3129. $phpbb_avatar_manager = $phpbb_container->get('avatar.manager');
  3130. $driver = $phpbb_avatar_manager->get_driver($row['avatar_type'], !$ignore_config);
  3131. $html = '';
  3132. if ($driver)
  3133. {
  3134. $html = $driver->get_custom_html($user, $row, $alt);
  3135. $avatar_data = $driver->get_data($row);
  3136. }
  3137. else
  3138. {
  3139. $avatar_data['src'] = '';
  3140. }
  3141. if (empty($html) && !empty($avatar_data['src']))
  3142. {
  3143. if ($lazy)
  3144. {
  3145. // Determine board url - we may need it later
  3146. $board_url = generate_board_url() . '/';
  3147. // This path is sent with the base template paths in the assign_vars()
  3148. // call below. We need to correct it in case we are accessing from a
  3149. // controller because the web paths will be incorrect otherwise.
  3150. $phpbb_path_helper = $phpbb_container->get('path_helper');
  3151. $corrected_path = $phpbb_path_helper->get_web_root_path();
  3152. $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path;
  3153. $theme = "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme';
  3154. $src = 'src="' . $theme . '/images/no_avatar.gif" data-src="' . $avatar_data['src'] . '"';
  3155. }
  3156. else
  3157. {
  3158. $src = 'src="' . $avatar_data['src'] . '"';
  3159. }
  3160. $html = '<img class="avatar" ' . $src . ' ' .
  3161. ($avatar_data['width'] ? ('width="' . $avatar_data['width'] . '" ') : '') .
  3162. ($avatar_data['height'] ? ('height="' . $avatar_data['height'] . '" ') : '') .
  3163. 'alt="' . ((!empty($user->lang[$alt])) ? $user->lang[$alt] : $alt) . '" />';
  3164. }
  3165. /**
  3166. * Event to modify HTML <img> tag of avatar
  3167. *
  3168. * @event core.get_avatar_after
  3169. * @var array row Row cleaned by \phpbb\avatar\manager::clean_row
  3170. * @var string alt Optional language string for alt tag within image, can be a language key or text
  3171. * @var bool ignore_config Ignores the config-setting, to be still able to view the avatar in the UCP
  3172. * @var array avatar_data The HTML attributes for avatar <img> tag
  3173. * @var string html The HTML <img> tag of generated avatar
  3174. * @since 3.1.6-RC1
  3175. */
  3176. $vars = array('row', 'alt', 'ignore_config', 'avatar_data', 'html');
  3177. extract($phpbb_dispatcher->trigger_event('core.get_avatar_after', compact($vars)));
  3178. return $html;
  3179. }
  3180. /**
  3181. * Generate page header
  3182. */
  3183. function page_header($page_title = '', $display_online_list = false, $item_id = 0, $item = 'forum', $send_headers = true)
  3184. {
  3185. global $db, $config, $template, $SID, $_SID, $_EXTRA_URL, $user, $auth, $phpEx, $phpbb_root_path;
  3186. global $phpbb_dispatcher, $request, $phpbb_container, $phpbb_admin_path;
  3187. if (defined('HEADER_INC'))
  3188. {
  3189. return;
  3190. }
  3191. define('HEADER_INC', true);
  3192. // A listener can set this variable to `true` when it overrides this function
  3193. $page_header_override = false;
  3194. /**
  3195. * Execute code and/or overwrite page_header()
  3196. *
  3197. * @event core.page_header
  3198. * @var string page_title Page title
  3199. * @var bool display_online_list Do we display online users list
  3200. * @var string item Restrict online users to a certain
  3201. * session item, e.g. forum for
  3202. * session_forum_id
  3203. * @var int item_id Restrict online users to item id
  3204. * @var bool page_header_override Shall we return instead of running
  3205. * the rest of page_header()
  3206. * @since 3.1.0-a1
  3207. */
  3208. $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'page_header_override');
  3209. extract($phpbb_dispatcher->trigger_event('core.page_header', compact($vars)));
  3210. if ($page_header_override)
  3211. {
  3212. return;
  3213. }
  3214. // gzip_compression
  3215. if ($config['gzip_compress'])
  3216. {
  3217. // to avoid partially compressed output resulting in blank pages in
  3218. // the browser or error messages, compression is disabled in a few cases:
  3219. //
  3220. // 1) if headers have already been sent, this indicates plaintext output
  3221. // has been started so further content must not be compressed
  3222. // 2) the length of the current output buffer is non-zero. This means
  3223. // there is already some uncompressed content in this output buffer
  3224. // so further output must not be compressed
  3225. // 3) if more than one level of output buffering is used because we
  3226. // cannot test all output buffer level content lengths. One level
  3227. // could be caused by php.ini output_buffering. Anything
  3228. // beyond that is manual, so the code wrapping phpBB in output buffering
  3229. // can easily compress the output itself.
  3230. //
  3231. if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)
  3232. {
  3233. ob_start('ob_gzhandler');
  3234. }
  3235. }
  3236. $user->update_session_infos();
  3237. // Generate logged in/logged out status
  3238. if ($user->data['user_id'] != ANONYMOUS)
  3239. {
  3240. $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=logout', true, $user->session_id);
  3241. $l_login_logout = $user->lang['LOGOUT'];
  3242. }
  3243. else
  3244. {
  3245. $redirect = $request->variable('redirect', rawurlencode($user->page['page']));
  3246. $u_login_logout = append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login&amp;redirect=' . $redirect);
  3247. $l_login_logout = $user->lang['LOGIN'];
  3248. }
  3249. // Last visit date/time
  3250. $s_last_visit = ($user->data['user_id'] != ANONYMOUS) ? $user->format_date($user->data['session_last_visit']) : '';
  3251. // Get users online list ... if required
  3252. $l_online_users = $online_userlist = $l_online_record = $l_online_time = '';
  3253. if ($config['load_online'] && $config['load_online_time'] && $display_online_list)
  3254. {
  3255. /**
  3256. * Load online data:
  3257. * For obtaining another session column use $item and $item_id in the function-parameter, whereby the column is session_{$item}_id.
  3258. */
  3259. $item_id = max($item_id, 0);
  3260. $online_users = obtain_users_online($item_id, $item);
  3261. $user_online_strings = obtain_users_online_string($online_users, $item_id, $item);
  3262. $l_online_users = $user_online_strings['l_online_users'];
  3263. $online_userlist = $user_online_strings['online_userlist'];
  3264. $total_online_users = $online_users['total_online'];
  3265. if ($total_online_users > $config['record_online_users'])
  3266. {
  3267. $config->set('record_online_users', $total_online_users, false);
  3268. $config->set('record_online_date', time(), false);
  3269. }
  3270. $l_online_record = $user->lang('RECORD_ONLINE_USERS', (int) $config['record_online_users'], $user->format_date($config['record_online_date'], false, true));
  3271. $l_online_time = $user->lang('VIEW_ONLINE_TIMES', (int) $config['load_online_time']);
  3272. }
  3273. $s_privmsg_new = false;
  3274. // Check for new private messages if user is logged in
  3275. if (!empty($user->data['is_registered']))
  3276. {
  3277. if ($user->data['user_new_privmsg'])
  3278. {
  3279. if (!$user->data['user_last_privmsg'] || $user->data['user_last_privmsg'] > $user->data['session_last_visit'])
  3280. {
  3281. $sql = 'UPDATE ' . USERS_TABLE . '
  3282. SET user_last_privmsg = ' . $user->data['session_last_visit'] . '
  3283. WHERE user_id = ' . $user->data['user_id'];
  3284. $db->sql_query($sql);
  3285. $s_privmsg_new = true;
  3286. }
  3287. else
  3288. {
  3289. $s_privmsg_new = false;
  3290. }
  3291. }
  3292. else
  3293. {
  3294. $s_privmsg_new = false;
  3295. }
  3296. }
  3297. $forum_id = $request->variable('f', 0);
  3298. $topic_id = $request->variable('t', 0);
  3299. $s_feed_news = false;
  3300. // Get option for news
  3301. if ($config['feed_enable'])
  3302. {
  3303. $sql = 'SELECT forum_id
  3304. FROM ' . FORUMS_TABLE . '
  3305. WHERE ' . $db->sql_bit_and('forum_options', FORUM_OPTION_FEED_NEWS, '<> 0');
  3306. $result = $db->sql_query_limit($sql, 1, 0, 600);
  3307. $s_feed_news = (int) $db->sql_fetchfield('forum_id');
  3308. $db->sql_freeresult($result);
  3309. }
  3310. // Determine board url - we may need it later
  3311. $board_url = generate_board_url() . '/';
  3312. // This path is sent with the base template paths in the assign_vars()
  3313. // call below. We need to correct it in case we are accessing from a
  3314. // controller because the web paths will be incorrect otherwise.
  3315. /* @var $phpbb_path_helper \phpbb\path_helper */
  3316. $phpbb_path_helper = $phpbb_container->get('path_helper');
  3317. $corrected_path = $phpbb_path_helper->get_web_root_path();
  3318. $web_path = (defined('PHPBB_USE_BOARD_URL_PATH') && PHPBB_USE_BOARD_URL_PATH) ? $board_url : $corrected_path;
  3319. // Send a proper content-language to the output
  3320. $user_lang = $user->lang['USER_LANG'];
  3321. if (strpos($user_lang, '-x-') !== false)
  3322. {
  3323. $user_lang = substr($user_lang, 0, strpos($user_lang, '-x-'));
  3324. }
  3325. $s_search_hidden_fields = array();
  3326. if ($_SID)
  3327. {
  3328. $s_search_hidden_fields['sid'] = $_SID;
  3329. }
  3330. if (!empty($_EXTRA_URL))
  3331. {
  3332. foreach ($_EXTRA_URL as $url_param)
  3333. {
  3334. $url_param = explode('=', $url_param, 2);
  3335. $s_search_hidden_fields[$url_param[0]] = $url_param[1];
  3336. }
  3337. }
  3338. $dt = $user->create_datetime();
  3339. $timezone_offset = $user->lang(array('timezones', 'UTC_OFFSET'), phpbb_format_timezone_offset($dt->getOffset()));
  3340. $timezone_name = $user->timezone->getName();
  3341. if (isset($user->lang['timezones'][$timezone_name]))
  3342. {
  3343. $timezone_name = $user->lang['timezones'][$timezone_name];
  3344. }
  3345. // Output the notifications
  3346. $notifications = false;
  3347. if ($config['load_notifications'] && $config['allow_board_notifications'] && $user->data['user_id'] != ANONYMOUS && $user->data['user_type'] != USER_IGNORE)
  3348. {
  3349. /* @var $phpbb_notifications \phpbb\notification\manager */
  3350. $phpbb_notifications = $phpbb_container->get('notification_manager');
  3351. $notifications = $phpbb_notifications->load_notifications('notification.method.board', array(
  3352. 'all_unread' => true,
  3353. 'limit' => 5,
  3354. ));
  3355. foreach ($notifications['notifications'] as $notification)
  3356. {
  3357. $template->assign_block_vars('notifications', $notification->prepare_for_display());
  3358. }
  3359. }
  3360. /** @var \phpbb\controller\helper $controller_helper */
  3361. $controller_helper = $phpbb_container->get('controller.helper');
  3362. $notification_mark_hash = generate_link_hash('mark_all_notifications_read');
  3363. $phpbb_version_parts = explode('.', PHPBB_VERSION, 3);
  3364. $phpbb_major = $phpbb_version_parts[0] . '.' . $phpbb_version_parts[1];
  3365. $s_login_redirect = build_hidden_fields(array('redirect' => $phpbb_path_helper->remove_web_root_path(build_url())));
  3366. // Add form token for login box, in case page is presenting a login form.
  3367. add_form_key('login', '_LOGIN');
  3368. // The following assigns all _common_ variables that may be used at any point in a template.
  3369. $template->assign_vars(array(
  3370. 'SITENAME' => $config['sitename'],
  3371. 'SITE_DESCRIPTION' => $config['site_desc'],
  3372. 'PAGE_TITLE' => $page_title,
  3373. 'SCRIPT_NAME' => str_replace('.' . $phpEx, '', $user->page['page_name']),
  3374. 'LAST_VISIT_DATE' => sprintf($user->lang['YOU_LAST_VISIT'], $s_last_visit),
  3375. 'LAST_VISIT_YOU' => $s_last_visit,
  3376. 'CURRENT_TIME' => sprintf($user->lang['CURRENT_TIME'], $user->format_date(time(), false, true)),
  3377. 'TOTAL_USERS_ONLINE' => $l_online_users,
  3378. 'LOGGED_IN_USER_LIST' => $online_userlist,
  3379. 'RECORD_USERS' => $l_online_record,
  3380. 'PRIVATE_MESSAGE_COUNT' => (!empty($user->data['user_unread_privmsg'])) ? $user->data['user_unread_privmsg'] : 0,
  3381. 'CURRENT_USER_AVATAR' => phpbb_get_user_avatar($user->data),
  3382. 'CURRENT_USERNAME_SIMPLE' => get_username_string('no_profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
  3383. 'CURRENT_USERNAME_FULL' => get_username_string('full', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
  3384. 'UNREAD_NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '',
  3385. 'NOTIFICATIONS_COUNT' => ($notifications !== false) ? $notifications['unread_count'] : '',
  3386. 'U_VIEW_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications'),
  3387. 'U_MARK_ALL_NOTIFICATIONS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&amp;mode=notification_list&amp;mark=all&amp;token=' . $notification_mark_hash),
  3388. 'U_NOTIFICATION_SETTINGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=ucp_notifications&amp;mode=notification_options'),
  3389. 'S_NOTIFICATIONS_DISPLAY' => $config['load_notifications'] && $config['allow_board_notifications'],
  3390. 'S_USER_NEW_PRIVMSG' => $user->data['user_new_privmsg'],
  3391. 'S_USER_UNREAD_PRIVMSG' => $user->data['user_unread_privmsg'],
  3392. 'S_USER_NEW' => $user->data['user_new'],
  3393. 'SID' => $SID,
  3394. '_SID' => $_SID,
  3395. 'SESSION_ID' => $user->session_id,
  3396. 'ROOT_PATH' => $web_path,
  3397. 'BOARD_URL' => $board_url,
  3398. 'PHPBB_VERSION' => PHPBB_VERSION,
  3399. 'PHPBB_MAJOR' => $phpbb_major,
  3400. 'L_LOGIN_LOGOUT' => $l_login_logout,
  3401. 'L_INDEX' => ($config['board_index_text'] !== '') ? $config['board_index_text'] : $user->lang['FORUM_INDEX'],
  3402. 'L_SITE_HOME' => ($config['site_home_text'] !== '') ? $config['site_home_text'] : $user->lang['HOME'],
  3403. 'L_ONLINE_EXPLAIN' => $l_online_time,
  3404. 'U_PRIVATEMSGS' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
  3405. 'U_RETURN_INBOX' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'i=pm&amp;folder=inbox'),
  3406. 'U_MEMBERLIST' => append_sid("{$phpbb_root_path}memberlist.$phpEx"),
  3407. 'U_VIEWONLINE' => ($auth->acl_gets('u_viewprofile', 'a_user', 'a_useradd', 'a_userdel')) ? append_sid("{$phpbb_root_path}viewonline.$phpEx") : '',
  3408. 'U_LOGIN_LOGOUT' => $u_login_logout,
  3409. 'U_INDEX' => append_sid("{$phpbb_root_path}index.$phpEx"),
  3410. 'U_SEARCH' => append_sid("{$phpbb_root_path}search.$phpEx"),
  3411. 'U_SITE_HOME' => $config['site_home_url'],
  3412. 'U_REGISTER' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=register'),
  3413. 'U_PROFILE' => append_sid("{$phpbb_root_path}ucp.$phpEx"),
  3414. 'U_USER_PROFILE' => get_username_string('profile', $user->data['user_id'], $user->data['username'], $user->data['user_colour']),
  3415. 'U_MODCP' => append_sid("{$phpbb_root_path}mcp.$phpEx", false, true, $user->session_id),
  3416. 'U_FAQ' => $controller_helper->route('phpbb_help_faq_controller'),
  3417. 'U_SEARCH_SELF' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=egosearch'),
  3418. 'U_SEARCH_NEW' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=newposts'),
  3419. 'U_SEARCH_UNANSWERED' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unanswered'),
  3420. 'U_SEARCH_UNREAD' => append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=unreadposts'),
  3421. 'U_SEARCH_ACTIVE_TOPICS'=> append_sid("{$phpbb_root_path}search.$phpEx", 'search_id=active_topics'),
  3422. 'U_DELETE_COOKIES' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=delete_cookies'),
  3423. 'U_CONTACT_US' => ($config['contact_admin_form_enable'] && $config['email_enable']) ? append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin') : '',
  3424. 'U_TEAM' => (!$auth->acl_get('u_viewprofile')) ? '' : append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=team'),
  3425. 'U_TERMS_USE' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=terms'),
  3426. 'U_PRIVACY' => append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy'),
  3427. 'UA_PRIVACY' => addslashes(append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=privacy')),
  3428. 'U_RESTORE_PERMISSIONS' => ($user->data['user_perm_from'] && $auth->acl_get('a_switchperm')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=restore_perm') : '',
  3429. 'U_FEED' => $controller_helper->route('phpbb_feed_index'),
  3430. 'S_USER_LOGGED_IN' => ($user->data['user_id'] != ANONYMOUS) ? true : false,
  3431. 'S_AUTOLOGIN_ENABLED' => ($config['allow_autologin']) ? true : false,
  3432. 'S_BOARD_DISABLED' => ($config['board_disable']) ? true : false,
  3433. 'S_REGISTERED_USER' => (!empty($user->data['is_registered'])) ? true : false,
  3434. 'S_IS_BOT' => (!empty($user->data['is_bot'])) ? true : false,
  3435. 'S_USER_LANG' => $user_lang,
  3436. 'S_USER_BROWSER' => (isset($user->data['session_browser'])) ? $user->data['session_browser'] : $user->lang['UNKNOWN_BROWSER'],
  3437. 'S_USERNAME' => $user->data['username'],
  3438. 'S_CONTENT_DIRECTION' => $user->lang['DIRECTION'],
  3439. 'S_CONTENT_FLOW_BEGIN' => ($user->lang['DIRECTION'] == 'ltr') ? 'left' : 'right',
  3440. 'S_CONTENT_FLOW_END' => ($user->lang['DIRECTION'] == 'ltr') ? 'right' : 'left',
  3441. 'S_CONTENT_ENCODING' => 'UTF-8',
  3442. 'S_TIMEZONE' => sprintf($user->lang['ALL_TIMES'], $timezone_offset, $timezone_name),
  3443. 'S_DISPLAY_ONLINE_LIST' => ($l_online_time) ? 1 : 0,
  3444. 'S_DISPLAY_SEARCH' => (!$config['load_search']) ? 0 : (isset($auth) ? ($auth->acl_get('u_search') && $auth->acl_getf_global('f_search')) : 1),
  3445. 'S_DISPLAY_PM' => ($config['allow_privmsg'] && !empty($user->data['is_registered']) && ($auth->acl_get('u_readpm') || $auth->acl_get('u_sendpm'))) ? true : false,
  3446. 'S_DISPLAY_MEMBERLIST' => (isset($auth)) ? $auth->acl_get('u_viewprofile') : 0,
  3447. 'S_NEW_PM' => ($s_privmsg_new) ? 1 : 0,
  3448. 'S_REGISTER_ENABLED' => ($config['require_activation'] != USER_ACTIVATION_DISABLE) ? true : false,
  3449. 'S_FORUM_ID' => $forum_id,
  3450. 'S_TOPIC_ID' => $topic_id,
  3451. 'S_LOGIN_ACTION' => ((!defined('ADMIN_START')) ? append_sid("{$phpbb_root_path}ucp.$phpEx", 'mode=login') : append_sid("{$phpbb_admin_path}index.$phpEx", false, true, $user->session_id)),
  3452. 'S_LOGIN_REDIRECT' => $s_login_redirect,
  3453. 'S_ENABLE_FEEDS' => ($config['feed_enable']) ? true : false,
  3454. 'S_ENABLE_FEEDS_OVERALL' => ($config['feed_overall']) ? true : false,
  3455. 'S_ENABLE_FEEDS_FORUMS' => ($config['feed_overall_forums']) ? true : false,
  3456. 'S_ENABLE_FEEDS_TOPICS' => ($config['feed_topics_new']) ? true : false,
  3457. 'S_ENABLE_FEEDS_TOPICS_ACTIVE' => ($config['feed_topics_active']) ? true : false,
  3458. 'S_ENABLE_FEEDS_NEWS' => ($s_feed_news) ? true : false,
  3459. 'S_LOAD_UNREADS' => ($config['load_unreads_search'] && ($config['load_anon_lastread'] || $user->data['is_registered'])) ? true : false,
  3460. 'S_SEARCH_HIDDEN_FIELDS' => build_hidden_fields($s_search_hidden_fields),
  3461. 'T_ASSETS_VERSION' => $config['assets_version'],
  3462. 'T_ASSETS_PATH' => "{$web_path}assets",
  3463. 'T_THEME_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme',
  3464. 'T_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',
  3465. 'T_SUPER_TEMPLATE_PATH' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/template',
  3466. 'T_IMAGES_PATH' => "{$web_path}images/",
  3467. 'T_SMILIES_PATH' => "{$web_path}{$config['smilies_path']}/",
  3468. 'T_AVATAR_GALLERY_PATH' => "{$web_path}{$config['avatar_gallery_path']}/",
  3469. 'T_ICONS_PATH' => "{$web_path}{$config['icons_path']}/",
  3470. 'T_RANKS_PATH' => "{$web_path}{$config['ranks_path']}/",
  3471. 'T_STYLESHEET_LINK' => "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/stylesheet.css?assets_version=' . $config['assets_version'],
  3472. 'T_STYLESHEET_LANG_LINK'=> "{$web_path}styles/" . rawurlencode($user->style['style_path']) . '/theme/' . $user->lang_name . '/stylesheet.css?assets_version=' . $config['assets_version'],
  3473. 'T_FONT_AWESOME_LINK' => !empty($config['allow_cdn']) && !empty($config['load_font_awesome_url']) ? $config['load_font_awesome_url'] : "{$web_path}assets/css/font-awesome.min.css?assets_version=" . $config['assets_version'],
  3474. 'T_FONT_AWESOME_V4SHIMS_LINK' => "{$web_path}assets/css/phpbb-fontawesome-v4-shims.min.css?assets_version=" . $config['assets_version'],
  3475. 'T_JQUERY_LINK' => !empty($config['allow_cdn']) && !empty($config['load_jquery_url']) ? $config['load_jquery_url'] : "{$web_path}assets/javascript/jquery-3.4.1.min.js?assets_version=" . $config['assets_version'],
  3476. 'S_ALLOW_CDN' => !empty($config['allow_cdn']),
  3477. 'S_COOKIE_NOTICE' => !empty($config['cookie_notice']),
  3478. 'T_THEME_NAME' => rawurlencode($user->style['style_path']),
  3479. 'T_THEME_LANG_NAME' => $user->lang_name,
  3480. 'T_TEMPLATE_NAME' => $user->style['style_path'],
  3481. 'T_SUPER_TEMPLATE_NAME' => rawurlencode((isset($user->style['style_parent_tree']) && $user->style['style_parent_tree']) ? $user->style['style_parent_tree'] : $user->style['style_path']),
  3482. 'T_IMAGES' => 'images',
  3483. 'T_SMILIES' => $config['smilies_path'],
  3484. 'T_AVATAR_GALLERY' => $config['avatar_gallery_path'],
  3485. 'T_ICONS' => $config['icons_path'],
  3486. 'T_RANKS' => $config['ranks_path'],
  3487. 'SITE_LOGO_IMG' => $user->img('site_logo'),
  3488. ));
  3489. $http_headers = array();
  3490. if ($send_headers)
  3491. {
  3492. // An array of http headers that phpBB will set. The following event may override these.
  3493. $http_headers += array(
  3494. // application/xhtml+xml not used because of IE
  3495. 'Content-type' => 'text/html; charset=UTF-8',
  3496. 'Cache-Control' => 'private, no-cache="set-cookie"',
  3497. 'Expires' => gmdate('D, d M Y H:i:s', time()) . ' GMT',
  3498. 'Referrer-Policy' => 'strict-origin-when-cross-origin',
  3499. );
  3500. if (!empty($user->data['is_bot']))
  3501. {
  3502. // Let reverse proxies know we detected a bot.
  3503. $http_headers['X-PHPBB-IS-BOT'] = 'yes';
  3504. }
  3505. }
  3506. /**
  3507. * Execute code and/or overwrite _common_ template variables after they have been assigned.
  3508. *
  3509. * @event core.page_header_after
  3510. * @var string page_title Page title
  3511. * @var bool display_online_list Do we display online users list
  3512. * @var string item Restrict online users to a certain
  3513. * session item, e.g. forum for
  3514. * session_forum_id
  3515. * @var int item_id Restrict online users to item id
  3516. * @var array http_headers HTTP headers that should be set by phpbb
  3517. *
  3518. * @since 3.1.0-b3
  3519. */
  3520. $vars = array('page_title', 'display_online_list', 'item_id', 'item', 'http_headers');
  3521. extract($phpbb_dispatcher->trigger_event('core.page_header_after', compact($vars)));
  3522. foreach ($http_headers as $hname => $hval)
  3523. {
  3524. header((string) $hname . ': ' . (string) $hval);
  3525. }
  3526. return;
  3527. }
  3528. /**
  3529. * Check and display the SQL report if requested.
  3530. *
  3531. * @param \phpbb\request\request_interface $request Request object
  3532. * @param \phpbb\auth\auth $auth Auth object
  3533. * @param \phpbb\db\driver\driver_interface $db Database connection
  3534. *
  3535. * @deprecated 3.3.1 (To be removed: 4.0.0-a1); use controller helper's display_sql_report()
  3536. */
  3537. function phpbb_check_and_display_sql_report(\phpbb\request\request_interface $request, \phpbb\auth\auth $auth, \phpbb\db\driver\driver_interface $db)
  3538. {
  3539. global $phpbb_container;
  3540. /** @var \phpbb\controller\helper $controller_helper */
  3541. $controller_helper = $phpbb_container->get('controller.helper');
  3542. $controller_helper->display_sql_report();
  3543. }
  3544. /**
  3545. * Generate the debug output string
  3546. *
  3547. * @param \phpbb\db\driver\driver_interface $db Database connection
  3548. * @param \phpbb\config\config $config Config object
  3549. * @param \phpbb\auth\auth $auth Auth object
  3550. * @param \phpbb\user $user User object
  3551. * @param \phpbb\event\dispatcher_interface $phpbb_dispatcher Event dispatcher
  3552. * @return string
  3553. */
  3554. function phpbb_generate_debug_output(\phpbb\db\driver\driver_interface $db, \phpbb\config\config $config, \phpbb\auth\auth $auth, \phpbb\user $user, \phpbb\event\dispatcher_interface $phpbb_dispatcher)
  3555. {
  3556. global $phpbb_container;
  3557. $debug_info = array();
  3558. // Output page creation time
  3559. if ($phpbb_container->getParameter('debug.load_time'))
  3560. {
  3561. if (isset($GLOBALS['starttime']))
  3562. {
  3563. $totaltime = microtime(true) - $GLOBALS['starttime'];
  3564. $debug_info[] = sprintf('<span title="SQL time: %.3fs / PHP time: %.3fs">Time: %.3fs</span>', $db->get_sql_time(), ($totaltime - $db->get_sql_time()), $totaltime);
  3565. }
  3566. }
  3567. if ($phpbb_container->getParameter('debug.memory'))
  3568. {
  3569. $memory_usage = memory_get_peak_usage();
  3570. if ($memory_usage)
  3571. {
  3572. $memory_usage = get_formatted_filesize($memory_usage);
  3573. $debug_info[] = 'Peak Memory Usage: ' . $memory_usage;
  3574. }
  3575. $debug_info[] = 'GZIP: ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off');
  3576. if ($user->load)
  3577. {
  3578. $debug_info[] = 'Load: ' . $user->load;
  3579. }
  3580. }
  3581. if ($phpbb_container->getParameter('debug.sql_explain'))
  3582. {
  3583. $debug_info[] = sprintf('<span title="Cached: %d">Queries: %d</span>', $db->sql_num_queries(true), $db->sql_num_queries());
  3584. if ($auth->acl_get('a_'))
  3585. {
  3586. $debug_info[] = '<a href="' . build_url() . '&amp;explain=1">SQL Explain</a>';
  3587. }
  3588. }
  3589. /**
  3590. * Modify debug output information
  3591. *
  3592. * @event core.phpbb_generate_debug_output
  3593. * @var array debug_info Array of strings with debug information
  3594. *
  3595. * @since 3.1.0-RC3
  3596. */
  3597. $vars = array('debug_info');
  3598. extract($phpbb_dispatcher->trigger_event('core.phpbb_generate_debug_output', compact($vars)));
  3599. return implode(' | ', $debug_info);
  3600. }
  3601. /**
  3602. * Generate page footer
  3603. *
  3604. * @param bool $run_cron Whether or not to run the cron
  3605. * @param bool $display_template Whether or not to display the template
  3606. * @param bool $exit_handler Whether or not to run the exit_handler()
  3607. */
  3608. function page_footer($run_cron = true, $display_template = true, $exit_handler = true)
  3609. {
  3610. global $phpbb_dispatcher, $phpbb_container, $template;
  3611. // A listener can set this variable to `true` when it overrides this function
  3612. $page_footer_override = false;
  3613. /**
  3614. * Execute code and/or overwrite page_footer()
  3615. *
  3616. * @event core.page_footer
  3617. * @var bool run_cron Shall we run cron tasks
  3618. * @var bool page_footer_override Shall we return instead of running
  3619. * the rest of page_footer()
  3620. * @since 3.1.0-a1
  3621. */
  3622. $vars = array('run_cron', 'page_footer_override');
  3623. extract($phpbb_dispatcher->trigger_event('core.page_footer', compact($vars)));
  3624. if ($page_footer_override)
  3625. {
  3626. return;
  3627. }
  3628. /** @var \phpbb\controller\helper $controller_helper */
  3629. $controller_helper = $phpbb_container->get('controller.helper');
  3630. $controller_helper->display_footer($run_cron);
  3631. /**
  3632. * Execute code and/or modify output before displaying the template.
  3633. *
  3634. * @event core.page_footer_after
  3635. * @var bool display_template Whether or not to display the template
  3636. * @var bool exit_handler Whether or not to run the exit_handler()
  3637. *
  3638. * @since 3.1.0-RC5
  3639. */
  3640. $vars = array('display_template', 'exit_handler');
  3641. extract($phpbb_dispatcher->trigger_event('core.page_footer_after', compact($vars)));
  3642. if ($display_template)
  3643. {
  3644. $template->display('body');
  3645. }
  3646. garbage_collection();
  3647. if ($exit_handler)
  3648. {
  3649. exit_handler();
  3650. }
  3651. }
  3652. /**
  3653. * Closing the cache object and the database
  3654. * Cool function name, eh? We might want to add operations to it later
  3655. */
  3656. function garbage_collection()
  3657. {
  3658. global $cache, $db;
  3659. global $phpbb_dispatcher;
  3660. if (!empty($phpbb_dispatcher))
  3661. {
  3662. /**
  3663. * Unload some objects, to free some memory, before we finish our task
  3664. *
  3665. * @event core.garbage_collection
  3666. * @since 3.1.0-a1
  3667. */
  3668. $phpbb_dispatcher->dispatch('core.garbage_collection');
  3669. }
  3670. // Unload cache, must be done before the DB connection if closed
  3671. if (!empty($cache))
  3672. {
  3673. $cache->unload();
  3674. }
  3675. // Close our DB connection.
  3676. if (!empty($db))
  3677. {
  3678. $db->sql_close();
  3679. }
  3680. }
  3681. /**
  3682. * Handler for exit calls in phpBB.
  3683. *
  3684. * Note: This function is called after the template has been outputted.
  3685. *
  3686. * @return void
  3687. */
  3688. function exit_handler()
  3689. {
  3690. global $phpbb_dispatcher;
  3691. $exit_handler_override = false;
  3692. /**
  3693. * This event can either supplement or override the exit_handler() function
  3694. *
  3695. * To override this function, the event must set $exit_handler_override to
  3696. * true to force it to exit directly after calling this event.
  3697. *
  3698. * @event core.exit_handler
  3699. * @var bool exit_handler_override Flag to signify exit is handled elsewhere
  3700. * @since 4.0.0-a1
  3701. */
  3702. $vars = ['exit_handler_override'];
  3703. extract($phpbb_dispatcher->trigger_event('core.exit_handler', compact($vars)));
  3704. if ($exit_handler_override)
  3705. {
  3706. return;
  3707. }
  3708. // As a pre-caution... some setups display a blank page if the flush() is not there.
  3709. (ob_get_level() > 0) ? @ob_flush() : @flush();
  3710. exit;
  3711. }
  3712. /**
  3713. * Casts a numeric string $input to an appropriate numeric type (i.e. integer or float)
  3714. *
  3715. * @param string $input A numeric string.
  3716. *
  3717. * @return int|float Integer $input if $input fits integer,
  3718. * float $input otherwise.
  3719. */
  3720. function phpbb_to_numeric($input)
  3721. {
  3722. return ($input > PHP_INT_MAX) ? (float) $input : (int) $input;
  3723. }
  3724. /**
  3725. * Get the board contact details (e.g. for emails)
  3726. *
  3727. * @param \phpbb\config\config $config
  3728. * @param string $phpEx
  3729. * @return string
  3730. */
  3731. function phpbb_get_board_contact(\phpbb\config\config $config, $phpEx)
  3732. {
  3733. if ($config['contact_admin_form_enable'])
  3734. {
  3735. return generate_board_url() . '/memberlist.' . $phpEx . '?mode=contactadmin';
  3736. }
  3737. else
  3738. {
  3739. return $config['board_contact'];
  3740. }
  3741. }
  3742. /**
  3743. * Get a clickable board contact details link
  3744. *
  3745. * @param \phpbb\config\config $config
  3746. * @param string $phpbb_root_path
  3747. * @param string $phpEx
  3748. * @return string
  3749. */
  3750. function phpbb_get_board_contact_link(\phpbb\config\config $config, $phpbb_root_path, $phpEx)
  3751. {
  3752. if ($config['contact_admin_form_enable'] && $config['email_enable'])
  3753. {
  3754. return append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=contactadmin');
  3755. }
  3756. else
  3757. {
  3758. return 'mailto:' . htmlspecialchars($config['board_contact']);
  3759. }
  3760. }