MobileComponent.php 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. <?php
  2. namespace Tools\Controller\Component;
  3. use Cake\Controller\Controller;
  4. use Cake\Core\Configure;
  5. use Cake\Event\Event;
  6. use Cake\Routing\Router;
  7. use RuntimeException;
  8. use Shim\Controller\Component\Component;
  9. /**
  10. * A component to easily store mobile in session and serve mobile views to users.
  11. * It allows good default values while not being restrictive as you can always
  12. * overwrite the auto-detection manually to force desktop or mobile version.
  13. *
  14. * Uses object attributes as well as Configure to store the results for later use.
  15. *
  16. * Don't foget to set up your mobile detectors in your bootstrap.
  17. *
  18. * Uses Configure to cache lookups in request: User.isMobile and User.setMobile
  19. * - isMobile is the auto-detection (true/false)
  20. * - setMobile can be set by the user and overrides the default behavior/detection
  21. * (1=true/0=false or -1=null which will remove the override)
  22. *
  23. * The overwrite of a user is stored in the session: User.mobile.
  24. * It overwrites the Configure value.
  25. *
  26. * It also pushes switch urls to the view.
  27. *
  28. * @author Mark Scherer
  29. * @license http://opensource.org/licenses/mit-license.php MIT
  30. */
  31. class MobileComponent extends Component {
  32. /**
  33. * Stores the result of the auto-detection.
  34. *
  35. * @var bool|null
  36. */
  37. public $isMobile = null;
  38. /**
  39. * Stores the final detection result including user preference.
  40. *
  41. * @var bool|null
  42. */
  43. public $setMobile = null;
  44. /**
  45. * Default values. Can also be set using Configure.
  46. *
  47. * @var array
  48. */
  49. protected $_defaultConfig = [
  50. 'on' => 'beforeFilter', // initialize (prior to controller's beforeRender) or startup
  51. 'engine' => null, // CakePHP internal if null
  52. 'themed' => false, // If false uses subfolders instead of themes: /View/.../mobile/
  53. 'auto' => false, // auto set mobile views
  54. ];
  55. /**
  56. * @param array $config
  57. * @return void
  58. */
  59. public function initialize(array $config) {
  60. parent::initialize($config);
  61. if ($this->_config['on'] !== 'initialize') {
  62. return;
  63. }
  64. $this->_init();
  65. }
  66. /**
  67. * @param \Cake\Event\Event $event
  68. * @return void
  69. */
  70. public function beforeFilter(Event $event) {
  71. if ($this->_config['on'] !== 'beforeFilter') {
  72. return;
  73. }
  74. $this->_init();
  75. }
  76. /**
  77. * Main auto-detection logic including session based storage to avoid
  78. * multiple lookups.
  79. *
  80. * Uses "mobile" query string to overwrite the auto-detection.
  81. * -1 clears the fixation
  82. * 1 forces mobile
  83. * 0 forces no-mobile
  84. *
  85. * @return void
  86. */
  87. protected function _init() {
  88. $mobileOverwrite = $this->Controller->getRequest()->getQuery('mobile');
  89. if ($mobileOverwrite !== null) {
  90. if ($mobileOverwrite === '-1') {
  91. $this->Controller->getRequest()->getSession()->delete('User.mobile');
  92. } else {
  93. $wantsMobile = (bool)$mobileOverwrite;
  94. $this->Controller->getRequest()->getSession()->write('User.mobile', (int)$wantsMobile);
  95. }
  96. }
  97. $this->isMobile();
  98. if (!$this->_config['auto']) {
  99. return;
  100. }
  101. $this->setMobile();
  102. }
  103. /**
  104. * Sets mobile views as `Mobile` theme.
  105. *
  106. * Only needs to be called if auto is set to false.
  107. * Then you probably want to call this from your AppController::beforeRender().
  108. *
  109. * @return void
  110. */
  111. public function setMobile() {
  112. if ($this->isMobile === null) {
  113. $this->isMobile();
  114. }
  115. $forceMobile = $this->Controller->getRequest()->getSession()->read('User.mobile');
  116. if ($forceMobile !== null && !$forceMobile) {
  117. $this->setMobile = false;
  118. } elseif ($forceMobile !== null && $forceMobile || $this->isMobile()) {
  119. $this->setMobile = true;
  120. } else {
  121. $this->setMobile = false;
  122. }
  123. $urlParams = $this->getController()->getRequest()->getAttribute('params');
  124. if (!isset($urlParams['pass'])) {
  125. $urlParams['pass'] = [];
  126. }
  127. $urlParams = array_merge($urlParams, $urlParams['pass']);
  128. unset($urlParams['pass']);
  129. if (isset($urlParams['prefix'])) {
  130. unset($urlParams['prefix']);
  131. }
  132. if ($this->setMobile) {
  133. $urlParams['?']['mobile'] = 0;
  134. $url = Router::url($urlParams);
  135. $this->Controller->set('desktopUrl', $url);
  136. } else {
  137. $urlParams['?']['mobile'] = 1;
  138. $url = Router::url($urlParams);
  139. $this->Controller->set('mobileUrl', $url);
  140. }
  141. Configure::write('User.setMobile', (int)$this->setMobile);
  142. if (!$this->setMobile) {
  143. return;
  144. }
  145. $this->Controller->viewBuilder()->setClassName('Theme');
  146. $this->Controller->viewBuilder()->setTheme('Mobile');
  147. }
  148. /**
  149. * Determines if we need to so serve mobile views based on session preference
  150. * and browser headers.
  151. *
  152. * @return bool Success
  153. */
  154. public function isMobile() {
  155. if ($this->isMobile !== null) {
  156. return $this->isMobile;
  157. }
  158. $this->isMobile = Configure::read('User.isMobile');
  159. if ($this->isMobile !== null) {
  160. return $this->isMobile;
  161. }
  162. $this->isMobile = (bool)$this->detect();
  163. Configure::write('User.isMobile', (int)$this->isMobile);
  164. return $this->isMobile;
  165. }
  166. /**
  167. * Detects if the current request is from a mobile device.
  168. *
  169. * Note that the cake internal way might soon be deprecated:
  170. * https://github.com/cakephp/cakephp/issues/2546
  171. *
  172. * @return bool Success
  173. * @throws \RuntimeException
  174. */
  175. public function detect() {
  176. // Deprecated - the vendor libs are far more accurate and up to date
  177. if (!$this->_config['engine']) {
  178. if (isset($this->Controller->RequestHandler)) {
  179. return $this->Controller->getRequest()->is('mobile') || $this->Controller->RequestHandler->accepts('wap');
  180. }
  181. return $this->Controller->getRequest()->is('mobile');
  182. }
  183. if (is_callable($this->_config['engine'])) {
  184. return call_user_func($this->_config['engine']);
  185. }
  186. throw new RuntimeException(sprintf('Engine %s not available', $this->_config['engine']));
  187. }
  188. }