| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- <?php
- App::uses('Component', 'Controller');
- App::uses('Router', 'Routing');
- /**
- * A component to easily serve mobile views to users.
- * It allows good default values while not being restrictive as you can always
- * overwrite the auto-detection manually to force desktop or mobile version.
- *
- * Uses Session to remember lookups: User.mobile and User.nomobile
- * - mobile is the auto-detection (true/false)
- * - nomobile can be set by the user and overrides the default behavior/detection
- * (1=true/0=false or -1=null which will remove the override)
- * Uses object attributes as well as Configure to store the results for later use.
- * It also pushes switch urls to the view.
- *
- * New:
- * - Support for named params has been dropped in favor of query strings.
- * - Support for different engines (vendor should be used as this is up to date).
- * - Allows Configure setup and auto-start for easy default cases.
- * - Accept closures to easily use any custom detection engine.
- * - Cleanup and tests
- *
- * @author Mark Scherer
- * @license http://opensource.org/licenses/mit-license.php MIT
- */
- class MobileComponent extends Component {
- public $components = ['Session'];
- public $Controller = null;
- /**
- * Stores the result of the auto-detection.
- *
- * @var bool
- */
- public $isMobile = null;
- /**
- * Stores the final detection result including user preference.
- *
- * @var bool
- */
- public $setMobile = null;
- /**
- * Default values. Can also be set using Configure.
- *
- * @param array
- */
- protected $_defaultConfig = [
- 'on' => 'startup', // initialize (prior to controller's beforeRender) or startup
- 'engine' => 'vendor', // cake internal (deprecated), tools (deprecated) or vendor
- 'themed' => false, // If false uses subfolders instead of themes: /View/.../mobile/
- 'mobile' => ['mobile', 'tablet'], // what is mobile? tablets as well? only for vendor
- 'auto' => false, // auto set mobile views
- ];
- /**
- * MobileComponent::__construct()
- *
- * @param ComponentCollection $collection
- * @param array $config
- */
- public function __construct(ComponentCollection $collection, $config = []) {
- $defaults = (array)Configure::read('Mobile') + $this->_defaultConfig;
- $config += $defaults;
- parent::__construct($collection, $config);
- }
- /**
- * MobileComponent::initialize()
- *
- * @param Controller $Controller
- * @return void
- */
- public function initialize(Controller $Controller) {
- parent::initialize($Controller);
- $this->Controller = $Controller;
- if ($this->settings['on'] !== 'initialize') {
- return;
- }
- $this->_init();
- }
- /**
- * MobileComponent::startup()
- *
- * @param Controller $Controller
- * @return void
- */
- public function startup(Controller $Controller) {
- parent::startup($Controller);
- if ($this->settings['on'] !== 'startup') {
- return;
- }
- $this->_init();
- }
- /**
- * Main auto-detection logic including session based storage to avoid
- * multiple lookups.
- *
- * Uses "mobile" query string to overwrite the auto-detection.
- * -1 clears the fixation
- * 1 forces mobile
- * 0 forces no-mobile
- *
- * @return void
- */
- protected function _init() {
- $mobileOverwrite = $this->Controller->request->query('mobile');
- if ($mobileOverwrite !== null) {
- if ($mobileOverwrite === '-1') {
- $noMobile = null;
- } else {
- $wantsMobile = (bool)$mobileOverwrite;
- $noMobile = (int)(!$wantsMobile);
- }
- $this->Session->write('User.nomobile', $noMobile);
- }
- $this->isMobile();
- if (!$this->settings['auto']) {
- return;
- }
- $this->setMobile();
- }
- /**
- * Serve mobile views if available
- *
- * can be called from beforeFilter() to automatically serve an alternative mobile view
- * if the file exists. If it doesn't exist in `/View/[ViewPath]/mobile/` the normal one
- * will be used.
- *
- * @deprecated in favor of themed solution?
- * @return void
- */
- public function serveMobileIfAvailable() {
- $viewDir = App::path('View');
- // returns an array
- /*
- * array(
- * (int) 0 => '/var/www/maps-cakephp2/app/View/'
- * )
- */
- $mobileViewFile = $viewDir[0] . $this->viewPath . DS . 'Mobile' . DS . $this->params['action'] . '.ctp';
- //Debugger::log($this->viewPath);
- // use this to log the output to
- // app/tmp/logs/debug.log
- if (file_exists($mobileViewFile)) {
- // if device is mobile, change layout to mobile
- // but only if a view exists for it.
- $this->layout = 'mobile';
- // and if a mobile view file has been
- // created for the action, serve it instead
- // of the default view file
- $this->viewPath = $this->viewPath . '/Mobile/';
- }
- }
- /**
- * Sets mobile views as `Mobile` theme.
- *
- * Only needs to be called if auto is set to false.
- * Then you probably want to call this from your AppController::beforeRender().
- *
- * @return void
- */
- public function setMobile() {
- if ($this->isMobile === null) {
- $this->isMobile();
- }
- $noMobile = $this->Session->read('User.nomobile');
- if (!$this->isMobile && $noMobile === null || $noMobile) {
- $this->setMobile = false;
- } else {
- $this->setMobile = true;
- }
- $urlParams = Router::getParams(true);
- if (!isset($urlParams['named'])) {
- $urlParams['named'] = [];
- }
- if (!isset($urlParams['pass'])) {
- $urlParams['pass'] = [];
- }
- $urlParams = array_merge($urlParams, $urlParams['named'], $urlParams['pass']);
- unset($urlParams['named']);
- unset($urlParams['pass']);
- if (isset($urlParams['prefix'])) {
- unset($urlParams['prefix']);
- }
- if ($this->setMobile) {
- $urlParams['?']['mobile'] = 0;
- $url = Router::url($urlParams);
- $this->Controller->set('desktopUrl', $url);
- } else {
- $urlParams['?']['mobile'] = 1;
- $url = Router::url($urlParams);
- $this->Controller->set('mobileUrl', $url);
- }
- Configure::write('User.isMobile', (int)$this->isMobile);
- Configure::write('User.setMobile', (int)$this->setMobile);
- if (!$this->setMobile) {
- return;
- }
- if (!$this->settings['themed']) {
- $this->serveMobileIfAvailable();
- return;
- }
- $this->Controller->viewClass = 'Theme';
- $this->Controller->theme = 'Mobile';
- }
- /**
- * Determines if we need to so serve mobile views based on session preference
- * and browser headers.
- *
- * @return bool Success
- */
- public function isMobile() {
- if ($this->isMobile !== null) {
- return $this->isMobile;
- }
- $this->isMobile = $this->Session->read('User.mobile');
- if ($this->isMobile !== null) {
- return $this->isMobile;
- }
- $this->isMobile = (bool)$this->detect();
- $this->Session->write('User.mobile', (int)$this->isMobile);
- return $this->isMobile;
- }
- /**
- * Detects if the current request is from a mobile device.
- *
- * Note that the cake internal way might soon be deprecated:
- * https://github.com/cakephp/cakephp/issues/2546
- *
- * @return bool Success
- */
- public function detect() {
- // Deprecated - the vendor libs are far more accurate and up to date
- if ($this->settings['engine'] === 'cake') {
- $this->Controller->request->addDetector('mobile', ['options' => ['OMNIA7']]);
- return $this->Controller->request->is('mobile');
- }
- if (is_callable($this->settings['engine'])) {
- return call_user_func($this->settings['engine']);
- }
- if (!in_array($this->settings['engine'], ['tools', 'vendor'])) {
- throw new CakeException(sprintf('Engine %s not available', $this->settings['engine']));
- }
- return $this->detectByVendor($this->settings['engine']);
- }
- /**
- * Simple auto-detection based on Tools plugin or vendor classes.
- *
- * @param string $engine
- * @return bool Success
- */
- public function detectByVendor($engine) {
- $isMobile = $this->Session->read('Session.mobile');
- if ($isMobile !== null) {
- return (bool)$isMobile;
- }
- // Deprecated - the vendor libs are far more accurate and up to date
- if ($engine === 'tools') {
- App::uses('UserAgentLib', 'Tools.Lib');
- $UserAgentLib = new UserAgentLib();
- return (bool)$UserAgentLib->isMobile();
- }
- App::import('Vendor', 'Tools.MobileDetect', ['file' => 'MobileDetect/Mobile_Detect.php']);
- $MobileDetect = new Mobile_Detect();
- $result = empty($this->settings['mobile']) ? 0 : 1;
- if (in_array('mobile', $this->settings['mobile'])) {
- $result &= $MobileDetect->isMobile();
- }
- if (in_array('tablet', $this->settings['mobile'])) {
- $result |= $MobileDetect->isTablet();
- } else {
- $result &= !$MobileDetect->isTablet();
- }
- $this->Session->write('Session.mobile', (bool)$result);
- return (bool)$result;
- }
- }
|