PageRenderTime 69ms CodeModel.GetById 22ms 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

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

  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…

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