├── Helper
├── Detect.php
├── MobileDetectModifier.php
└── Redirect.php
├── README.md
├── Test
└── Unit
│ └── MobiledetectPluginTest.php
├── View
└── Plugin
│ └── DesignExceptions.php
├── composer.json
├── etc
├── acl.xml
├── adminhtml
│ ├── menu.xml
│ └── system.xml
├── config.xml
├── di.xml
└── module.xml
└── registration.php
/Helper/Detect.php:
--------------------------------------------------------------------------------
1 | mobileDetect = $mobileDetect;
57 | parent::__construct($context);
58 | }
59 |
60 | /**
61 | * @return bool
62 | */
63 | public function isDetected()
64 | {
65 | return $this->detected;
66 | }
67 |
68 | /**
69 | * If is mobile device
70 | * @return bool
71 | */
72 | public function isMobile()
73 | {
74 | $this->detected = self::EA_MOBILE;
75 | return $this->mobileDetect->isMobile();
76 | }
77 |
78 | /**
79 | * If is a tablet
80 | * @return bool
81 | */
82 | public function isTablet()
83 | {
84 | $this->detected = self::EA_TABLET;
85 | return $this->mobileDetect->isTablet();
86 | }
87 |
88 | /**
89 | * If is desktop device
90 | * @return bool
91 | */
92 | public function isDesktop()
93 | {
94 | if ($this->isMobile()) {
95 | return false;
96 | }
97 | $this->detected = self::EA_DESKTOP;
98 | return true;
99 | }
100 |
101 | /**
102 | * The mobile detect instance to be able to use all the functionality
103 | * @return MobileDetect
104 | */
105 | public function getMobileDetect()
106 | {
107 | return $this->mobileDetect;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/Helper/MobileDetectModifier.php:
--------------------------------------------------------------------------------
1 | config = $context->getScopeConfig();
69 | $this->responseFactory = $responseFactory;
70 | $this->validator = $validator;
71 | parent::__construct($context);
72 | }
73 |
74 | /**
75 | * Get config value
76 | *
77 | * @param string $configPath
78 | * @return string
79 | */
80 | public function getConfig($configPath)
81 | {
82 | return $this->config->getValue(
83 | $configPath,
84 | ScopeInterface::SCOPE_STORE
85 | );
86 | }
87 |
88 | /**
89 | * Check if module is enable
90 | *
91 | * @return boolean
92 | */
93 | public function isEnable()
94 | {
95 | return $this->getConfig(self::MOBILEDETECT_ENABLED);
96 | }
97 |
98 | /**
99 | * Redirect to tablet url
100 | */
101 | public function redirectTablet()
102 | {
103 |
104 | $tablet = $this->getConfig(self::MOBILEDETECT_TABLET);
105 |
106 | if ($this->validator->isValid($tablet)) {
107 | $this->responseFactory->create()->setRedirect($tablet)->sendResponse();
108 | }
109 | }
110 |
111 | /**
112 | * Redirect to mobile url
113 | */
114 | public function redirectMobile()
115 | {
116 |
117 | $mobile = $this->getConfig(self::MOBILEDETECT_MOBILE);
118 |
119 | if ($this->validator->isValid($mobile)) {
120 | $this->responseFactory->create()->setRedirect($mobile)->sendResponse();
121 | }
122 | }
123 |
124 | /**
125 | * Redirect to desktop url
126 | */
127 | public function redirectDesktop()
128 | {
129 |
130 | $desktop = $this->getConfig(self::MOBILEDETECT_TABLET);
131 |
132 | if ($this->validator->isValid($desktop)) {
133 | $this->responseFactory->create()->setRedirect($desktop)->sendResponse();
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Magento 2 Mobile Detect Theme Change
2 |
3 | [](https://travis-ci.org/EaDesgin/magento2-mobiledetect)
4 |
5 | Magento 2 Mobile detect system can be used to load different themes base on the client device (desktop, tablet, mobile).
6 | It uses the library https://github.com/serbanghita/Mobile-Detect.
7 |
8 | # How to use the module
9 |
10 | The main configuration can be done under the Content > Design > Configuration. There (Design Rule > User Agent Rules) you can add user agent expressions.
11 |
12 | * add `eadesign_is_mobile` to load a theme for mobile
13 | * add `eadesign_is_tablet` to load a theme for tablet
14 | * add `eadesign_is_desktop` to load a theme for desktop
15 |
16 | Under system configurations you need to enable the extension. Also there you will find 3 fields for redirects.
17 | If you add a url to the mobile field for example the user will be redirected to the url in there.
18 | This can be useful if you want to use a different website/store view url for the mobile theme.
19 |
20 |
21 | # Installation.
22 |
23 | You can install the module via composer or manually by adding it to the app/code directory. The module is available on [packagist.org](https://packagist.org/packages/eadesignro/module-mobiledetect).
24 |
25 | Via composer
26 |
27 | ``` bash
28 | composer config repositories.magento2-mobiledetect git git@github.com:EaDesgin/magento2-mobiledetect;
29 | ```
30 |
31 | ``` bash
32 | composer require eadesignro/module-mobiledetect;
33 | ```
34 |
35 | ``` bash
36 | php bin/magento setup:upgrade;
37 | ```
38 |
39 | # Uninstall
40 |
41 | You need to remove the module.
42 |
43 | ``` bash
44 | composer remove eadesignro/module-mobiledetect;
45 | ```
46 |
--------------------------------------------------------------------------------
/Test/Unit/MobiledetectPluginTest.php:
--------------------------------------------------------------------------------
1 | objectManager = new ObjectManager($this);
76 |
77 | $this->requestMock = $this->getMockBuilder(Http::class)
78 | ->disableOriginalConstructor()
79 | ->getMock();
80 |
81 | $this->detectHelper = $this->getMockBuilder(Detect::class)
82 | ->setMethods(['getMobileDetect', 'isMobile', 'isTablet','isDetected'])
83 | ->disableOriginalConstructor()
84 | ->getMock();
85 |
86 | $this->redirectHelper = $this->getMockBuilder(Redirect::class)
87 | ->disableOriginalConstructor()
88 | ->getMock();
89 |
90 | $this->scopeConfigInterface = $this->getMockBuilder(ScopeConfigInterface::class)
91 | ->disableOriginalConstructor()
92 | ->getMock();
93 |
94 | $this->subject = $this->objectManager->getObject(InitialDesignExceptions::class);
95 |
96 | $unSerialize = new Unserialize();
97 |
98 | $this->designExceptions = new DesignExceptions(
99 | $this->scopeConfigInterface,
100 | $this->exceptionConfigPath,
101 | $this->scopeType,
102 | $this->detectHelper,
103 | $this->redirectHelper,
104 | $unSerialize
105 | );
106 | }
107 |
108 | /**
109 | * @param string $userAgent
110 | * @param bool $useConfig
111 | * @param bool|string $result
112 | * @param array $expressions
113 | * @dataProvider getThemeByRequestDataProvider
114 | */
115 | public function testIfTheModuleIsNotEnabledStandardSystem($userAgent, $useConfig, $result, $expressions = [])
116 | {
117 | $this->redirectHelper
118 | ->method('isEnable')
119 | ->willReturn(false);
120 |
121 | $this->requestMock->expects($this->once())
122 | ->method('getServer')
123 | ->with($this->equalTo('HTTP_USER_AGENT'))
124 | ->will($this->returnValue($userAgent));
125 |
126 | if ($useConfig) {
127 | $this->scopeConfigInterface->expects($this->any())
128 | ->method('getValue')
129 | ->with($this->equalTo($this->exceptionConfigPath), $this->equalTo($this->scopeType))
130 | ->will($this->returnValue(serialize($expressions)));
131 | }
132 |
133 | $subject = $this->subject;
134 |
135 | $proceed = function () use ($result) {
136 | return $result;
137 | };
138 |
139 | $this->assertSame(
140 | $result,
141 | $this->designExceptions->aroundGetThemeByRequest($subject, $proceed, $this->requestMock)
142 | );
143 | }
144 |
145 | /**
146 | * @return array
147 | */
148 | public function getThemeByRequestDataProvider()
149 | {
150 | return [
151 | [false, false, false],
152 | ['iphone', false, false],
153 | ['iphone', true, false],
154 | ['iphone', true, 'matched', [['regexp' => '/iphone/', 'value' => 'matched']]],
155 | ['explorer', true, false, [['regexp' => '/iphone/', 'value' => 'matched']]],
156 | ];
157 | }
158 |
159 | /**
160 | * @param $userAgent
161 | * @param $case
162 | * @param $ifMobileString
163 | * @param $useConfig
164 | * @param $result
165 | * @param array $expressions
166 | * @dataProvider getIfModuleIfMobileException
167 | */
168 | public function testIfModuleIfMobileException(
169 | $userAgent,
170 | $case,
171 | $ifMobileString,
172 | $useConfig,
173 | $result,
174 | $expressions = []
175 | ) {
176 |
177 | $this->redirectHelper
178 | ->method('isEnable')
179 | ->willReturn(true);
180 |
181 | $this->requestMock->method('getServer')
182 | ->with($this->equalTo('HTTP_USER_AGENT'))
183 | ->will($this->returnValue($userAgent));
184 |
185 | $mobileDetectModifier = $this->getMockBuilder(MobileDetectModifier::class)
186 | ->setMethods(['setHttpHeaders','setUserAgent'])
187 | ->disableOriginalConstructor()
188 | ->getMock();
189 |
190 | $this->detectHelper->method('getMobileDetect')
191 | ->will($this->returnValue($mobileDetectModifier));
192 |
193 | $this->detectHelper->method('is' . $case)
194 | ->willReturn(true);
195 |
196 | $this->detectHelper->expects($this->once())
197 | ->method('isDetected')
198 | ->willReturn($ifMobileString);
199 |
200 | if ($useConfig) {
201 | $this->scopeConfigInterface
202 | ->method('getValue')
203 | ->with($this->equalTo($this->exceptionConfigPath), $this->equalTo($this->scopeType))
204 | ->will($this->returnValue(serialize($expressions)));
205 | }
206 |
207 | $subject = $this->subject;
208 |
209 | $proceed = function () use ($result) {
210 | return $result;
211 | };
212 |
213 | $this->assertSame(
214 | $result,
215 | $this->designExceptions->aroundGetThemeByRequest($subject, $proceed, $this->requestMock)
216 | );
217 | }
218 |
219 | /**
220 | * @return array
221 | */
222 | public function getIfModuleIfMobileException()
223 | {
224 | return [
225 | ['iphone', 'Mobile', Detect::EA_MOBILE, false, false],
226 | ['iphone', 'Mobile', Detect::EA_MOBILE, true, false],
227 | ['iphone', 'Tablet', Detect::EA_TABLET, false, false],
228 | ['iphone', 'Tablet', Detect::EA_TABLET, true, false],
229 | ['iphone', 'Desktop', Detect::EA_DESKTOP, false, false],
230 | ['iphone', 'Desktop', Detect::EA_DESKTOP, true, false],
231 | ['iphone', 'Mobile', Detect::EA_MOBILE, true, 'matched',
232 | [['regexp' => '/' . Detect::EA_MOBILE . '/', 'value' => 'matched']]
233 | ],
234 | ['explorer', 'Tablet', Detect::EA_TABLET, true, 'matched',
235 | [['regexp' => '/' . Detect::EA_TABLET . '/', 'value' => 'matched']]
236 | ],
237 | ['explorer', 'Desktop', Detect::EA_DESKTOP, true, 'matched',
238 | [['regexp' => '/' . Detect::EA_DESKTOP . '/', 'value' => 'matched']]
239 | ],
240 | ];
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/View/Plugin/DesignExceptions.php:
--------------------------------------------------------------------------------
1 | detect = $detect;
70 | $this->redirect = $redirect;
71 | $this->unSerialize = $unSerialize;
72 | }
73 |
74 | /**
75 | * @param $subject
76 | * @param $proceed
77 | * @param HttpRequest $request
78 | * @return bool|string
79 | * @SuppressWarnings("unused")
80 | */
81 | // @codingStandardsIgnoreLine
82 | public function aroundGetThemeByRequest($subject, callable $proceed, HttpRequest $request)
83 | {
84 | $rules = $proceed($request);
85 |
86 | $userAgent = $request->getServer('HTTP_USER_AGENT');
87 |
88 | if (empty($userAgent)) {
89 | return $rules;
90 | }
91 |
92 | $this->userAgent = $userAgent;
93 |
94 | if (!$this->redirect->isEnable()) {
95 | return $rules;
96 | }
97 |
98 | $mobileDetect = $this->detect->getMobileDetect();
99 | $mobileDetect->setHttpHeaders($request->getHeaders());
100 | $mobileDetect->setUserAgent($userAgent);
101 |
102 | $exception = $this->ifThemeChange();
103 |
104 | if (!$exception) {
105 | return $rules;
106 | }
107 |
108 | $expressions = $this->scopeConfig->getValue(
109 | $this->exceptionConfigPath,
110 | $this->scopeType
111 | );
112 |
113 | if (!$expressions) {
114 | return $rules;
115 | }
116 |
117 | $expressions = $this->unSerialize->unserialize($expressions);
118 |
119 | foreach ($expressions as $rule) {
120 | if (preg_match($rule['regexp'], $exception)) {
121 | return $rule['value'];
122 | }
123 | }
124 |
125 | return $rules;
126 | }
127 |
128 | /**
129 | * The tablet is overwritten by the mobile
130 | *
131 | * @return bool
132 | */
133 | private function ifThemeChange()
134 | {
135 | if ($this->detect->isTablet()) {
136 | $this->redirect->redirectTablet();
137 | }
138 |
139 | if ($this->detect->isMobile()) {
140 | $this->redirect->redirectMobile();
141 | }
142 |
143 | if ($this->detect->isDesktop()) {
144 | $this->redirect->redirectDesktop();
145 | }
146 |
147 | $exception = $this->detect->isDetected();
148 |
149 | return $exception;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eadesignro/module-mobiledetect",
3 | "description": "Magento 2 EaDesign Mobile Detect System",
4 | "type": "magento2-module",
5 | "version": "1.1.8",
6 | "autoload": {
7 | "files": [
8 | "registration.php"
9 | ],
10 | "psr-4": {
11 | "Eadesigndev\\Mobiledetect\\": ""
12 | }
13 | },
14 | "repositories": [
15 | {
16 | "type": "composer",
17 | "url": "https://repo.magento.com/"
18 | }
19 | ],
20 | "license": [
21 | "Apache-2.0"
22 | ],
23 | "authors": [
24 | {
25 | "name": "EaDesign",
26 | "email": "office@eadesign.ro",
27 | "homepage": "https://www.eadesign.ro/",
28 | "role": "Developer"
29 | }
30 | ],
31 | "require": {
32 | "php": "~5.6.5|7.0.2|7.0.4|~7.0.6|7.0.25|~7.1.3|~7.2.0",
33 | "magento/module-backend": "100.1.*|101.0.*|100.2.*",
34 | "magento/framework": "100.1.*|100.2.*|101.0.*|102.0.*",
35 | "magento/module-theme": "100.1.*|100.2.*|101.0.*",
36 | "eadesignro/module-eacore": "^0.2.6",
37 | "mobiledetect/mobiledetectlib": "2.8.*"
38 | },
39 | "require-dev": {
40 | "friendsofphp/php-cs-fixer": "~2.13.0",
41 | "lusitanian/oauth": "~0.8.10",
42 | "magento/magento2-functional-testing-framework": "2.3.9",
43 | "pdepend/pdepend": "2.5.2",
44 | "phpmd/phpmd": "@stable",
45 | "phpunit/phpunit": "~6.5.0",
46 | "sebastian/phpcpd": "~3.0.0",
47 | "squizlabs/php_codesniffer": "3.3.1"
48 | },
49 | "minimum-stability": "dev",
50 | "prefer-stable": true
51 | }
52 |
--------------------------------------------------------------------------------
/etc/acl.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/etc/adminhtml/menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
46 |
47 |
--------------------------------------------------------------------------------
/etc/adminhtml/system.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
24 |
25 | eadesign_extensions
26 | Eadesigndev_Mobiledetect::config_system
27 |
28 |
29 |
30 |
31 | Enables or disables extension. If this is set to yes and you get the default template from Magento then you did something wrong.
32 | Magento\Config\Model\Config\Source\Yesno
33 |
34 |
35 |
36 | If the user comes from mobile
37 |
38 |
39 |
40 | If the user comes from tablet
41 |
42 |
43 |
44 | If the user comes from desktop
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/etc/config.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
24 |
25 | 1
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/etc/di.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
24 |
25 |
26 |
27 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/etc/module.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/registration.php:
--------------------------------------------------------------------------------
1 |