PageRenderTime 54ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 0ms

/includes/cache_guests_pages.php

https://gitlab.com/ppkbb3cker/ppkbb3cker2
PHP | 537 lines | 317 code | 67 blank | 153 comment | 50 complexity | 2aaa93104e3ce99e8b1a391b579ddb67 MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * @package cgp
  5. * @version $Id: cache_guests_pages.php, v.1.1.2, 2013/04/05 Kot $
  6. * @copyright (c) 2013 Vitaly Filatenko
  7. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8. *
  9. */
  10. /**
  11. * @ignore
  12. */
  13. if (!defined('IN_PHPBB'))
  14. {
  15. exit;
  16. }
  17. if (isset($config['cgp_enabled']) && $config['cgp_enabled'])
  18. {
  19. define('CGP_ENABLED', true);
  20. }
  21. if (defined('CGP_ENABLED') && !class_exists('CGP'))
  22. {
  23. /**
  24. * Helper class for providing Cache guests pages functionality
  25. *
  26. */
  27. class CGP
  28. {
  29. /**
  30. * Cache class variable
  31. */
  32. public static $cache;
  33. const debug_signature = '<!-- CGP DEBUG OUTPUT -->';
  34. /**
  35. * Simulate constructor for static class, should be called after class definition
  36. *
  37. * @return void
  38. *
  39. */
  40. public static function init()
  41. {
  42. global $acm_type, $config, $phpbb_root_path, $phpEx;
  43. // check for cache type settings
  44. if ($acm_type != 'file' && isset($config['cgp_force_fcache']) && $config['cgp_force_fcache'])
  45. {
  46. // force file cache using
  47. if (!class_exists('acm_file_extended'))
  48. {
  49. include($phpbb_root_path . 'includes/acm/acm_file_extended.' . $phpEx);
  50. }
  51. self::$cache = new acm_file_extended();
  52. }
  53. else
  54. {
  55. // use configured cache type
  56. global $cache;
  57. self::$cache = &$cache;
  58. }
  59. }
  60. /**
  61. * Indicates if user is allowed for pages caching
  62. *
  63. * @param user $user User class
  64. * @return bool True if this type of user is allowed for pages caching
  65. *
  66. */
  67. public static function is_cacheable_user(&$user)
  68. {
  69. return $user->data['user_id'] == ANONYMOUS || $user->data['is_bot'];
  70. }
  71. /**
  72. * Writes page content obtained from cached file, forces exit
  73. *
  74. * @param string $cache_key Name of cached file
  75. * @return void
  76. *
  77. */
  78. public static function display_if_cached($cache_key)
  79. {
  80. global $db, $config, $starttime;
  81. // check for If-None-Match behavior
  82. $match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? trim($_SERVER['HTTP_IF_NONE_MATCH']) : false;
  83. // prevent unnecessary acm_file_extended initialization
  84. if (!is_subclass_of(self::$cache, 'acm_file_extended', false))
  85. {
  86. //include extended cache class
  87. if (!class_exists('acm_file_extended'))
  88. {
  89. global $phpbb_root_path, $phpEx;
  90. include($phpbb_root_path . 'includes/acm/acm_file_extended.' . $phpEx);
  91. }
  92. $cache_ext = new acm_file_extended();
  93. }
  94. else
  95. {
  96. $cache_ext = self::$cache;
  97. }
  98. $cache_expired = $cache_ext->get_expiration($cache_key);
  99. // is matches to cached file?
  100. if ($match !== false && $cache_expired !== false && $match == $cache_expired)
  101. {
  102. // return not modified status
  103. send_status_line(304, 'Not Modified');
  104. self::set_cache_headers($cache_expired, $user->data['is_bot']);
  105. garbage_collection();
  106. exit_handler();
  107. }
  108. elseif (($cache_item = $cache_ext->get($cache_key)) !== false)
  109. {
  110. // gzip_compression (copied from page_header() )
  111. if ($config['gzip_compress'])
  112. {
  113. if (@extension_loaded('zlib') && !headers_sent() && ob_get_level() <= 1 && ob_get_length() == 0)
  114. {
  115. ob_start('ob_gzhandler');
  116. }
  117. }
  118. // not sure that we need for compatibility with HTTP Guest cache anymore
  119. // since own ETag/If-None-Match mechanism used now for cached pages
  120. self::set_cache_headers($cache_expired, $user->data['is_bot']);
  121. // echo debug information to the end of the page
  122. // it is not possible to use templated place, because no templates are used in cached output
  123. if (defined('DEBUG'))
  124. {
  125. $mtime = explode(' ', microtime());
  126. $totaltime = $mtime[0] + $mtime[1] - $starttime;
  127. $debug_output = sprintf('<br />CGP output time : %.3fs | ' . $db->sql_num_queries() . ' Queries | GZIP : ' . (($config['gzip_compress'] && @extension_loaded('zlib')) ? 'On' : 'Off'), $totaltime);
  128. $cache_item = str_replace(self::debug_signature, $debug_output, $cache_item);
  129. }
  130. // echo cached content
  131. echo $cache_item;
  132. unset($cache_item);
  133. garbage_collection();
  134. exit_handler();
  135. }
  136. // do nothing
  137. }
  138. /**
  139. * Puts rendered page content to cache, overrides cache-related headers, flushes output buffer
  140. *
  141. * @param string $cache_key Name of cached file
  142. * @param int $cache_ttl Cache time to live in minutes, default value equals 12 hours
  143. * @return void
  144. *
  145. */
  146. public static function cache_content_page($cache_key, $cache_ttl = 720)
  147. {
  148. // prevent unnecessary acm_file_extended initialization
  149. if (!is_subclass_of(self::$cache, 'acm_file_extended', false))
  150. {
  151. //include extended cache class
  152. if (!class_exists('acm_file_extended'))
  153. {
  154. global $phpbb_root_path, $phpEx;
  155. include($phpbb_root_path . 'includes/acm/acm_file_extended.' . $phpEx);
  156. }
  157. $cache_ext = new acm_file_extended();
  158. }
  159. else
  160. {
  161. $cache_ext = self::$cache;
  162. }
  163. // cache page content
  164. $cache_item = ob_get_contents();
  165. $expires = $cache_ext->put($cache_key, $cache_item, $cache_ttl * 60);
  166. // override Cache-Control headers
  167. if (!headers_sent())
  168. {
  169. global $user;
  170. self::set_cache_headers($expires, $user->data['is_bot']);
  171. }
  172. // note sure that we need to force buffer flushing,
  173. // anyway it is supposed that no more content operations will be performed.
  174. // Uncomment string below to force output buffer flushing
  175. //ob_end_flush();
  176. }
  177. /**
  178. * Set cache-related HTTP headers
  179. *
  180. * @param string $etag ETag header value
  181. * @param bool $is_bot Let reverse proxies know we detected a bot
  182. * @return void
  183. *
  184. */
  185. public static function set_cache_headers($etag, $is_bot = false)
  186. {
  187. // setting up page headers
  188. header('Content-type: text/html; charset=UTF-8');
  189. header('Cache-Control: public, no-cache="set-cookie"');
  190. header('Pragma: public');
  191. header('ETag: ' . $etag);
  192. if ($is_bot)
  193. {
  194. // Let reverse proxies know we detected a bot.
  195. header('X-PHPBB-IS-BOT: yes');
  196. }
  197. /* BEGIN compatibility with mod_http_guest_cache */
  198. global $mod_http_guest_cache;
  199. if (isset($mod_http_guest_cache) &&
  200. $mod_http_guest_cache->is_cacheable() &&
  201. method_exists($mod_http_guest_cache, 'set_headers'))
  202. {
  203. $mod_http_guest_cache->set_headers();
  204. }
  205. /* END compatibility with mod_http_guest_cache */
  206. }
  207. /**
  208. * Returns cache key suffix based on user display preferences
  209. *
  210. * @param user $user Base user class
  211. * @return string Cache key suffix
  212. *
  213. */
  214. public static function user_type_suffix(&$user)
  215. {
  216. global $config;
  217. // assumed that guests can use only one predefined forum style
  218. if(!$config['cgp_multi_styles'])
  219. {
  220. return '_' . ($user->data['is_bot'] ? 'bot' : 'guest');
  221. }
  222. else
  223. {
  224. return '_st' . $user->data['user_style'] . '_' . ($user->data['is_bot'] ? 'bot' : 'guest');
  225. }
  226. }
  227. /**
  228. * Removes all cached files related to the topic
  229. *
  230. * @param int $topic_id Topic id
  231. * @param array $topic_data $topic_data object
  232. * @return bool True if success, False if fails
  233. *
  234. */
  235. public static function destroy_topic_cache($topic_id, &$topic_data = null)
  236. {
  237. global $db, $config;
  238. // try to obtain $topic_data if it wasn't passed by param
  239. if (!isset($topic_data))
  240. {
  241. // I'd prefer to use same function from mcp.php,
  242. // but that file contains too much trash inside ;)
  243. $topic_data = CGP::get_topic_data($db, $topic_id);
  244. // can't obtain any topic information
  245. if ($topic_data === false)
  246. return false;
  247. }
  248. // destroy cache for topic's forum and forum's parents
  249. CGP::destroy_forum_cache($topic_data['forum_id'], $topic_data);
  250. // calculate pages count
  251. $total_pages = ceil(($topic_data['topic_replies'] + 1) / $config['posts_per_page']);
  252. if($config['cgp_multi_styles'])
  253. {
  254. $styles=CGP::get_active_styles();
  255. }
  256. if(!$config['cgp_multi_styles'])
  257. {
  258. // destroy cache for all topic pages, for both guest and bot
  259. for ($i = 0; $i < $total_pages; $i++)
  260. {
  261. $start = $i * $config['posts_per_page'];
  262. self::$cache->destroy("_vt_t{$topic_id}_s{$start}_guest");
  263. self::$cache->destroy("_vt_t{$topic_id}_s{$start}_bot");
  264. }
  265. }
  266. else
  267. {
  268. // destroy cache for all topic pages, for both guest and bot
  269. for ($i = 0; $i < $total_pages; $i++)
  270. {
  271. $start = $i * $config['posts_per_page'];
  272. foreach($styles as $st)
  273. {
  274. self::$cache->destroy("_vt_t{$topic_id}_s{$start}_st{$st}_guest");
  275. self::$cache->destroy("_vt_t{$topic_id}_s{$start}_st{$st}_bot");
  276. }
  277. }
  278. }
  279. return true;
  280. }
  281. /**
  282. * Removes all cached files related to the forum
  283. *
  284. * @param int $forum_id Forum id
  285. * @param array $topic_data $topic_data object
  286. * @return bool True if success, False if fails
  287. *
  288. */
  289. public static function destroy_forum_cache($forum_id, &$topic_data = null)
  290. {
  291. global $db, $config;
  292. // try to obtain $topic_data if it wasn't passed by param
  293. if (!isset($topic_data))
  294. {
  295. // I'd prefer to use same function from mcp.php,
  296. // but that file contains too much trash inside ;)
  297. $topic_data = CGP::get_forum_data($db, $forum_id);
  298. // can't obtain any topic information
  299. if ($topic_data === false)
  300. return false;
  301. }
  302. if(!$config['cgp_multi_styles'])
  303. {
  304. // destroy cache for index and portal pages
  305. self::$cache->destroy("_index_guest");
  306. self::$cache->destroy("_index_bot");
  307. // you can comment lines below if you don't have phpBB3 Portal installed
  308. self::$cache->destroy("_portal_guest");
  309. self::$cache->destroy("_portal_bot");
  310. }
  311. else
  312. {
  313. $styles=CGP::get_active_styles();
  314. foreach($styles as $st)
  315. {
  316. self::$cache->destroy("_index_st{$st}_guest");
  317. self::$cache->destroy("_index_st{$st}_bot");
  318. self::$cache->destroy("_portal_st{$st}_guest");
  319. self::$cache->destroy("_portal_st{$st}_bot");
  320. }
  321. }
  322. // calculate pages count
  323. $total_pages = ceil(($topic_data['forum_topics'] + 1) / $config['topics_per_page']);
  324. if(!$config['cgp_multi_styles'])
  325. {
  326. // destroy cache for all topic pages, for both guest and bot
  327. for ($i = 0; $i < $total_pages; $i++)
  328. {
  329. $start = $i * $config['topics_per_page'];
  330. self::$cache->destroy("_vf_f{$topic_data['forum_id']}_s{$start}_guest");
  331. self::$cache->destroy("_vf_f{$topic_data['forum_id']}_s{$start}_bot");
  332. }
  333. }
  334. else
  335. {
  336. // destroy cache for all topic pages, for both guest and bot
  337. for ($i = 0; $i < $total_pages; $i++)
  338. {
  339. $start = $i * $config['topics_per_page'];
  340. foreach($styles as $st)
  341. {
  342. self::$cache->destroy("_vf_f{$topic_data['forum_id']}_s{$start}_st{$st}_guest");
  343. self::$cache->destroy("_vf_f{$topic_data['forum_id']}_s{$start}_st{$st}_bot");
  344. }
  345. }
  346. }
  347. // destroy cache for forum's parents as well
  348. if (!function_exists('get_forum_parents'))
  349. {
  350. global $phpbb_root_path, $phpEx;
  351. include($phpbb_root_path . 'includes/functions_display.' . $phpEx);
  352. }
  353. $forum_parents = get_forum_parents($topic_data);
  354. if(!$config['cgp_multi_styles'])
  355. {
  356. foreach ($forum_parents as $forum_id => $value)
  357. {
  358. // Honestly, we need calculate amount of pages for each of these underlying forums.
  359. // But I'm not sure that heap of additional sql requests are appropriated here.
  360. // I assume outdated information related to latest message in child forum
  361. // doesn't costs any additional sql request. For guests, of course. Register for realtime data, yah! :)
  362. self::$cache->destroy("_vf_f{$forum_id}_s0_guest");
  363. self::$cache->destroy("_vf_f{$forum_id}_s0_bot");
  364. }
  365. }
  366. else
  367. {
  368. foreach ($forum_parents as $forum_id => $value)
  369. {
  370. foreach($styles as $st)
  371. {
  372. self::$cache->destroy("_vf_f{$forum_id}_s0_st{$st}_guest");
  373. self::$cache->destroy("_vf_f{$forum_id}_s0_st{$st}_bot");
  374. }
  375. }
  376. }
  377. return true;
  378. }
  379. /**
  380. * Obtains $topic_data object
  381. *
  382. * @param dbal $db Database class, passed by reference to avoid unnecessary "global" using
  383. * @param int $topic_id Topic id
  384. * @return array $topic_data
  385. *
  386. */
  387. private static function get_topic_data(&$db, $topic_id)
  388. {
  389. // get related forum data to clear corresponding forum caches as well
  390. $sql_array = array(
  391. 'SELECT' => 't.*, f.*',
  392. 'FROM' => array(FORUMS_TABLE => 'f'),
  393. );
  394. // Firebird handles two columns of the same name a little differently, this
  395. // addresses that by forcing the forum_id to come from the forums table.
  396. if ($db->sql_layer === 'firebird')
  397. {
  398. $sql_array['SELECT'] = 'f.forum_id AS forum_id, ' . $sql_array['SELECT'];
  399. }
  400. // Topics table need to be the last in the chain
  401. $sql_array['FROM'][TOPICS_TABLE] = 't';
  402. $sql_array['WHERE'] = "t.topic_id = $topic_id";
  403. $sql_array['WHERE'] .= ' AND (f.forum_id = t.forum_id';
  404. // If it is a global announcement make sure to set the forum id to a postable forum
  405. $sql_array['WHERE'] .= ' OR (t.topic_type = ' . POST_GLOBAL . '
  406. AND f.forum_type = ' . FORUM_POST . ')';
  407. $sql_array['WHERE'] .= ')';
  408. // Join to forum table on topic forum_id unless topic forum_id is zero
  409. // whereupon we join on the forum_id passed as a parameter ... this
  410. // is done so navigation, forum name, etc. remain consistent with where
  411. // user clicked to view a global topic
  412. $sql = $db->sql_build_query('SELECT', $sql_array);
  413. $result = $db->sql_query($sql);
  414. $topic_data = $db->sql_fetchrow($result);
  415. $db->sql_freeresult($result);
  416. if (!$topic_data)
  417. {
  418. return false;
  419. }
  420. else
  421. {
  422. return $topic_data;
  423. }
  424. }
  425. /**
  426. * Obtains $forum_data object
  427. *
  428. * @param dbal $db Database class, passed by reference to avoid unnecessary "global" using
  429. * @param int $forum_id Forum id
  430. * @return array $forum_data
  431. *
  432. */
  433. private static function get_forum_data(&$db, $forum_id)
  434. {
  435. $sql = "SELECT *
  436. FROM " . FORUMS_TABLE .
  437. " WHERE " . $db->sql_in_set('forum_id', $forum_id);
  438. $result = $db->sql_query($sql);
  439. $forum_data = $db->sql_fetchrow($result);
  440. $db->sql_freeresult($result);
  441. if (!$forum_data)
  442. {
  443. return false;
  444. }
  445. else
  446. {
  447. return $forum_data;
  448. }
  449. }
  450. private static function get_active_styles($all=false)
  451. {
  452. global $db;
  453. $sql_where = (!$all) ? 'WHERE style_active = 1 ' : '';
  454. $sql = 'SELECT style_id
  455. FROM ' . STYLES_TABLE . "
  456. $sql_where";
  457. $result = $db->sql_query($sql);
  458. $styles = array();
  459. while ($row = $db->sql_fetchrow($result))
  460. {
  461. $styles[]=$row['style_id'];
  462. }
  463. $db->sql_freeresult($result);
  464. return $styles;
  465. }
  466. } // class CGP
  467. // emulating static class constructor
  468. CGP::init();
  469. }
  470. ?>