PageRenderTime 55ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/style/contrib/mobile.php

https://github.com/cyberalien/phpbb3_mobile
PHP | 677 lines | 426 code | 64 blank | 187 comment | 93 complexity | f77f633e295733fb5dca1eee160ce196 MD5 | raw file
  1. <?php
  2. /**
  3. * phpBB Mobile style mod for phpBB 3.0
  4. *
  5. * Created by Vjacheslav Trushkin (Arty) for use with one of mobile phpBB styles.
  6. * See detect_mobile.xml for mod installation instructions.
  7. * Check http://www.phpbbmobile.com/ for latest version.
  8. *
  9. * @version 3.4
  10. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  11. */
  12. if (!defined('IN_PHPBB'))
  13. {
  14. exit;
  15. }
  16. /**
  17. * Mobile style class
  18. */
  19. class phpbb_mobile
  20. {
  21. /**
  22. * Mobile style path.
  23. * Change it to correct string to make script locate mobile style faster.
  24. * Alternatively you can define 'MOBILE_STYLE_PATH' in includes/constants.php or config.php
  25. *
  26. * @var string|bool
  27. */
  28. public static $mobile_style_path = '';
  29. /**
  30. * Mobile style ID, if style is installed.
  31. * Change it to correct string to make script locate mobile style faster.
  32. * Alternatively you can define 'MOBILE_STYLE_ID' in includes/constants.php or config.php
  33. *
  34. * If mobile style path is set, this variable will be ignored
  35. *
  36. * @var int
  37. */
  38. public static $mobile_style_id = 0;
  39. /**
  40. * True if mobile style should be used for search engines
  41. *
  42. * @var bool
  43. */
  44. public static $mobile_seo = true;
  45. /**
  46. * True if link to switch to mobile style should be shown for desktop browsers
  47. *
  48. * @var bool
  49. */
  50. public static $always_show_link = true;
  51. /**
  52. * @var bool
  53. */
  54. protected static $mobile_mode = false;
  55. protected static $mobile_var = 'mobile';
  56. protected static $cookie_var = false;
  57. protected static $is_bot = false;
  58. protected static $is_desktop = false;
  59. protected static $passed_mobile_path = false;
  60. /**
  61. * Start mobile style setup
  62. *
  63. * @param bool|string $style_path Path to mobile style, saved to $passed_mobile_path
  64. */
  65. public static function setup($style_path = false)
  66. {
  67. global $user;
  68. self::set_cookie_var();
  69. self::override_template();
  70. self::$is_bot = empty($user->data['is_bot']) ? false : $user->data['is_bot'];
  71. // Check mode only if it wasn't checked already
  72. if (self::$mobile_mode === false)
  73. {
  74. if (is_string($style_path) && strlen($style_path))
  75. {
  76. self::$passed_mobile_path = $style_path;
  77. }
  78. elseif (is_int($style_path) && $style_path > 0)
  79. {
  80. self::$mobile_style_id = $style_path;
  81. }
  82. self::$mobile_mode = self::get_mode();
  83. }
  84. if (self::is_desktop_mode(self::$mobile_mode))
  85. {
  86. // Force desktop style
  87. return;
  88. }
  89. if (!self::is_mobile_mode(self::$mobile_mode))
  90. {
  91. // Detect browser
  92. $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
  93. if (!self::is_mobile_browser($user_agent))
  94. {
  95. self::$is_desktop = true;
  96. return;
  97. }
  98. }
  99. // Locate mobile style
  100. $path = self::locate_mobile_style();
  101. if ($path === false)
  102. {
  103. // Mobile style was not found
  104. self::set_cookie('404', false);
  105. self::$mobile_mode = '404';
  106. return;
  107. }
  108. // Set mobile style data
  109. self::set_mobile_style();
  110. }
  111. /**
  112. * Set mobile style
  113. */
  114. protected static function set_mobile_style()
  115. {
  116. global $user, $template;
  117. // Define MOBILE_STYLE if it is not defined yet
  118. if (!defined('MOBILE_STYLE'))
  119. {
  120. define('MOBILE_STYLE', true);
  121. }
  122. // Change user->theme data
  123. $user->theme = array_merge($user->theme, self::user_theme_data());
  124. $template->orig_tpl_inherits_id = $user->theme['template_inherits_id'];
  125. // Reset imageset
  126. $user->img_array = array();
  127. // Set template
  128. $template->set_template();
  129. }
  130. /**
  131. * Override global $template variable
  132. */
  133. protected static function override_template()
  134. {
  135. if (defined('ADMIN_START'))
  136. {
  137. return;
  138. }
  139. global $template;
  140. if (is_a($template, 'mobile_template'))
  141. {
  142. return;
  143. }
  144. $tpl = new mobile_template();
  145. $tpl->clone_properties($template);
  146. $template = $tpl;
  147. }
  148. /**
  149. * @return array Data for $user->theme
  150. */
  151. protected static function user_theme_data()
  152. {
  153. return array(
  154. 'style_id' => self::$mobile_style_id,
  155. 'template_storedb' => 0,
  156. 'template_path' => self::$mobile_style_path,
  157. 'template_id' => 0,
  158. 'bbcode_bitfield' => 'lNg=',
  159. 'template_inherits_id' => 1,
  160. 'template_inherit_path' => 'prosilver',
  161. 'theme_path' => self::$mobile_style_path,
  162. 'theme_name' => self::$mobile_style_path,
  163. 'theme_storedb' => 0,
  164. 'theme_id' => 0,
  165. 'imageset_path' => self::$mobile_style_path,
  166. 'imageset_id' => 0,
  167. 'imageset_name' => self::$mobile_style_path,
  168. );
  169. }
  170. /**
  171. * Set cookie variable name
  172. */
  173. protected static function set_cookie_var()
  174. {
  175. if (self::$cookie_var !== false)
  176. {
  177. return;
  178. }
  179. global $config;
  180. self::$cookie_var = (isset($config['cookie_name']) ? $config['cookie_name'] . '_' : '') . self::$mobile_var;
  181. }
  182. /**
  183. * Set cookie value
  184. *
  185. * param string $value Cookie value
  186. * param bool $long If true, cookie will be set for full duration. If false, cookie will be set for browser session duration
  187. */
  188. protected static function set_cookie($value, $long = true)
  189. {
  190. global $config;
  191. $cookietime = ($long && empty($_SERVER['HTTP_DNT'])) ? time() + (($config['max_autologin_time']) ? 86400 * (int) $config['max_autologin_time'] : 31536000) : 0;
  192. $name_data = rawurlencode(self::$cookie_var) . '=' . rawurlencode($value);
  193. $expire = ($cookietime) ? gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime) : '';
  194. $domain = (!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain'];
  195. header('Set-Cookie: ' . $name_data . (($cookietime) ? '; expires=' . $expire : '') . '; path=' . $config['cookie_path'] . $domain . ((!$config['cookie_secure']) ? '' : '; secure') . '; HttpOnly', false);
  196. }
  197. /**
  198. * Check if mode triggers mobile style
  199. *
  200. * @param string $mode Browser mode
  201. *
  202. * @return bool
  203. */
  204. protected static function is_mobile_mode($mode)
  205. {
  206. return ($mode == 'mobile');
  207. }
  208. /**
  209. * Check if mode triggers desktop style
  210. *
  211. * @param string $mode Browser mode
  212. *
  213. * @return bool
  214. */
  215. protected static function is_desktop_mode($mode)
  216. {
  217. return ($mode == 'desktop' || $mode == '404');
  218. }
  219. /*
  220. * Get browser mode from cookie and $_GET data
  221. *
  222. * @return string|false Mobile style mode, false if default
  223. */
  224. protected static function get_mode()
  225. {
  226. $value = request_var(self::$mobile_var, '');
  227. switch ($value)
  228. {
  229. case 'reset':
  230. // Reset to default mode
  231. self::set_cookie('', false);
  232. return false;
  233. case 'on':
  234. // Mobile device detected by JavaScript
  235. $value = 'mobile';
  236. self::set_cookie($value, false);
  237. return $value;
  238. case 'off':
  239. // Desktop detected by JavaScript
  240. $value = 'desktop';
  241. self::set_cookie($value, false);
  242. return $value;
  243. case '404': // Mobile style detected, but not found
  244. case 'desktop': // Force desktop style
  245. case 'mobile': // Force mobile style
  246. self::set_cookie($value, $value != '404');
  247. return $value;
  248. }
  249. if (isset($_COOKIE[self::$cookie_var]))
  250. {
  251. switch ($_COOKIE[self::$cookie_var])
  252. {
  253. case 'mobile': // Force mobile style
  254. case 'desktop': // Force desktop style
  255. $value = $_COOKIE[self::$cookie_var];
  256. return $value;
  257. }
  258. }
  259. else
  260. {
  261. self::set_cookie('');
  262. }
  263. return false;
  264. }
  265. /**
  266. * Return path to styles directory
  267. *
  268. * @param bool $include_style_path If true, result will include mobile style path
  269. *
  270. * @return string Path to 'styles' or to 'styles/' + mobile style path
  271. */
  272. protected static function styles_path($include_style_path = false)
  273. {
  274. global $phpbb_root_path;
  275. return $phpbb_root_path . 'styles' . ($include_style_path ? '/' . self::$mobile_style_path : '');
  276. }
  277. /**
  278. * Check user agent string for mobile browser id.
  279. *
  280. * @param string $user_agent User agent string
  281. *
  282. * @return bool True if mobile browser
  283. */
  284. public static function is_mobile_browser($user_agent)
  285. {
  286. if (self::$mobile_seo && self::$is_bot)
  287. {
  288. return true;
  289. }
  290. if ((strpos($user_agent, 'Mobile') === false && // Generic mobile browser string, most browsers have it.
  291. strpos($user_agent, 'SymbianOS') === false && // Nokia device running Symbian OS.
  292. strpos($user_agent, 'Opera M') === false && // Opera Mini or Opera Mobile.
  293. strpos($user_agent, 'Android') === false && // Android devices that don't have 'Mobile' in UA string.
  294. stripos($user_agent, 'HTC_') === false && // HTC devices that don't have 'Mobile' nor 'Android' in UA string. Case insensitive.
  295. strpos($user_agent, 'Fennec/') === false && // Firefox mobile
  296. strpos($user_agent, 'Kindle') === false && // Kindle Fire tablet
  297. strpos($user_agent, 'BlackBerry') === false) || // BlackBerry
  298. strpos($user_agent, 'iPad') !== false) // iPad should be excluded
  299. {
  300. // Not a mobile browser
  301. return false;
  302. }
  303. // Mobile browser
  304. return true;
  305. }
  306. /**
  307. * Check if mobile style exists
  308. *
  309. * @param string $path Directory name of mobile style
  310. *
  311. * @return bool True if mobile style exists
  312. */
  313. protected static function check_style_path($path)
  314. {
  315. // Locate and read style.cfg
  316. $style_cfg = self::styles_path() . '/' . $path . '/style.cfg';
  317. if (!file_exists($style_cfg))
  318. {
  319. return false;
  320. }
  321. $style_cfg_data = @file_get_contents($style_cfg);
  322. if ($style_cfg_data === false || strpos($style_cfg_data, 'mobile') === false)
  323. {
  324. return false;
  325. }
  326. // Check style.cfg for "mobile = 1"
  327. foreach (explode("\n", $style_cfg_data) as $row)
  328. {
  329. $list = explode('=', $row);
  330. if (count($list) == 2 && trim($list[0]) == 'mobile' && trim($list[1]) == '1')
  331. {
  332. return true;
  333. }
  334. }
  335. return false;
  336. }
  337. /**
  338. * Check if mobile style exists
  339. *
  340. * @param int $id Style id
  341. *
  342. * @return string|bool Path to style if mobile style exists, false on error
  343. */
  344. protected static function check_style_id($id)
  345. {
  346. global $db;
  347. $id = (int) $id;
  348. $sql = 'SELECT t.template_path
  349. FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . " t
  350. WHERE s.style_id = $id
  351. AND t.template_id = s.template_id";
  352. $result = $db->sql_query($sql);
  353. $row = $db->sql_fetchrow($result);
  354. $db->sql_freeresult($result);
  355. if ($row === false || !self::check_style_path($row['template_path']))
  356. {
  357. return false;
  358. }
  359. return $row['template_path'];
  360. }
  361. /**
  362. * Locate mobile style
  363. *
  364. * @param bool $check_db If true and mobile style id is set, script will check phpbb_styles table for mobile style
  365. * @param bool $check_dirs If true, script will search for mobile style in styles directory
  366. *
  367. * @return string|bool Mobile style path
  368. */
  369. public static function locate_mobile_style($check_db = true, $check_dirs = true)
  370. {
  371. // Locate by style path
  372. if (defined('MOBILE_STYLE_PATH') && self::check_style_path(MOBILE_STYLE_PATH))
  373. {
  374. self::$mobile_style_path = MOBILE_STYLE_PATH;
  375. return self::$mobile_style_path;
  376. }
  377. if (!empty(self::$mobile_style_path) && self::check_style_path(self::$mobile_style_path))
  378. {
  379. return self::$mobile_style_path;
  380. }
  381. // Locate by style id
  382. if ($check_db && defined('MOBILE_STYLE_ID') && ($path = self::check_style_id(MOBILE_STYLE_ID)) !== false)
  383. {
  384. self::$mobile_style_path = $path;
  385. return $path;
  386. }
  387. if ($check_db && self::$mobile_style_id && (!defined('MOBILE_STYLE_ID') || MOBILE_STYLE_ID != self::$mobile_style_id) && ($path = self::check_style_id(self::$mobile_style_id)) !== false)
  388. {
  389. self::$mobile_style_path = $path;
  390. return $path;
  391. }
  392. // Default style path, passed in session.php
  393. if (!empty(self::$passed_mobile_path) && self::check_style_path(self::$passed_mobile_path))
  394. {
  395. self::$mobile_style_path = self::$passed_mobile_path;
  396. return self::$mobile_style_path;
  397. }
  398. // Search styles directory
  399. if (!$check_dirs)
  400. {
  401. return false;
  402. }
  403. $styles_dir = self::styles_path();
  404. $iterator = new DirectoryIterator($styles_dir);
  405. foreach ($iterator as $fileinfo)
  406. {
  407. if ($fileinfo->isDir() && self::check_style_path($fileinfo->getFilename()))
  408. {
  409. self::$mobile_style_path = $fileinfo->getFilename();
  410. return self::$mobile_style_path;
  411. }
  412. }
  413. return false;
  414. }
  415. /**
  416. * Create HTML code for mobile style link
  417. *
  418. * @param template $template Template class instance
  419. * @param string $mode Mode to add to URL
  420. * @param string $lang Language variable for link text
  421. *
  422. * @return string HTML code for link
  423. */
  424. protected static function create_link($template, $mode, $lang)
  425. {
  426. global $user;
  427. $text = '';
  428. $lang_key = 'MOBILE_STYLE_' . strtoupper($lang);
  429. switch ($lang)
  430. {
  431. case 'switch_full':
  432. $text = isset($user->lang[$lang_key]) ? $user->lang[$lang_key] : 'Switch to full style';
  433. break;
  434. case 'switch_mobile':
  435. $text = isset($user->lang[$lang_key]) ? $user->lang[$lang_key] : 'Switch to mobile style';
  436. break;
  437. case 'not_found':
  438. $text = isset($user->lang[$lang_key]) ? $user->lang[$lang_key] : 'Error locating mobile style files';
  439. break;
  440. default:
  441. return '';
  442. }
  443. $link = $template->_rootref['U_INDEX'];
  444. if (!empty($template->_rootref['U_VIEW_TOPIC']))
  445. {
  446. $link = $template->_rootref['U_VIEW_TOPIC'];
  447. }
  448. elseif (!empty($template->_tpldata['navlinks']) && isset($template->_tpldata['navlinks'][count($template->_tpldata['navlinks']) - 1]['U_VIEW_FORUM']))
  449. {
  450. $link = $template->_tpldata['navlinks'][count($template->_tpldata['navlinks']) - 1]['U_VIEW_FORUM'];
  451. }
  452. $link .= (strpos($link, '?') === false ? '?' : '&amp;') . self::$mobile_var . '=' . $mode;
  453. return '<a href="' . $link . '">' . $text . '</a>';
  454. }
  455. /**
  456. * Add data to overall_footer.html
  457. *
  458. * @param mobile_template $template Template class instance
  459. *
  460. * @return string Empty string
  461. */
  462. public static function template_footer($template)
  463. {
  464. if (defined('MOBILE_STYLE') && self::$is_bot)
  465. {
  466. return '';
  467. }
  468. $link = '';
  469. switch (self::$mobile_mode)
  470. {
  471. case '404':
  472. $link = self::create_link($template, 'mobile', 'not_found');
  473. break;
  474. case 'mobile':
  475. $link = self::create_link($template, 'desktop', 'switch_full');
  476. break;
  477. case 'desktop':
  478. $link = self::create_link($template, 'mobile', 'switch_mobile');
  479. break;
  480. case '':
  481. if (!defined('MOBILE_STYLE') && (self::$always_show_link || !self::$is_desktop))
  482. {
  483. // Detected desktop style
  484. $link = self::create_link($template, 'mobile', 'switch_mobile');
  485. }
  486. elseif (defined('MOBILE_STYLE'))
  487. {
  488. // Detected mobile style
  489. $link = self::create_link($template, 'desktop', 'switch_full');
  490. }
  491. break;
  492. }
  493. if (strlen($link))
  494. {
  495. echo '<div class="mobile-style-switch mobile-style-switch-footer" style="padding: 5px; text-align: center;">' . $link . '</div>';
  496. }
  497. return '';
  498. }
  499. /**
  500. * Add data to overall_header.html
  501. *
  502. * @param mobile_template $template Template class instance
  503. *
  504. * @return string HTML code to echo after overall_header.html
  505. */
  506. public static function template_header($template)
  507. {
  508. if (defined('MOBILE_STYLE') && self::$is_bot)
  509. {
  510. return '';
  511. }
  512. $link = '';
  513. switch (self::$mobile_mode)
  514. {
  515. case 'mobile':
  516. if (isset($_GET[self::$mobile_var]))
  517. {
  518. // Show link below header only if style was just switched
  519. $link = self::create_link($template, 'desktop', 'switch_full');
  520. }
  521. break;
  522. case 'desktop':
  523. if (isset($_GET[self::$mobile_var]))
  524. {
  525. // Show link below header only if style was just switched
  526. $link = self::create_link($template, 'mobile', 'switch_mobile');
  527. }
  528. break;
  529. case '':
  530. self::include_js($template);
  531. if (defined('MOBILE_STYLE') && !isset($_COOKIE[self::$cookie_var]))
  532. {
  533. // Detected mobile style
  534. $link = self::create_link($template, 'desktop', 'switch_full');
  535. }
  536. break;
  537. }
  538. if (strlen($link))
  539. {
  540. return '<div class="mobile-style-switch mobile-style-switch-header" style="padding: 5px; text-align: center;">' . $link . '</div>';
  541. }
  542. return '';
  543. }
  544. /**
  545. * Attempt to include detect.js from mobile style
  546. *
  547. * @param mobile_template $template Template class instance
  548. */
  549. protected static function include_js($template)
  550. {
  551. if (defined('MOBILE_STYLE') && self::$is_bot)
  552. {
  553. return;
  554. }
  555. if (count($_POST) || isset($_GET[self::$mobile_var]))
  556. {
  557. // Do not redirect on forms or when URL has mode
  558. return;
  559. }
  560. // Locate mobile style
  561. if (self::locate_mobile_style(false, false) === false)
  562. {
  563. return;
  564. }
  565. $script = self::styles_path(true) . '/template/detect.js';
  566. if (!@file_exists($script))
  567. {
  568. return;
  569. }
  570. $template->_rootref['META'] = (isset($template->_rootref['META']) ? $template->_rootref['META'] : '') . '<script type="text/javascript"> var phpBBMobileStyle = ' . (defined('MOBILE_STYLE') ? 'true' : 'false') . ', phpBBMobileVar = \'' . addslashes(self::$mobile_var) . '\'; </script><script type="text/javascript" src="' . htmlspecialchars($script) . '?t=' . @filemtime($script) . '"></script>';
  571. }
  572. }
  573. /**
  574. * Extend template class to override _tpl_include()
  575. */
  576. class mobile_template extends template
  577. {
  578. /**
  579. * Override _tpl_include function
  580. */
  581. function _tpl_include($filename, $include = true)
  582. {
  583. if ($include)
  584. {
  585. $to_echo = '';
  586. if ($filename == 'overall_footer.html')
  587. {
  588. $to_echo = phpbb_mobile::template_footer($this);
  589. }
  590. if ($filename == 'overall_header.html')
  591. {
  592. $to_echo = phpbb_mobile::template_header($this);
  593. }
  594. }
  595. parent::_tpl_include($filename, $include);
  596. if ($include)
  597. {
  598. echo $to_echo;
  599. }
  600. }
  601. /**
  602. * Clone template class properties
  603. *
  604. * @param template $template Template class instance to copy from
  605. */
  606. public function clone_properties($template)
  607. {
  608. foreach (array_keys(get_class_vars(get_class($this))) as $var)
  609. {
  610. $this->$var = $template->$var;
  611. }
  612. }
  613. }