data['is_bot']) ? false : $user->data['is_bot']; // Check mode only if it wasn't checked already if (self::$mobile_mode === false) { if (is_string($style_path) && strlen($style_path)) { self::$passed_mobile_path = $style_path; } elseif (is_int($style_path) && $style_path > 0) { self::$mobile_style_id = $style_path; } self::$mobile_mode = self::get_mode(); } if (self::is_desktop_mode(self::$mobile_mode)) { // Force desktop style return; } if (!self::is_mobile_mode(self::$mobile_mode)) { // Detect browser $user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : ''; if (!self::is_mobile_browser($user_agent)) { self::$is_desktop = true; return; } } // Locate mobile style $path = self::locate_mobile_style(); if ($path === false) { // Mobile style was not found self::set_cookie('404', false); self::$mobile_mode = '404'; return; } // Set mobile style data self::set_mobile_style(); } /** * Set mobile style */ protected static function set_mobile_style() { global $user, $template; // Define MOBILE_STYLE if it is not defined yet if (!defined('MOBILE_STYLE')) { define('MOBILE_STYLE', true); } // Change user->theme data $user->theme = array_merge($user->theme, self::user_theme_data()); $template->orig_tpl_inherits_id = $user->theme['template_inherits_id']; // Reset imageset $user->img_array = array(); // Set template $template->set_template(); } /** * Override global $template variable */ protected static function override_template() { if (defined('ADMIN_START')) { return; } global $template; if (is_a($template, 'mobile_template')) { return; } $tpl = new mobile_template(); $tpl->clone_properties($template); $template = $tpl; } /** * @return array Data for $user->theme */ protected static function user_theme_data() { return array( 'style_id' => self::$mobile_style_id, 'template_storedb' => 0, 'template_path' => self::$mobile_style_path, 'template_id' => 0, 'bbcode_bitfield' => 'lNg=', 'template_inherits_id' => 1, 'template_inherit_path' => 'prosilver', 'theme_path' => self::$mobile_style_path, 'theme_name' => self::$mobile_style_path, 'theme_storedb' => 0, 'theme_id' => 0, 'imageset_path' => self::$mobile_style_path, 'imageset_id' => 0, 'imageset_name' => self::$mobile_style_path, ); } /** * Set cookie variable name */ protected static function set_cookie_var() { if (self::$cookie_var !== false) { return; } global $config; self::$cookie_var = (isset($config['cookie_name']) ? $config['cookie_name'] . '_' : '') . self::$mobile_var; } /** * Set cookie value * * param string $value Cookie value * param bool $long If true, cookie will be set for full duration. If false, cookie will be set for browser session duration */ protected static function set_cookie($value, $long = true) { global $config; $cookietime = ($long && empty($_SERVER['HTTP_DNT'])) ? time() + (($config['max_autologin_time']) ? 86400 * (int) $config['max_autologin_time'] : 31536000) : 0; $name_data = rawurlencode(self::$cookie_var) . '=' . rawurlencode($value); $expire = ($cookietime) ? gmdate('D, d-M-Y H:i:s \\G\\M\\T', $cookietime) : ''; $domain = (!$config['cookie_domain'] || $config['cookie_domain'] == 'localhost' || $config['cookie_domain'] == '127.0.0.1') ? '' : '; domain=' . $config['cookie_domain']; header('Set-Cookie: ' . $name_data . (($cookietime) ? '; expires=' . $expire : '') . '; path=' . $config['cookie_path'] . $domain . ((!$config['cookie_secure']) ? '' : '; secure') . '; HttpOnly', false); } /** * Check if mode triggers mobile style * * @param string $mode Browser mode * * @return bool */ protected static function is_mobile_mode($mode) { return ($mode == 'mobile'); } /** * Check if mode triggers desktop style * * @param string $mode Browser mode * * @return bool */ protected static function is_desktop_mode($mode) { return ($mode == 'desktop' || $mode == '404'); } /* * Get browser mode from cookie and $_GET data * * @return string|false Mobile style mode, false if default */ protected static function get_mode() { $value = request_var(self::$mobile_var, ''); switch ($value) { case 'reset': // Reset to default mode self::set_cookie('', false); return false; case 'on': // Mobile device detected by JavaScript $value = 'mobile'; self::set_cookie($value, false); return $value; case 'off': // Desktop detected by JavaScript $value = 'desktop'; self::set_cookie($value, false); return $value; case '404': // Mobile style detected, but not found case 'desktop': // Force desktop style case 'mobile': // Force mobile style self::set_cookie($value, $value != '404'); return $value; } if (isset($_COOKIE[self::$cookie_var])) { switch ($_COOKIE[self::$cookie_var]) { case 'mobile': // Force mobile style case 'desktop': // Force desktop style $value = $_COOKIE[self::$cookie_var]; return $value; } } else { self::set_cookie(''); } return false; } /** * Return path to styles directory * * @param bool $include_style_path If true, result will include mobile style path * * @return string Path to 'styles' or to 'styles/' + mobile style path */ protected static function styles_path($include_style_path = false) { global $phpbb_root_path; return $phpbb_root_path . 'styles' . ($include_style_path ? '/' . self::$mobile_style_path : ''); } /** * Check user agent string for mobile browser id. * * @param string $user_agent User agent string * * @return bool True if mobile browser */ public static function is_mobile_browser($user_agent) { if (self::$mobile_seo && self::$is_bot) { return true; } if ((strpos($user_agent, 'Mobile') === false && // Generic mobile browser string, most browsers have it. strpos($user_agent, 'SymbianOS') === false && // Nokia device running Symbian OS. strpos($user_agent, 'Opera M') === false && // Opera Mini or Opera Mobile. strpos($user_agent, 'Android') === false && // Android devices that don't have 'Mobile' in UA string. stripos($user_agent, 'HTC_') === false && // HTC devices that don't have 'Mobile' nor 'Android' in UA string. Case insensitive. strpos($user_agent, 'Fennec/') === false && // Firefox mobile strpos($user_agent, 'Kindle') === false && // Kindle Fire tablet strpos($user_agent, 'BlackBerry') === false) || // BlackBerry strpos($user_agent, 'iPad') !== false) // iPad should be excluded { // Not a mobile browser return false; } // Mobile browser return true; } /** * Check if mobile style exists * * @param string $path Directory name of mobile style * * @return bool True if mobile style exists */ protected static function check_style_path($path) { // Locate and read style.cfg $style_cfg = self::styles_path() . '/' . $path . '/style.cfg'; if (!file_exists($style_cfg)) { return false; } $style_cfg_data = @file_get_contents($style_cfg); if ($style_cfg_data === false || strpos($style_cfg_data, 'mobile') === false) { return false; } // Check style.cfg for "mobile = 1" foreach (explode("\n", $style_cfg_data) as $row) { $list = explode('=', $row); if (count($list) == 2 && trim($list[0]) == 'mobile' && trim($list[1]) == '1') { return true; } } return false; } /** * Check if mobile style exists * * @param int $id Style id * * @return string|bool Path to style if mobile style exists, false on error */ protected static function check_style_id($id) { global $db; $id = (int) $id; $sql = 'SELECT t.template_path FROM ' . STYLES_TABLE . ' s, ' . STYLES_TEMPLATE_TABLE . " t WHERE s.style_id = $id AND t.template_id = s.template_id"; $result = $db->sql_query($sql); $row = $db->sql_fetchrow($result); $db->sql_freeresult($result); if ($row === false || !self::check_style_path($row['template_path'])) { return false; } return $row['template_path']; } /** * Locate mobile style * * @param bool $check_db If true and mobile style id is set, script will check phpbb_styles table for mobile style * @param bool $check_dirs If true, script will search for mobile style in styles directory * * @return string|bool Mobile style path */ public static function locate_mobile_style($check_db = true, $check_dirs = true) { // Locate by style path if (defined('MOBILE_STYLE_PATH') && self::check_style_path(MOBILE_STYLE_PATH)) { self::$mobile_style_path = MOBILE_STYLE_PATH; return self::$mobile_style_path; } if (!empty(self::$mobile_style_path) && self::check_style_path(self::$mobile_style_path)) { return self::$mobile_style_path; } // Locate by style id if ($check_db && defined('MOBILE_STYLE_ID') && ($path = self::check_style_id(MOBILE_STYLE_ID)) !== false) { self::$mobile_style_path = $path; return $path; } 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) { self::$mobile_style_path = $path; return $path; } // Default style path, passed in session.php if (!empty(self::$passed_mobile_path) && self::check_style_path(self::$passed_mobile_path)) { self::$mobile_style_path = self::$passed_mobile_path; return self::$mobile_style_path; } // Search styles directory if (!$check_dirs) { return false; } $styles_dir = self::styles_path(); $iterator = new DirectoryIterator($styles_dir); foreach ($iterator as $fileinfo) { if ($fileinfo->isDir() && self::check_style_path($fileinfo->getFilename())) { self::$mobile_style_path = $fileinfo->getFilename(); return self::$mobile_style_path; } } return false; } /** * Create HTML code for mobile style link * * @param template $template Template class instance * @param string $mode Mode to add to URL * @param string $lang Language variable for link text * * @return string HTML code for link */ protected static function create_link($template, $mode, $lang) { global $user; $text = ''; $lang_key = 'MOBILE_STYLE_' . strtoupper($lang); switch ($lang) { case 'switch_full': $text = isset($user->lang[$lang_key]) ? $user->lang[$lang_key] : 'Switch to full style'; break; case 'switch_mobile': $text = isset($user->lang[$lang_key]) ? $user->lang[$lang_key] : 'Switch to mobile style'; break; case 'not_found': $text = isset($user->lang[$lang_key]) ? $user->lang[$lang_key] : 'Error locating mobile style files'; break; default: return ''; } $link = $template->_rootref['U_INDEX']; if (!empty($template->_rootref['U_VIEW_TOPIC'])) { $link = $template->_rootref['U_VIEW_TOPIC']; } elseif (!empty($template->_tpldata['navlinks']) && isset($template->_tpldata['navlinks'][count($template->_tpldata['navlinks']) - 1]['U_VIEW_FORUM'])) { $link = $template->_tpldata['navlinks'][count($template->_tpldata['navlinks']) - 1]['U_VIEW_FORUM']; } $link .= (strpos($link, '?') === false ? '?' : '&') . self::$mobile_var . '=' . $mode; return '' . $text . ''; } /** * Add data to overall_footer.html * * @param mobile_template $template Template class instance * * @return string Empty string */ public static function template_footer($template) { if (defined('MOBILE_STYLE') && self::$is_bot) { return ''; } $link = ''; switch (self::$mobile_mode) { case '404': $link = self::create_link($template, 'mobile', 'not_found'); break; case 'mobile': $link = self::create_link($template, 'desktop', 'switch_full'); break; case 'desktop': $link = self::create_link($template, 'mobile', 'switch_mobile'); break; case '': if (!defined('MOBILE_STYLE') && (self::$always_show_link || !self::$is_desktop)) { // Detected desktop style $link = self::create_link($template, 'mobile', 'switch_mobile'); } elseif (defined('MOBILE_STYLE')) { // Detected mobile style $link = self::create_link($template, 'desktop', 'switch_full'); } break; } if (strlen($link)) { echo ''; } return ''; } /** * Add data to overall_header.html * * @param mobile_template $template Template class instance * * @return string HTML code to echo after overall_header.html */ public static function template_header($template) { if (defined('MOBILE_STYLE') && self::$is_bot) { return ''; } $link = ''; switch (self::$mobile_mode) { case 'mobile': if (isset($_GET[self::$mobile_var])) { // Show link below header only if style was just switched $link = self::create_link($template, 'desktop', 'switch_full'); } break; case 'desktop': if (isset($_GET[self::$mobile_var])) { // Show link below header only if style was just switched $link = self::create_link($template, 'mobile', 'switch_mobile'); } break; case '': self::include_js($template); if (defined('MOBILE_STYLE') && !isset($_COOKIE[self::$cookie_var])) { // Detected mobile style $link = self::create_link($template, 'desktop', 'switch_full'); } break; } if (strlen($link)) { return '
' . $link . '
'; } return ''; } /** * Attempt to include detect.js from mobile style * * @param mobile_template $template Template class instance */ protected static function include_js($template) { if (defined('MOBILE_STYLE') && self::$is_bot) { return; } if (count($_POST) || isset($_GET[self::$mobile_var])) { // Do not redirect on forms or when URL has mode return; } // Locate mobile style if (self::locate_mobile_style(false, false) === false) { return; } $script = self::styles_path(true) . '/template/detect.js'; if (!@file_exists($script)) { return; } $template->_rootref['META'] = (isset($template->_rootref['META']) ? $template->_rootref['META'] : '') . ''; } } /** * Extend template class to override _tpl_include() */ class mobile_template extends template { /** * Override _tpl_include function */ function _tpl_include($filename, $include = true) { if ($include) { $to_echo = ''; if ($filename == 'overall_footer.html') { $to_echo = phpbb_mobile::template_footer($this); } if ($filename == 'overall_header.html') { $to_echo = phpbb_mobile::template_header($this); } } parent::_tpl_include($filename, $include); if ($include) { echo $to_echo; } } /** * Clone template class properties * * @param template $template Template class instance to copy from */ public function clone_properties($template) { foreach (array_keys(get_class_vars(get_class($this))) as $var) { $this->$var = $template->$var; } } }