├── tests ├── tmp │ └── .gitignore ├── php.ini-unix ├── IPubTests │ ├── .gitignore │ ├── MobileDetect │ │ ├── files │ │ │ ├── presenters.neon │ │ │ └── config.neon │ │ ├── templates │ │ │ └── default.latte │ │ ├── libs │ │ │ └── RouterFactory.php │ │ ├── ExtensionTest.phpt │ │ ├── DeviceViewTest.phpt │ │ ├── MobileDetectTest.phpt │ │ └── TemplateTest.phpt │ └── bootstrap.php └── php.ini-win ├── .gitignore ├── .travis.yml ├── src └── IPub │ └── MobileDetect │ ├── Exceptions │ ├── IException.php │ ├── CompileException.php │ └── InvalidArgumentException.php │ ├── TMobileDetect.php │ ├── Events │ ├── OnResponseHandler.php │ └── OnRequestHandler.php │ ├── MobileDetect.php │ ├── Templating │ └── Helpers.php │ ├── Helpers │ ├── CookieSettings.php │ └── DeviceView.php │ ├── Diagnostics │ └── Panel.php │ ├── DI │ └── MobileDetectExtension.php │ └── Latte │ └── Macros.php ├── .travis.composer.php ├── .scrutinizer.yml ├── composer.json ├── readme.md ├── license.md └── docs └── en └── index.md /tests/tmp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.* -------------------------------------------------------------------------------- /tests/php.ini-unix: -------------------------------------------------------------------------------- 1 | extension = xdebug.so 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor 3 | tests/temp 4 | -------------------------------------------------------------------------------- /tests/IPubTests/.gitignore: -------------------------------------------------------------------------------- 1 | coverage.dat 2 | *.actual 3 | *.expected 4 | -------------------------------------------------------------------------------- /tests/php.ini-win: -------------------------------------------------------------------------------- 1 | date.timezone = Europe/Prague 2 | 3 | [PHP] 4 | extension_dir = "./ext" 5 | extension=php_xdebug.dll 6 | -------------------------------------------------------------------------------- /tests/IPubTests/MobileDetect/files/presenters.neon: -------------------------------------------------------------------------------- 1 | application: 2 | mapping: 3 | *: IPubTests\MobileDetect\*Presenter 4 | 5 | services: 6 | # Test presenter 7 | presenters.test: 8 | class: IPubTests\MobileDetect\TestPresenter 9 | 10 | router: IPubTests\MobileDetect\Libs\RouterFactory::createRouter 11 | -------------------------------------------------------------------------------- /tests/IPubTests/MobileDetect/files/config.neon: -------------------------------------------------------------------------------- 1 | mobileDetect: 2 | redirect: 3 | mobile: 4 | isEnabled : true 5 | host : http://m.site.com 6 | statusCode : 301 7 | action : redirect 8 | tablet: 9 | isEnabled : true 10 | host : http://t.site.com 11 | statusCode : 301 12 | action : redirect 13 | detectTabletAsMobile: true 14 | switchDeviceView: 15 | saveRefererPath: true -------------------------------------------------------------------------------- /tests/IPubTests/MobileDetect/templates/default.latte: -------------------------------------------------------------------------------- 1 | {isMobile} 2 |
Mobile device
3 | {/isMobile} 4 | {isTablet} 5 |
Table device
6 | {/isTablet} 7 | 8 | {isPhone} 9 |
Phone device
10 | {/isPhone} 11 | 12 | {isMobileDevice iPhone} 13 |
iPhone
14 | {/isMobileDevice} 15 | 16 | {isMobileOs iOS} 17 |
iOS
18 | {/isMobileOs} 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | env: 4 | - NETTE=default 5 | - NETTE=~3.0.0 6 | 7 | php: 8 | - 7.2 9 | - 7.3 10 | 11 | before_install: 12 | - composer self-update 13 | 14 | before_script: 15 | - php .travis.composer.php 16 | - composer install --no-interaction --prefer-source --dev 17 | 18 | script: 19 | - vendor/bin/tester -s -p php -c ./tests/php.ini-unix tests 20 | 21 | after_failure: 22 | - 'for i in $(find ./tests -name \*.actual); do echo "--- $i"; cat $i; echo; echo; done' 23 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/Exceptions/IException.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Exceptions 10 | * @since 2.0.0 11 | * 12 | * @date 13.01.17 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Exceptions; 18 | 19 | interface IException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /.travis.composer.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Exceptions 10 | * @since 2.0.0 11 | * 12 | * @date 13.01.17 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Exceptions; 18 | 19 | use Latte; 20 | 21 | class CompileException extends Latte\CompileException implements IException 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/Exceptions/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Exceptions 10 | * @since 2.0.0 11 | * 12 | * @date 20.07.16 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Exceptions; 18 | 19 | class InvalidArgumentException extends \InvalidArgumentException implements IException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | php: true 3 | 4 | build: 5 | environment: 6 | php: 7 | version: 7.2 8 | variables: 9 | NETTE: default 10 | 11 | dependencies: 12 | before: 13 | - 14 | command: 'mkdir -p build/logs' 15 | 16 | tests: 17 | override: 18 | - 19 | command: 'vendor/bin/tester tests -p php -c ./tests/php.ini-unix --coverage build/logs/clover.xml --coverage-src src' 20 | coverage: 21 | file: build/logs/clover.xml 22 | format: php-clover 23 | 24 | filter: 25 | excluded_paths: 26 | - tests/* 27 | 28 | coding_style: 29 | php: 30 | indentation: 31 | general: 32 | use_tabs: true 33 | spaces: 34 | before_parentheses: 35 | closure_definition: true 36 | -------------------------------------------------------------------------------- /tests/IPubTests/bootstrap.php: -------------------------------------------------------------------------------- 1 | run(); 35 | } 36 | -------------------------------------------------------------------------------- /tests/IPubTests/MobileDetect/libs/RouterFactory.php: -------------------------------------------------------------------------------- 1 | 9 | * @package iPublikuj:MobileDetect! 10 | * @subpackage Tests 11 | * @since 2.1.1 12 | * 13 | * @date 15.01.17 14 | */ 15 | 16 | declare(strict_types = 1); 17 | 18 | namespace IPubTests\MobileDetect\Libs; 19 | 20 | use Nette\Application; 21 | use Nette\Application\Routers; 22 | 23 | /** 24 | * Simple routes factory 25 | * 26 | * @package iPublikuj:MobileDetect! 27 | * @subpackage Tests 28 | * 29 | * @author Adam Kadlec 30 | */ 31 | class RouterFactory 32 | { 33 | /** 34 | * @return Application\IRouter 35 | */ 36 | public static function createRouter() : Application\IRouter 37 | { 38 | $router = new Routers\RouteList(); 39 | $router[] = new Routers\Route('/[/]', 'Test:default'); 40 | 41 | return $router; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/TMobileDetect.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage common 10 | * @since 1.0.0 11 | * 12 | * @date 24.04.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect; 18 | 19 | use IPub\MobileDetect\Helpers; 20 | 21 | /** 22 | * Mobile detect trait for presenters & components 23 | * 24 | * @package iPublikuj:MobileDetect! 25 | * @subpackage common 26 | * 27 | * @author Adam Kadlec 28 | */ 29 | trait TMobileDetect 30 | { 31 | /** 32 | * @var MobileDetect 33 | */ 34 | protected $mobileDetect; 35 | 36 | /** 37 | * @var Helpers\DeviceView 38 | */ 39 | protected $deviceView; 40 | 41 | /** 42 | * @param MobileDetect $mobileDetect 43 | * @param Helpers\DeviceView $deviceView 44 | * 45 | * @return void 46 | */ 47 | public function injectMobileDetector(MobileDetect $mobileDetect, Helpers\DeviceView $deviceView) : void 48 | { 49 | $this->mobileDetect = $mobileDetect; 50 | $this->deviceView = $deviceView; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/IPubTests/MobileDetect/ExtensionTest.phpt: -------------------------------------------------------------------------------- 1 | 9 | * @package iPublikuj:MobileDetect! 10 | * @subpackage Tests 11 | * @since 1.0.0 12 | * 13 | * @date 10.01.15 14 | */ 15 | 16 | declare(strict_types = 1); 17 | 18 | namespace IPubTests\MobileDetect; 19 | 20 | use Nette; 21 | 22 | use Tester; 23 | use Tester\Assert; 24 | 25 | use IPub\MobileDetect; 26 | 27 | require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'bootstrap.php'; 28 | 29 | class ExtensionTest extends Tester\TestCase 30 | { 31 | public function testFunctional() : void 32 | { 33 | $dic = $this->createContainer(); 34 | 35 | Assert::true($dic->getService('mobileDetect.mobileDetect') instanceof MobileDetect\MobileDetect); 36 | Assert::true($dic->getService('mobileDetect.deviceView') instanceof MobileDetect\Helpers\DeviceView); 37 | Assert::true($dic->getService('mobileDetect.onRequestHandler') instanceof MobileDetect\Events\OnRequestHandler); 38 | Assert::true($dic->getService('mobileDetect.onResponseHandler') instanceof MobileDetect\Events\OnResponseHandler); 39 | } 40 | 41 | /** 42 | * @return Nette\DI\Container 43 | */ 44 | protected function createContainer() : Nette\DI\Container 45 | { 46 | $config = new Nette\Configurator(); 47 | $config->setTempDirectory(TEMP_DIR); 48 | 49 | MobileDetect\DI\MobileDetectExtension::register($config); 50 | 51 | $config->addConfig(__DIR__ . DS . 'files' . DS . 'config.neon'); 52 | 53 | return $config->createContainer(); 54 | } 55 | } 56 | 57 | \run(new ExtensionTest()); 58 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/Events/OnResponseHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Events 10 | * @since 1.0.0 11 | * 12 | * @date 23.04.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Events; 18 | 19 | use Nette\Application; 20 | 21 | use IPub\MobileDetect\Helpers\DeviceView; 22 | 23 | /** 24 | * On response event handler 25 | * 26 | * @package iPublikuj:MobileDetect! 27 | * @subpackage Events 28 | * 29 | * @author Adam Kadlec 30 | */ 31 | final class OnResponseHandler 32 | { 33 | /** 34 | * @var bool 35 | */ 36 | private $needModifyResponse = false; 37 | 38 | /** 39 | * @var DeviceView 40 | */ 41 | private $deviceView; 42 | 43 | /** 44 | * @var \Closure 45 | */ 46 | public $modifyResponseClosure; 47 | 48 | /** 49 | * @param DeviceView $deviceView 50 | */ 51 | public function __construct(DeviceView $deviceView) 52 | { 53 | $this->deviceView = $deviceView; 54 | } 55 | 56 | /** 57 | * Stores information about modifying response 58 | * 59 | * @return void 60 | */ 61 | public function needModifyResponse() : void 62 | { 63 | $this->needModifyResponse = true; 64 | } 65 | 66 | /** 67 | * @param Application\Application $application 68 | * 69 | * @return void 70 | */ 71 | public function __invoke(Application\Application $application) : void 72 | { 73 | if ($this->needModifyResponse && $this->modifyResponseClosure instanceof \Closure) { 74 | $modifyClosure = $this->modifyResponseClosure; 75 | $modifyClosure($this->deviceView); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/MobileDetect.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage common 10 | * @since 1.0.0 11 | * 12 | * @date 21.04.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect; 18 | 19 | use Nette\Http; 20 | 21 | use IPub\MobileDetect\Helpers; 22 | 23 | use Jenssegers\Agent; 24 | 25 | /** 26 | * Mobile detect detector service 27 | * 28 | * @package iPublikuj:MobileDetect! 29 | * @subpackage common 30 | * 31 | * @author Adam Kadlec 32 | */ 33 | final class MobileDetect extends Agent\Agent 34 | { 35 | /** 36 | * @var Helpers\DeviceView 37 | */ 38 | private $deviceView; 39 | 40 | /** 41 | * @param Helpers\DeviceView $deviceView 42 | * @param Http\Request $httpRequest 43 | */ 44 | public function __construct( 45 | Helpers\DeviceView $deviceView, 46 | Http\Request $httpRequest 47 | ) { 48 | // Get http headers 49 | $httpHeaders = $httpRequest->getHeaders(); 50 | 51 | $userAgent = NULL; 52 | 53 | // If user agent info is set in headers... 54 | if (isset($httpHeaders['user-agent'])) { 55 | // ...set user agent details 56 | $userAgent = $httpHeaders['user-agent']; 57 | } 58 | 59 | parent::__construct($httpHeaders, $userAgent); 60 | 61 | $this->deviceView = $deviceView; 62 | } 63 | 64 | /** 65 | * Check if the device is not mobile phone 66 | * 67 | * @return bool 68 | */ 69 | public function isNotPhone() : bool 70 | { 71 | return (($this->isMobile() && $this->isTablet()) || !$this->isMobile()); 72 | } 73 | 74 | /** 75 | * @return string 76 | */ 77 | public function view() : string 78 | { 79 | return $this->deviceView->getViewType(); 80 | } 81 | 82 | /** 83 | * @return string 84 | */ 85 | public function browserVersion() : string 86 | { 87 | return (string) $this->version($this->browser()); 88 | } 89 | 90 | /** 91 | * @return string 92 | */ 93 | public function platformVersion() : string 94 | { 95 | return (string) $this->version($this->platform()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "ipub/mobile-detect", 3 | "type" : "library", 4 | "description" : "Extension for detecting mobile devices, managing mobile view types, redirect to mobile version for Nette Framework", 5 | "keywords" : ["nette", "mobile", "mobile detect", "mobile view managing", "mobile redirect", "ipub", "ipublikuj", "framework", "tools"], 6 | "homepage" : "https://github.com/iPublikuj/mobile-detect", 7 | "license" : ["GPL-2.0", "GPL-3.0"], 8 | 9 | "authors": [ 10 | { 11 | "name" : "iPublikuj:cms", 12 | "email" : "info@ipublikuj.eu", 13 | "homepage" : "https://www.ipublikuj.eu/" 14 | } 15 | ], 16 | 17 | "support":{ 18 | "email" :"support@ipublikuj.eu", 19 | "issues" :"https://github.com/iPublikuj/mobile-detect/issues" 20 | }, 21 | 22 | "extra": { 23 | "ipub" : { 24 | "configuration" : { 25 | "extensions" : { 26 | "mobileDetect" : "IPub\\MobileDetect\\DI\\MobileDetectExtension" 27 | } 28 | }, 29 | "mobileDetect" : { 30 | "redirect" : { 31 | "mobile" : { 32 | "isEnabled" : false, 33 | "host" : null, 34 | "statusCode" : 301, 35 | "action" : "noRedirect" 36 | }, 37 | "phone" : { 38 | "isEnabled" : false, 39 | "host" : null, 40 | "statusCode" : 301, 41 | "action" : "noRedirect" 42 | }, 43 | "tablet" : { 44 | "isEnabled" : false, 45 | "host" : null, 46 | "statusCode" : 301, 47 | "action" : "noRedirect" 48 | }, 49 | "detectPhoneAsMobile" : false, 50 | "detectTabletAsMobile" : false 51 | }, 52 | "switchDeviceView" : { 53 | "saveRefererPath" : true 54 | } 55 | } 56 | } 57 | }, 58 | 59 | "require": { 60 | "php" : ">=7.2.0", 61 | 62 | "nette/application" : "~3.0", 63 | "nette/di" : "~3.0", 64 | "nette/http" : "~3.0", 65 | "nette/utils" : "~3.0", 66 | 67 | "latte/latte" : "~2.5", 68 | 69 | "jenssegers/agent" : "~2.5" 70 | }, 71 | 72 | "require-dev": { 73 | "nette/bootstrap" : "~3.0", 74 | "nette/mail" : "~3.0", 75 | "nette/robot-loader" : "~3.0", 76 | "nette/safe-stream" : "~2.4", 77 | "nette/tester" : "~2.3", 78 | 79 | "tracy/tracy" : "~2.6", 80 | 81 | "pds/skeleton" : "~1.0" 82 | }, 83 | 84 | "autoload": { 85 | "psr-0": { 86 | "IPub\\MobileDetect\\": "src/" 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # MobileDetect 2 | 3 | [![Build Status](https://img.shields.io/travis/iPublikuj/mobile-detect.svg?style=flat-square)](https://travis-ci.org/iPublikuj/mobile-detect) 4 | [![Scrutinizer Code Coverage](https://img.shields.io/scrutinizer/coverage/g/iPublikuj/mobile-detect.svg?style=flat-square)](https://scrutinizer-ci.com/g/iPublikuj/mobile-detect/?branch=master) 5 | [![Scrutinizer Code Quality](https://img.shields.io/scrutinizer/g/iPublikuj/mobile-detect.svg?style=flat-square)](https://scrutinizer-ci.com/g/iPublikuj/mobile-detect/?branch=master) 6 | [![Latest Stable Version](https://img.shields.io/packagist/v/ipub/mobile-detect.svg?style=flat-square)](https://packagist.org/packages/ipub/mobile-detect) 7 | [![Composer Downloads](https://img.shields.io/packagist/dt/ipub/mobile-detect.svg?style=flat-square)](https://packagist.org/packages/ipub/mobile-detect) 8 | [![License](https://img.shields.io/packagist/l/ipub/mobile-detect.svg?style=flat-square)](https://packagist.org/packages/ipub/mobile-detect) 9 | 10 | Extension for detecting mobile devices, managing mobile view types, redirect to mobile version for [Nette Framework](http://nette.org/) 11 | 12 | ## Introduction 13 | 14 | This extension use [Mobile_Detect](https://github.com/serbanghita/Mobile-Detect) class and provides the following features: 15 | 16 | * Detect the various mobile devices by name, OS, browser User-Agent 17 | * Manages site views for the variuos mobile devices (`mobile`, `phone`, `tablet`, `full`) 18 | * Redirects to mobile and tablet sites 19 | 20 | ## Installation 21 | 22 | The best way to install ipub/mobile-detect is using [Composer](http://getcomposer.org/): 23 | 24 | ```sh 25 | $ composer require ipub/mobile-detect 26 | ``` 27 | 28 | After that you have to register extension in config.neon. 29 | 30 | ```neon 31 | extensions: 32 | mobileDetect: IPub\MobileDetect\DI\MobileDetectExtension 33 | ``` 34 | 35 | Package contains trait, which you will have to use in class, where you want to use mobile detector. 36 | 37 | ```php 38 | 9 | * @package iPublikuj:MobileDetect! 10 | * @subpackage Tests 11 | * @since 1.0.0 12 | * 13 | * @date 10.01.15 14 | */ 15 | 16 | declare(strict_types = 1); 17 | 18 | namespace IPubTests\MobileDetect; 19 | 20 | use Nette; 21 | 22 | use Tester; 23 | use Tester\Assert; 24 | 25 | use IPub\MobileDetect; 26 | use IPub\MobileDetect\Helpers; 27 | 28 | require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'bootstrap.php'; 29 | 30 | class DeviceViewTest extends Tester\TestCase 31 | { 32 | public function testRequestHasSwitchParam() : void 33 | { 34 | $query = [ 35 | 'myparam' => 'myvalue', 36 | 'device_view' => Helpers\DeviceView::VIEW_MOBILE 37 | ]; 38 | 39 | // Get helper 40 | $deviceView = $this->getHelper($query); 41 | 42 | Assert::true($deviceView->hasSwitchParameter()); 43 | Assert::equal(Helpers\DeviceView::VIEW_MOBILE, $deviceView->getSwitchParameterValue()); 44 | } 45 | 46 | public function testDeviceIsMobile() : void 47 | { 48 | $query = [ 49 | 'myparam' => 'myvalue', 50 | 'device_view' => Helpers\DeviceView::VIEW_MOBILE 51 | ]; 52 | 53 | // Get helper 54 | $deviceView = $this->getHelper($query); 55 | 56 | Assert::equal(Helpers\DeviceView::VIEW_MOBILE, $deviceView->getViewType()); 57 | Assert::true($deviceView->isMobileView()); 58 | Assert::false($deviceView->isFullView()); 59 | } 60 | 61 | public function testMobileViewType() : void 62 | { 63 | $query = [ 64 | 'myparam' => 'myvalue', 65 | 'device_view' => Helpers\DeviceView::VIEW_MOBILE 66 | ]; 67 | 68 | // Get helper 69 | $deviceView = $this->getHelper($query); 70 | 71 | Assert::equal(Helpers\DeviceView::VIEW_MOBILE, $deviceView->getViewType()); 72 | } 73 | 74 | public function testSetMobileViewType() : void 75 | { 76 | $query = [ 77 | 'myparam' => 'myvalue', 78 | 'device_view' => Helpers\DeviceView::VIEW_MOBILE 79 | ]; 80 | 81 | // Get helper 82 | $deviceView = $this->getHelper($query); 83 | $deviceView->setTabletView(); 84 | 85 | Assert::notEqual(Helpers\DeviceView::VIEW_MOBILE, $deviceView->getViewType()); 86 | } 87 | 88 | /** 89 | * Create DeviceView helper 90 | * 91 | * @param array $query 92 | * 93 | * @return Helpers\DeviceView 94 | */ 95 | private function getHelper($query = []) : Helpers\DeviceView 96 | { 97 | $url = new Nette\Http\Url('https://www.ipublikuj.eu'); 98 | $url->setQuery($query); 99 | 100 | $urlScript = new Nette\Http\UrlScript($url); 101 | 102 | $httpRequest = new Nette\Http\Request($urlScript); 103 | $httpResponse = new Nette\Http\Response(); 104 | 105 | $cookieSettings = new Helpers\CookieSettings('device_view', NULL, '+1 month', '/', false, true); 106 | 107 | // Get helper 108 | return new Helpers\DeviceView('device_view', $cookieSettings, $httpRequest, $httpResponse); 109 | } 110 | } 111 | 112 | \run(new DeviceViewTest()); 113 | -------------------------------------------------------------------------------- /tests/IPubTests/MobileDetect/MobileDetectTest.phpt: -------------------------------------------------------------------------------- 1 | 9 | * @package iPublikuj:MobileDetect! 10 | * @subpackage Tests 11 | * @since 1.0.0 12 | * 13 | * @date 10.01.15 14 | */ 15 | 16 | declare(strict_types = 1); 17 | 18 | namespace IPubTests\MobileDetect; 19 | 20 | use Nette; 21 | 22 | use Tester; 23 | use Tester\Assert; 24 | 25 | use IPub\MobileDetect; 26 | 27 | require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'bootstrap.php'; 28 | 29 | class MobileDetectTest extends Tester\TestCase 30 | { 31 | /** 32 | * @var MobileDetect\MobileDetect 33 | */ 34 | private $mobileDetector; 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function setUp() : void 40 | { 41 | parent::setUp(); 42 | 43 | $dic = $this->createContainer(); 44 | 45 | // Get extension services 46 | $this->mobileDetector = $dic->getService('mobileDetect.mobileDetect'); 47 | } 48 | 49 | public function testBasicMethods() : void 50 | { 51 | $this->mobileDetector->setHttpHeaders([ 52 | 'SERVER_SOFTWARE' => 'Apache/2.2.15 (Linux) Whatever/4.0 PHP/5.2.13', 53 | 'REQUEST_METHOD' => 'POST', 54 | 'HTTP_HOST' => 'home.ghita.org', 55 | 'HTTP_X_REAL_IP' => '1.2.3.4', 56 | 'HTTP_X_FORWARDED_FOR' => '1.2.3.5', 57 | 'HTTP_CONNECTION' => 'close', 58 | 'HTTP_USER_AGENT' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0_1 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A523 Safari/8536.25', 59 | 'HTTP_ACCEPT' => 'text/vnd.wap.wml, application/json, text/javascript, */*; q=0.01', 60 | 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', 61 | 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', 62 | 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest', 63 | 'HTTP_REFERER' => 'http://ipublikuj.eu', 64 | 'HTTP_PRAGMA' => 'no-cache', 65 | 'HTTP_CACHE_CONTROL' => 'no-cache', 66 | 'REMOTE_ADDR' => '11.22.33.44', 67 | 'REQUEST_TIME' => '01-10-2012 07:57' 68 | ]); 69 | 70 | Assert::true($this->mobileDetector->isMobile()); 71 | Assert::false($this->mobileDetector->isTablet()); 72 | 73 | Assert::false($this->mobileDetector->isiphone()); 74 | Assert::false($this->mobileDetector->isiOS()); 75 | 76 | $this->mobileDetector->setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 6_0_1 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A523 Safari/8536.25'); 77 | 78 | Assert::true($this->mobileDetector->isiphone()); 79 | Assert::true($this->mobileDetector->isiOS()); 80 | Assert::true($this->mobileDetector->isios()); 81 | Assert::true($this->mobileDetector->is('iphone')); 82 | Assert::true($this->mobileDetector->is('ios')); 83 | } 84 | 85 | /** 86 | * @return Nette\DI\Container 87 | */ 88 | protected function createContainer() : Nette\DI\Container 89 | { 90 | $config = new Nette\Configurator(); 91 | $config->setTempDirectory(TEMP_DIR); 92 | 93 | MobileDetect\DI\MobileDetectExtension::register($config); 94 | 95 | $config->addConfig(__DIR__ . DS . 'files' . DS . 'config.neon'); 96 | 97 | return $config->createContainer(); 98 | } 99 | } 100 | 101 | \run(new MobileDetectTest()); 102 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/Templating/Helpers.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Templating 10 | * @since 1.0.0 11 | * 12 | * @date 16.06.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Templating; 18 | 19 | use Nette; 20 | 21 | use IPub\MobileDetect; 22 | 23 | /** 24 | * Mobile detect template helpers 25 | * 26 | * @package iPublikuj:MobileDetect! 27 | * @subpackage Templating 28 | * 29 | * @author Adam Kadlec 30 | */ 31 | final class Helpers 32 | { 33 | /** 34 | * Implement nette smart magic 35 | */ 36 | use Nette\SmartObject; 37 | 38 | /** 39 | * @var MobileDetect\MobileDetect 40 | */ 41 | private $mobileDetect; 42 | 43 | /** 44 | * @var MobileDetect\Helpers\DeviceView 45 | */ 46 | private $deviceView; 47 | 48 | /** 49 | * @param MobileDetect\MobileDetect $mobileDetect 50 | * @param MobileDetect\Helpers\DeviceView $deviceView 51 | */ 52 | public function __construct( 53 | MobileDetect\MobileDetect $mobileDetect, 54 | MobileDetect\Helpers\DeviceView $deviceView 55 | ) { 56 | $this->mobileDetect = $mobileDetect; 57 | $this->deviceView = $deviceView; 58 | } 59 | 60 | /** 61 | * @return MobileDetect\MobileDetect 62 | */ 63 | public function getMobileDetectService() : MobileDetect\MobileDetect 64 | { 65 | return $this->mobileDetect; 66 | } 67 | 68 | /** 69 | * @return MobileDetect\Helpers\DeviceView 70 | */ 71 | public function getDeviceViewService() : MobileDetect\Helpers\DeviceView 72 | { 73 | return $this->deviceView; 74 | } 75 | 76 | /** 77 | * @return bool 78 | */ 79 | public function isMobile() : bool 80 | { 81 | return $this->mobileDetect->isMobile(); 82 | } 83 | 84 | /** 85 | * @return bool 86 | */ 87 | public function isPhone() : bool 88 | { 89 | return $this->mobileDetect->isPhone(); 90 | } 91 | 92 | /** 93 | * @return bool 94 | */ 95 | public function isTablet() : bool 96 | { 97 | return $this->mobileDetect->isTablet(); 98 | } 99 | 100 | /** 101 | * @param string $device 102 | * 103 | * @return bool 104 | */ 105 | public function isDevice(string $device) : bool 106 | { 107 | return $this->mobileDetect->is($device); 108 | } 109 | 110 | /** 111 | * @param string $os 112 | * 113 | * @return bool 114 | */ 115 | public function isOs(string $os) : bool 116 | { 117 | return $this->mobileDetect->is($os); 118 | } 119 | 120 | /** 121 | * @return bool 122 | */ 123 | public function isFullView() : bool 124 | { 125 | return $this->deviceView->isFullView(); 126 | } 127 | 128 | /** 129 | * @return bool 130 | */ 131 | public function isMobileView() : bool 132 | { 133 | return $this->deviceView->isMobileView(); 134 | } 135 | 136 | /** 137 | * @return bool 138 | */ 139 | public function isPhoneView() : bool 140 | { 141 | return $this->deviceView->isPhoneView(); 142 | } 143 | 144 | /** 145 | * @return bool 146 | */ 147 | public function isTabletView() : bool 148 | { 149 | return $this->deviceView->isTabletView(); 150 | } 151 | 152 | /** 153 | * @return bool 154 | */ 155 | public function isNotMobileView() : bool 156 | { 157 | return $this->deviceView->isNotMobileView(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/Helpers/CookieSettings.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Helpers 10 | * @since 1.0.0 11 | * 12 | * @date 23.04.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Helpers; 18 | 19 | use Nette; 20 | 21 | use IPub\MobileDetect\Exceptions; 22 | 23 | /** 24 | * Cookies creator helper 25 | * 26 | * @package iPublikuj:MobileDetect! 27 | * @subpackage Helpers 28 | * 29 | * @author Adam Kadlec 30 | */ 31 | final class CookieSettings 32 | { 33 | /** 34 | * Implement nette smart magic 35 | */ 36 | use Nette\SmartObject; 37 | 38 | /** 39 | * @var string 40 | */ 41 | private $name; 42 | 43 | /** 44 | * @var string|NULL 45 | */ 46 | private $domain; 47 | 48 | /** 49 | * @var string 50 | */ 51 | private $expire; 52 | 53 | /** 54 | * @var string 55 | */ 56 | private $path; 57 | 58 | /** 59 | * @var bool 60 | */ 61 | private $secure; 62 | 63 | /** 64 | * @var bool 65 | */ 66 | private $httpOnly; 67 | 68 | /** 69 | * @param string $name The name of the cookie 70 | * @param string $expireAfter The time the cookie expires 71 | * @param string $path The path on the server in which the cookie will be available on 72 | * @param string $domain The domain that the cookie is available to 73 | * @param bool $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client 74 | * @param bool $httpOnly Whether the cookie will be made accessible only through the HTTP protocol 75 | * 76 | * @throws Exceptions\InvalidArgumentException 77 | */ 78 | public function __construct(string $name, ?string $domain = null, ?string $expireAfter = null, string $path = '/', bool $secure = false, bool $httpOnly = true) 79 | { 80 | // from PHP source code 81 | if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { 82 | throw new Exceptions\InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); 83 | } 84 | 85 | if (empty($name)) { 86 | throw new Exceptions\InvalidArgumentException('The cookie name cannot be empty.'); 87 | } 88 | 89 | $expire = new \DateTime; 90 | 91 | if ($expireAfter !== null) { 92 | $expire->modify($expireAfter); 93 | } 94 | 95 | $this->name = $name; 96 | $this->domain = $domain; 97 | $this->expire = (int) $expire->format('U'); 98 | $this->path = empty($path) ? '/' : $path; 99 | $this->secure = $secure; 100 | $this->httpOnly = $httpOnly; 101 | } 102 | 103 | /** 104 | * Gets the name of the cookie 105 | * 106 | * @return string 107 | */ 108 | public function getName() : string 109 | { 110 | return $this->name; 111 | } 112 | 113 | /** 114 | * Gets the domain that the cookie is available to 115 | * 116 | * @return string|NULL 117 | */ 118 | public function getDomain() : ?string 119 | { 120 | return $this->domain; 121 | } 122 | 123 | /** 124 | * Gets the time the cookie expires 125 | * 126 | * @return int 127 | */ 128 | public function getExpiresTime() : int 129 | { 130 | return $this->expire; 131 | } 132 | 133 | /** 134 | * Gets the path on the server in which the cookie will be available on 135 | * 136 | * @return string 137 | */ 138 | public function getPath() : string 139 | { 140 | return $this->path; 141 | } 142 | 143 | /** 144 | * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client 145 | * 146 | * @return bool 147 | */ 148 | public function isSecure() : bool 149 | { 150 | return $this->secure; 151 | } 152 | 153 | /** 154 | * Checks whether the cookie will be made accessible only through the HTTP protocol 155 | * 156 | * @return bool 157 | */ 158 | public function isHttpOnly() : bool 159 | { 160 | return $this->httpOnly; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/Diagnostics/Panel.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Diagnostics 10 | * @since 1.0.0 11 | * 12 | * @date 21.04.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Diagnostics; 18 | 19 | use Nette; 20 | 21 | use Tracy; 22 | 23 | use IPub\MobileDetect; 24 | 25 | /** 26 | * Mobile device detect tracy panel 27 | * 28 | * @package iPublikuj:MobileDetect! 29 | * @subpackage Diagnostics 30 | * 31 | * @author Václav Pelíšek 32 | */ 33 | final class Panel implements Tracy\IBarPanel 34 | { 35 | /** 36 | * Implement nette smart magic 37 | */ 38 | use Nette\SmartObject; 39 | 40 | /** @var MobileDetect\MobileDetect 41 | */ 42 | private $mobileDetect; 43 | 44 | /** 45 | * @param MobileDetect\MobileDetect $mobileDetect 46 | * 47 | * @return self 48 | */ 49 | public function register(MobileDetect\MobileDetect $mobileDetect) : self 50 | { 51 | $this->mobileDetect = $mobileDetect; 52 | 53 | Tracy\Debugger::getBar()->addPanel($this, 'ipub.mobileDetect'); 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * Renders HTML code for custom tab 60 | * 61 | * @return string 62 | */ 63 | public function getTab() 64 | { 65 | return '' 66 | . $this->mobileDetect->view() . ' / ' . $this->mobileDetect->platform() . ''; 67 | } 68 | 69 | /** 70 | * Renders HTML code for custom panel 71 | * 72 | * @return string 73 | */ 74 | public function getPanel() 75 | { 76 | $h = 'htmlSpecialChars'; 77 | 78 | $panel = []; 79 | 80 | $panel[] = '

Original User agent header

'; 81 | $panel[] = '

' . $h($this->mobileDetect->getUserAgent()) . '

'; 82 | 83 | $properties = ['view', 'platform', 'platformVersion', 'device', 'browser', 'browserVersion']; 84 | 85 | $panel[] = ''; 86 | foreach ($properties as $property) { 87 | $panel[] = ''; 88 | } 89 | $panel[] = '
' . ucfirst($property) . '' . $h($this->mobileDetect->{$property}() ?: '') . '
'; 90 | 91 | return empty($panel) ? '' : 92 | '

View: ' . $this->mobileDetect->view() . ', OS: ' . $this->mobileDetect->platform() . '

' . 93 | '
' . implode($panel) . '
' . 94 | ''; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/IPubTests/MobileDetect/TemplateTest.phpt: -------------------------------------------------------------------------------- 1 | 9 | * @package iPublikuj:MobileDetect! 10 | * @subpackage Tests 11 | * @since 1.0.0 12 | * 13 | * @date 15.01.17 14 | */ 15 | 16 | declare(strict_types = 1); 17 | 18 | namespace IPubTests\MobileDetect; 19 | 20 | use Nette; 21 | use Nette\Application; 22 | use Nette\Application\Routers; 23 | use Nette\Application\UI; 24 | use Nette\Utils; 25 | 26 | use Tester; 27 | use Tester\Assert; 28 | 29 | use IPub\MobileDetect; 30 | use IPub\MobileDetect\Helpers; 31 | 32 | require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'bootstrap.php'; 33 | require __DIR__ . DS . 'libs' . DS . 'RouterFactory.php'; 34 | 35 | class TemplateTest extends Tester\TestCase 36 | { 37 | /** 38 | * @var Application\IPresenterFactory 39 | */ 40 | private $presenterFactory; 41 | 42 | /** 43 | * @var Nette\DI\Container 44 | */ 45 | private $container; 46 | 47 | /** 48 | * @var MobileDetect\MobileDetect 49 | */ 50 | private $mobileDetector; 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function setUp() : void 56 | { 57 | parent::setUp(); 58 | 59 | $this->container = $this->createContainer(); 60 | 61 | // Get presenter factory from container 62 | $this->presenterFactory = $this->container->getByType(Application\IPresenterFactory::class); 63 | 64 | // Get device view service 65 | $this->mobileDetector = $this->container->getByType(MobileDetect\MobileDetect::class); 66 | 67 | $this->mobileDetector->setHttpHeaders([ 68 | 'SERVER_SOFTWARE' => 'Apache/2.2.15 (Linux) Whatever/4.0 PHP/5.2.13', 69 | 'REQUEST_METHOD' => 'POST', 70 | 'HTTP_HOST' => 'home.ghita.org', 71 | 'HTTP_X_REAL_IP' => '1.2.3.4', 72 | 'HTTP_X_FORWARDED_FOR' => '1.2.3.5', 73 | 'HTTP_CONNECTION' => 'close', 74 | 'HTTP_USER_AGENT' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0_1 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A523 Safari/8536.25', 75 | 'HTTP_ACCEPT' => 'text/vnd.wap.wml, application/json, text/javascript, */*; q=0.01', 76 | 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', 77 | 'HTTP_ACCEPT_ENCODING' => 'gzip, deflate', 78 | 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest', 79 | 'HTTP_REFERER' => 'http://ipublikuj.eu', 80 | 'HTTP_PRAGMA' => 'no-cache', 81 | 'HTTP_CACHE_CONTROL' => 'no-cache', 82 | 'REMOTE_ADDR' => '11.22.33.44', 83 | 'REQUEST_TIME' => '01-10-2012 07:57' 84 | ]); 85 | } 86 | 87 | public function testMobileVersion() : void 88 | { 89 | // Create test presenter 90 | $presenter = $this->createPresenter(); 91 | 92 | // Create GET request 93 | $request = new Application\Request('Test', 'GET', ['action' => 'default']); 94 | // & fire presenter & catch response 95 | $response = $presenter->run($request); 96 | 97 | $dq = Tester\DomQuery::fromHtml((string) $response->getSource()); 98 | 99 | Assert::true($dq->has('div[id*="mobileDevice"]')); 100 | Assert::true($dq->has('div[id*="phoneDevice"]')); 101 | Assert::false($dq->has('div[id*="tableDevice"]')); 102 | } 103 | 104 | public function testPhoneVersion() : void 105 | { 106 | $this->mobileDetector->setUserAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 6_0_1 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A523 Safari/8536.25'); 107 | 108 | // Create test presenter 109 | $presenter = $this->createPresenter(); 110 | 111 | // Create GET request 112 | $request = new Application\Request('Test', 'GET', ['action' => 'default']); 113 | // & fire presenter & catch response 114 | $response = $presenter->run($request); 115 | 116 | $dq = Tester\DomQuery::fromHtml((string) $response->getSource()); 117 | 118 | Assert::true($dq->has('div[id*="mobileDevice"]')); 119 | Assert::true($dq->has('div[id*="phoneDevice"]')); 120 | Assert::true($dq->has('div[id*="mobileDeviceType"]')); 121 | Assert::true($dq->has('div[id*="mobileDeviceOs"]')); 122 | Assert::false($dq->has('div[id*="tableDevice"]')); 123 | } 124 | 125 | /** 126 | * @return Application\IPresenter 127 | */ 128 | private function createPresenter() : Application\IPresenter 129 | { 130 | // Create test presenter 131 | $presenter = $this->presenterFactory->createPresenter('Test'); 132 | // Disable auto canonicalize to prevent redirection 133 | $presenter->autoCanonicalize = FALSE; 134 | 135 | return $presenter; 136 | } 137 | 138 | /** 139 | * @return Nette\DI\Container 140 | */ 141 | private function createContainer() : Nette\DI\Container 142 | { 143 | $config = new Nette\Configurator(); 144 | $config->setTempDirectory(TEMP_DIR); 145 | 146 | MobileDetect\DI\MobileDetectExtension::register($config); 147 | 148 | $config->addConfig(__DIR__ . DS . 'files' . DS . 'config.neon'); 149 | 150 | $version = getenv('NETTE'); 151 | 152 | $config->addConfig(__DIR__ . DS . 'files' . DS . 'presenters.neon'); 153 | 154 | return $config->createContainer(); 155 | } 156 | } 157 | 158 | class TestPresenter extends UI\Presenter 159 | { 160 | use MobileDetect\TMobileDetect; 161 | 162 | public function renderDefault() : void 163 | { 164 | // Set template for component testing 165 | $this->template->setFile(__DIR__ . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . 'default.latte'); 166 | } 167 | } 168 | 169 | \run(new TemplateTest()); 170 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/DI/MobileDetectExtension.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage DI 10 | * @since 1.0.0 11 | * 12 | * @date 21.04.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\DI; 18 | 19 | use Nette; 20 | use Nette\Bridges; 21 | use Nette\DI; 22 | 23 | use Tracy; 24 | 25 | use IPub\MobileDetect; 26 | use IPub\MobileDetect\Diagnostics; 27 | use IPub\MobileDetect\Events; 28 | use IPub\MobileDetect\Helpers; 29 | use IPub\MobileDetect\Templating; 30 | 31 | /** 32 | * Mobile device detect extension container 33 | * 34 | * @package iPublikuj:MobileDetect! 35 | * @subpackage DI 36 | * 37 | * @author Adam Kadlec 38 | */ 39 | final class MobileDetectExtension extends DI\CompilerExtension 40 | { 41 | /** 42 | * @var array 43 | */ 44 | private $defaults = [ 45 | 'redirect' => [ 46 | 'mobile' => [ 47 | 'isEnabled' => false, 48 | 'host' => null, 49 | 'statusCode' => 301, 50 | 'action' => 'noRedirect', // redirect/noRedirect/redirectWithoutPath 51 | ], 52 | 'phone' => [ 53 | 'isEnabled' => false, 54 | 'host' => null, 55 | 'statusCode' => 301, 56 | 'action' => 'noRedirect', // redirect/noRedirect/redirectWithoutPath 57 | ], 58 | 'tablet' => [ 59 | 'isEnabled' => false, 60 | 'host' => null, 61 | 'statusCode' => 301, 62 | 'action' => 'noRedirect', // redirect/noRedirect/redirectWithoutPath 63 | ], 64 | 'detectPhoneAsMobile' => false, 65 | 'detectTabletAsMobile' => false, 66 | ], 67 | 'switchDeviceView' => [ 68 | 'saveRefererPath' => true 69 | ], 70 | 'switchParameterName' => 'device_view', 71 | 'deviceViewCookie' => [ 72 | 'name' => 'device_view', 73 | 'domain' => null, 74 | 'expireAfter' => '+1 month', 75 | 'path' => '/', 76 | 'secure' => false, 77 | 'httpOnly' => true, 78 | ], 79 | 'debugger' => '%debugMode%' 80 | ]; 81 | 82 | /** 83 | * @return void 84 | */ 85 | public function loadConfiguration() : void 86 | { 87 | // Get container builder 88 | $builder = $this->getContainerBuilder(); 89 | 90 | // Set the default configuration 91 | $this->validateConfig($this->defaults); 92 | 93 | // Get extension configuration 94 | $configuration = $this->getConfig(); 95 | 96 | // Install mobile detect service 97 | $mobileDetect = $builder->addDefinition($this->prefix('mobileDetect')) 98 | ->setType(MobileDetect\MobileDetect::class); 99 | 100 | $builder->addDefinition($this->prefix('deviceView')) 101 | ->setType(Helpers\DeviceView::class) 102 | ->setArguments(['setSwitchParameterName' => $configuration['switchParameterName']]); 103 | 104 | $builder->addDefinition($this->prefix('cookieSettings')) 105 | ->setType(Helpers\CookieSettings::class) 106 | ->setArguments([ 107 | 'name' => $configuration['deviceViewCookie']['name'], 108 | 'domain' => $configuration['deviceViewCookie']['domain'], 109 | 'expireAfter' => $configuration['deviceViewCookie']['expireAfter'], 110 | 'path' => $configuration['deviceViewCookie']['path'], 111 | 'secure' => $configuration['deviceViewCookie']['secure'], 112 | 'httpOnly' => $configuration['deviceViewCookie']['httpOnly'], 113 | ]); 114 | 115 | if ($configuration['debugger'] && interface_exists(Tracy\IBarPanel::class)) { 116 | $builder->addDefinition($this->prefix('panel')) 117 | ->setType(Diagnostics\Panel::class); 118 | 119 | $mobileDetect->addSetup('?->register(?)', [$this->prefix('@panel'), '@self']); 120 | } 121 | 122 | $builder->addDefinition($this->prefix('onRequestHandler')) 123 | ->setType(Events\OnRequestHandler::class) 124 | ->addSetup('$redirectConf', [$configuration['redirect']]) 125 | ->addSetup('$isFullPath', [$configuration['switchDeviceView']['saveRefererPath']]); 126 | 127 | $builder->addDefinition($this->prefix('onResponseHandler')) 128 | ->setType(Events\OnResponseHandler::class); 129 | 130 | // Register template helpers 131 | $builder->addDefinition($this->prefix('helpers')) 132 | ->setType(Templating\Helpers::class) 133 | ->setAutowired(FALSE); 134 | 135 | $application = $builder->getDefinition('application'); 136 | $application->addSetup('$service->onRequest[] = ?', ['@' . $this->prefix('onRequestHandler')]); 137 | $application->addSetup('$service->onResponse[] = ?', ['@' . $this->prefix('onResponseHandler')]); 138 | } 139 | 140 | /** 141 | * {@inheritdoc} 142 | */ 143 | public function beforeCompile() : void 144 | { 145 | parent::beforeCompile(); 146 | 147 | $builder = $this->getContainerBuilder(); 148 | 149 | // Install extension latte macros 150 | $latteFactory = $builder->getDefinition($builder->getByType(Bridges\ApplicationLatte\ILatteFactory::class) ?: 'nette.latteFactory'); 151 | 152 | $latteFactory->getResultDefinition() 153 | ->addSetup('IPub\MobileDetect\Latte\Macros::install(?->getCompiler())', ['@self']) 154 | ->addSetup('addFilter', ['isMobile', [$this->prefix('@helpers'), 'isMobile']]) 155 | ->addSetup('addFilter', ['isPhone', [$this->prefix('@helpers'), 'isPhone']]) 156 | ->addSetup('addFilter', ['isTablet', [$this->prefix('@helpers'), 'isTablet']]) 157 | ->addSetup('addFilter', ['isDevice', [$this->prefix('@helpers'), 'isDevice']]) 158 | ->addSetup('addFilter', ['isOs', [$this->prefix('@helpers'), 'isOs']]) 159 | ->addSetup('addFilter', ['isFullView', [$this->prefix('@helpers'), 'isFullView']]) 160 | ->addSetup('addFilter', ['isMobileView', [$this->prefix('@helpers'), 'isMobileView']]) 161 | ->addSetup('addFilter', ['isPhoneView', [$this->prefix('@helpers'), 'isPhoneView']]) 162 | ->addSetup('addFilter', ['isTabletView', [$this->prefix('@helpers'), 'isTabletView']]) 163 | ->addSetup('addFilter', ['isNotMobileView', [$this->prefix('@helpers'), 'isNotMobileView']]) 164 | ->addSetup('addFilter', ['getMobileDetectService', [$this->prefix('@helpers'), 'getMobileDetectService']]) 165 | ->addSetup('addFilter', ['getDeviceViewService', [$this->prefix('@helpers'), 'getDeviceViewService']]); 166 | } 167 | 168 | /** 169 | * @param Nette\Configurator $config 170 | * @param string $extensionName 171 | * 172 | * @return void 173 | */ 174 | public static function register(Nette\Configurator $config, string $extensionName = 'mobileDetect') : void 175 | { 176 | $config->onCompile[] = function (Nette\Configurator $config, Nette\DI\Compiler $compiler) use ($extensionName) : void { 177 | $compiler->addExtension($extensionName, new MobileDetectExtension()); 178 | }; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /docs/en/index.md: -------------------------------------------------------------------------------- 1 | # MobileDetect 2 | 3 | Extension for detecting mobile devices, managing mobile view types, redirect to mobile version for [Nette Framework](http://nette.org/) 4 | 5 | ## Introduction 6 | 7 | This extension use [Mobile_Detect](https://github.com/serbanghita/Mobile-Detect) class and provides the following features: 8 | 9 | * Detect the various mobile devices by name, OS, browser User-Agent 10 | * Manages site views for the variuos mobile devices (`mobile`, `phone`, `tablet`, `full`) 11 | * Redirects to mobile and tablet sites 12 | 13 | ## Installation 14 | 15 | The best way to install ipub/mobile-detect is using [Composer](http://getcomposer.org/): 16 | 17 | ```sh 18 | $ composer require ipub/mobile-detect 19 | ``` 20 | 21 | After that you have to register extension in config.neon. 22 | 23 | ```neon 24 | extensions: 25 | mobileDetect: IPub\MobileDetect\DI\MobileDetectExtension 26 | ``` 27 | 28 | Package contains trait, which you will have to use in class, where you want to use mobile detector. 29 | 30 | ```php 31 | redirectUrl = http://site.com/current/path 74 | # false => redirectUrl = http://site.com 75 | ``` 76 | 77 | ## Usage in PHP files 78 | 79 | ### Switch device view 80 | 81 | For switch device view, use `device_view` GET parameter: 82 | 83 | ```` 84 | http://site.com?device_view={full/mobile/phone/tablet} 85 | ```` 86 | 87 | ### How to use in presenter etc. 88 | 89 | In presenters or other services where you import mobile detector you could create calls like this 90 | 91 | ```php 92 | class SomePresenter extends Nette\Application\UI\Presenter 93 | { 94 | /** 95 | * @var \IPub\MobileDetect\MobileDetect 96 | */ 97 | protected $mobileDetect 98 | 99 | /** 100 | * Some action with mobile detection 101 | */ 102 | public function someAction() 103 | { 104 | if ($this->mobileDetect->isMobile()) { 105 | //...do whatever 106 | } 107 | } 108 | } 109 | ``` 110 | 111 | ### Check type device 112 | 113 | ```php 114 | $mobileDetect->isMobile(); 115 | $mobileDetect->isTablet(); 116 | $mobileDetect->isPhone(); 117 | ``` 118 | 119 | ### Check phone 120 | 121 | is[iPhone|HTC|Nexus|Dell|Motorola|Samsung|Sony|Asus|Palm|Vertu|GenericPhone] 122 | 123 | ```php 124 | $mobileDetect->isIphone(); 125 | $mobileDetect->isHTC(); 126 | // etc. 127 | ``` 128 | 129 | ### Check tablet 130 | 131 | is[BlackBerryTablet|iPad|Kindle|SamsungTablet|HTCtablet|MotorolaTablet|AsusTablet|NookTablet|AcerTablet| YarvikTablet|GenericTablet] 132 | 133 | ```php 134 | $mobileDetect->isIpad(); 135 | $mobileDetect->isMotorolaTablet(); 136 | // etc. 137 | ``` 138 | 139 | ### Check mobile OS 140 | 141 | is[AndroidOS|BlackBerryOS|PalmOS|SymbianOS|WindowsMobileOS|iOS|badaOS] 142 | 143 | ```php 144 | $mobileDetect->isAndroidOS(); 145 | $mobileDetect->isIOS(); 146 | // etc. 147 | ``` 148 | 149 | ### Check mobile browser User-Agent 150 | 151 | is[Chrome|Dolfin|Opera|Skyfire|IE|Firefox|Bolt|TeaShark|Blazer|Safari|Midori|GenericBrowser] 152 | 153 | ```php 154 | $mobileDetect->isChrome(); 155 | $mobileDetect->isSafari(); 156 | // etc. 157 | ``` 158 | 159 | ## Using in Latte 160 | 161 | ### Check device type 162 | 163 | ```html 164 | {isMobile} 165 | This content will be only on mobile devices.... 166 | {/isMobile} 167 | 168 | {isTablet} 169 | This content will be only on tablet devices.... 170 | {/isTablet} 171 | 172 | {isPhone} 173 | This content will be only on phone devices.... 174 | {/isPhone} 175 | ``` 176 | 177 | Available Latte macros: 178 | 179 | ```html 180 | {isMobile}....{/isMobile} 181 | {isNotMobile}....{/isNotMobile} 182 | 183 | {isTablet}....{/isTablet} 184 | {isNotTablet}....{/isNotTablet} 185 | 186 | {isPhone}....{/isPhone} 187 | {isNotPhone}....{/isNotPhone} 188 | ``` 189 | 190 | ### Check device type by provided name 191 | 192 | ```html 193 | {isMobileDevice iPhone} 194 | This content will be only on Apple iPhone devices.... 195 | {/isMobileDevice} 196 | 197 |
198 | This content will be only on Apple iPhone devices.... 199 |
200 | ``` 201 | 202 | ### Check device OS by provided name 203 | 204 | ```html 205 | {isMobileOs iOS} 206 | This content will be only on mobile devices with iOS operating system.... 207 | {/isMobileOs} 208 | 209 |
210 | This content will be only on mobile devices with iOS operating system.... 211 |
212 | ``` 213 | 214 | ### Check view type set by helper 215 | 216 | With view type detector you could change your default layout in templates. 217 | 218 | ```html 219 | {isMobileView} 220 | {layout '../Path/To/Your/Mobile/Device/@layout.latte'} 221 | {/isMobileView} 222 | 223 | {isTabletView} 224 | {layout '../Path/To/Your/Tablet/Device/@layout.latte'} 225 | {/isTabletView} 226 | 227 | {isPhoneView} 228 | {layout '../Path/To/Your/Phone/Device/@layout.latte'} 229 | {/isPhoneView} 230 | 231 | {isFullView} 232 | {layout '../Path/To/Your/Full/View/@layout.latte'} 233 | {/isFullView} 234 | 235 | {isNotMobileView} 236 | {layout '../Path/To/Your/Not/Mobile/Device/@layout.latte'} 237 | {/isNotMobileView} 238 | ``` 239 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/Helpers/DeviceView.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Helpers 10 | * @since 1.0.0 11 | * 12 | * @date 23.04.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Helpers; 18 | 19 | use Nette; 20 | use Nette\Application; 21 | use Nette\Http; 22 | 23 | /** 24 | * Device view detector helper 25 | * 26 | * @package iPublikuj:MobileDetect! 27 | * @subpackage Helpers 28 | * 29 | * @author Adam Kadlec 30 | */ 31 | final class DeviceView 32 | { 33 | /** 34 | * Implement nette smart magic 35 | */ 36 | use Nette\SmartObject; 37 | 38 | public const VIEW_MOBILE = 'mobile'; 39 | public const VIEW_PHONE = 'phone'; 40 | public const VIEW_TABLET = 'tablet'; 41 | public const VIEW_FULL = 'full'; 42 | public const VIEW_NOT_MOBILE = 'not_mobile'; 43 | 44 | /** 45 | * @var Http\IRequest 46 | */ 47 | private $httpRequest; 48 | 49 | /** 50 | * @var Http\IResponse 51 | */ 52 | private $httpResponse; 53 | 54 | /** 55 | * @var string 56 | */ 57 | private $viewType = self::VIEW_FULL; 58 | 59 | /** 60 | * @var CookieSettings 61 | */ 62 | private $cookieSettings; 63 | 64 | /** 65 | * @var string 66 | */ 67 | private $switchParameterName = 'device_view'; 68 | 69 | /** 70 | * @param string $setSwitchParameterName 71 | * @param CookieSettings $cookieSettings 72 | * @param Http\IRequest $httpRequest 73 | * @param Http\IResponse $httpResponse 74 | */ 75 | public function __construct(string $setSwitchParameterName, CookieSettings $cookieSettings, Http\IRequest $httpRequest, Http\IResponse $httpResponse) 76 | { 77 | $this->cookieSettings = $cookieSettings; 78 | $this->httpRequest = $httpRequest; 79 | $this->httpResponse = $httpResponse; 80 | 81 | $this->switchParameterName = $setSwitchParameterName; 82 | 83 | if ($this->httpRequest->getQuery($this->switchParameterName) ?? false) { 84 | $this->viewType = $this->httpRequest->getQuery($this->switchParameterName); 85 | 86 | } elseif ($this->httpRequest->getCookie($this->cookieSettings->getName())) { 87 | $this->viewType = $this->httpRequest->getCookie($this->cookieSettings->getName()); 88 | } 89 | } 90 | 91 | /** 92 | * Gets the view type for a device 93 | * 94 | * @return string 95 | */ 96 | public function getViewType() : string 97 | { 98 | return $this->viewType; 99 | } 100 | 101 | /** 102 | * Is the device in full view 103 | * 104 | * @return bool 105 | */ 106 | public function isFullView() : bool 107 | { 108 | return $this->viewType === self::VIEW_FULL; 109 | } 110 | 111 | /** 112 | * Is the device a tablet view type 113 | * 114 | * @return bool 115 | */ 116 | public function isTabletView() : bool 117 | { 118 | return $this->viewType === self::VIEW_TABLET; 119 | } 120 | 121 | /** 122 | * Is the device a phone view type 123 | * 124 | * @return bool 125 | */ 126 | public function isPhoneView() : bool 127 | { 128 | return $this->viewType === self::VIEW_PHONE; 129 | } 130 | 131 | /** 132 | * Is the device a mobile view type 133 | * 134 | * @return bool 135 | */ 136 | public function isMobileView() : bool 137 | { 138 | return $this->viewType === self::VIEW_MOBILE || $this->isPhoneView() || $this->isTabletView(); 139 | } 140 | 141 | /** 142 | * Is not the device a mobile view type (PC, Mac, etc.) 143 | * 144 | * @return bool 145 | */ 146 | public function isNotMobileView() : bool 147 | { 148 | return $this->viewType === self::VIEW_NOT_MOBILE; 149 | } 150 | 151 | /** 152 | * Sets the tablet view type 153 | * 154 | * @return void 155 | */ 156 | public function setTabletView() : void 157 | { 158 | $this->viewType = self::VIEW_TABLET; 159 | } 160 | 161 | /** 162 | * Sets the phone view type 163 | * 164 | * @return void 165 | */ 166 | public function setPhoneView() : void 167 | { 168 | $this->viewType = self::VIEW_PHONE; 169 | } 170 | 171 | /** 172 | * Sets the mobile view type 173 | * 174 | * @return void 175 | */ 176 | public function setMobileView() : void 177 | { 178 | $this->viewType = self::VIEW_MOBILE; 179 | } 180 | 181 | /** 182 | * Sets the not mobile view type 183 | * 184 | * @return void 185 | */ 186 | public function setNotMobileView() : void 187 | { 188 | $this->viewType = self::VIEW_NOT_MOBILE; 189 | } 190 | 191 | /** 192 | * @return string 193 | */ 194 | public function getSwitchParameterName() : string 195 | { 196 | return $this->switchParameterName; 197 | } 198 | 199 | /** 200 | * Gets the switch param value from the query string (GET header) 201 | * 202 | * @return string 203 | */ 204 | public function getSwitchParameterValue() : string 205 | { 206 | return $this->httpRequest->getQuery($this->switchParameterName) ?? self::VIEW_FULL; 207 | } 208 | 209 | /** 210 | * Has the Request the switch param in the query string (GET header) 211 | * 212 | * @return bool 213 | */ 214 | public function hasSwitchParameter() : bool 215 | { 216 | return ($this->httpRequest->getQuery($this->switchParameterName) ?? false) ? true : false; 217 | } 218 | 219 | /** 220 | * Gets the RedirectResponse by switch param value 221 | * 222 | * @param string $redirectUrl 223 | * 224 | * @return Application\Responses\RedirectResponse 225 | */ 226 | public function getRedirectResponseBySwitchParam(string $redirectUrl) : Application\Responses\RedirectResponse 227 | { 228 | $statusCode = 302; 229 | 230 | switch ($this->getSwitchParameterValue()) { 231 | case self::VIEW_MOBILE: 232 | $this->createCookie(self::VIEW_MOBILE); 233 | break; 234 | 235 | case self::VIEW_PHONE: 236 | $this->createCookie(self::VIEW_PHONE); 237 | break; 238 | 239 | case self::VIEW_TABLET: 240 | $this->createCookie(self::VIEW_TABLET); 241 | break; 242 | 243 | default: 244 | $this->createCookie(self::VIEW_FULL); 245 | break; 246 | } 247 | 248 | return new Application\Responses\RedirectResponse($redirectUrl, $statusCode); 249 | } 250 | 251 | /** 252 | * Modifies the Response for non-mobile devices 253 | * 254 | * @return Http\IResponse 255 | */ 256 | public function modifyNotMobileResponse() : Http\IResponse 257 | { 258 | // Create cookie 259 | $this->createCookie(self::VIEW_NOT_MOBILE); 260 | 261 | return $this->httpResponse; 262 | } 263 | 264 | /** 265 | * Modifies the Response for tablet devices 266 | * 267 | * @return Http\IResponse 268 | */ 269 | public function modifyTabletResponse() : Http\IResponse 270 | { 271 | // Create cookie 272 | $this->createCookie(self::VIEW_TABLET); 273 | 274 | return $this->httpResponse; 275 | } 276 | 277 | /** 278 | * Modifies the Response for phone devices 279 | * 280 | * @return Http\IResponse 281 | */ 282 | public function modifyPhoneResponse() : Http\IResponse 283 | { 284 | // Create cookie 285 | $this->createCookie(self::VIEW_PHONE); 286 | 287 | return $this->httpResponse; 288 | } 289 | 290 | /** 291 | * Modifies the Response for mobile devices 292 | * 293 | * @return Http\IResponse 294 | */ 295 | public function modifyMobileResponse() : Http\IResponse 296 | { 297 | // Create cookie 298 | $this->createCookie(self::VIEW_MOBILE); 299 | 300 | return $this->httpResponse; 301 | } 302 | 303 | /** 304 | * Gets the RedirectResponse for phone devices 305 | * 306 | * @param string $host Uri host 307 | * @param int $statusCode Status code 308 | * 309 | * @return Application\Responses\RedirectResponse 310 | */ 311 | public function getPhoneRedirectResponse(string $host, int $statusCode) : Application\Responses\RedirectResponse 312 | { 313 | // Create cookie 314 | $this->createCookie(self::VIEW_PHONE); 315 | 316 | return new Application\Responses\RedirectResponse($host, $statusCode); 317 | } 318 | 319 | /** 320 | * Gets the RedirectResponse for tablet devices 321 | * 322 | * @param string $host Uri host 323 | * @param int $statusCode Status code 324 | * 325 | * @return Application\Responses\RedirectResponse 326 | */ 327 | public function getTabletRedirectResponse(string $host, int $statusCode) : Application\Responses\RedirectResponse 328 | { 329 | // Create cookie 330 | $this->createCookie(self::VIEW_TABLET); 331 | 332 | return new Application\Responses\RedirectResponse($host, $statusCode); 333 | } 334 | 335 | /** 336 | * Gets the RedirectResponse for mobile devices 337 | * 338 | * @param string $host Uri host 339 | * @param int $statusCode Status code 340 | * 341 | * @return Application\Responses\RedirectResponse 342 | */ 343 | public function getMobileRedirectResponse(string $host, int $statusCode) : Application\Responses\RedirectResponse 344 | { 345 | // Create cookie 346 | $this->createCookie(self::VIEW_MOBILE); 347 | 348 | return new Application\Responses\RedirectResponse($host, $statusCode); 349 | } 350 | 351 | /** 352 | * Gets the cookie 353 | * 354 | * @param string $cookieValue 355 | * 356 | * @return void 357 | */ 358 | private function createCookie(string $cookieValue) : void 359 | { 360 | // Store cookie in response 361 | $this->httpResponse->setCookie( 362 | $this->cookieSettings->getName(), 363 | $cookieValue, 364 | $this->cookieSettings->getExpiresTime(), 365 | $this->cookieSettings->getPath(), 366 | $this->cookieSettings->getDomain(), 367 | $this->cookieSettings->isSecure(), 368 | $this->cookieSettings->isHttpOnly() 369 | ); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/Latte/Macros.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Latte 10 | * @since 1.0.0 11 | * 12 | * @date 22.04.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Latte; 18 | 19 | use Latte\Compiler; 20 | use Latte\MacroNode; 21 | use Latte\PhpWriter; 22 | use Latte\Macros\MacroSet; 23 | 24 | use IPub\MobileDetect\Exceptions; 25 | 26 | /** 27 | * Mobile detect latte macros definition 28 | * 29 | * @package iPublikuj:MobileDetect! 30 | * @subpackage Latte 31 | * 32 | * @author Adam Kadlec 33 | */ 34 | final class Macros extends MacroSet 35 | { 36 | /** 37 | * Register latte macros 38 | * 39 | * @param Compiler $compiler 40 | * 41 | * @return static 42 | */ 43 | public static function install(Compiler $compiler) 44 | { 45 | $me = new static($compiler); 46 | 47 | /** 48 | * {isMobile /}, {isNotMobile /} 49 | */ 50 | $me->addMacro('isMobile', [$me, 'macroIsMobile'], '}'); 51 | $me->addMacro('isNotMobile', [$me, 'macroIsNotMobile'], '}'); 52 | 53 | /** 54 | * {isPhone /}, {isNotPhone /} 55 | */ 56 | $me->addMacro('isPhone', [$me, 'macroIsPhone'], '}'); 57 | $me->addMacro('isNotPhone', [$me, 'macroIsNotPhone'], '}'); 58 | 59 | /** 60 | * {isTablet /}, {isNotTablet /} 61 | */ 62 | $me->addMacro('isTablet', [$me, 'macroIsTablet'], '}'); 63 | $me->addMacro('isNotTablet', [$me, 'macroIsNotTablet'], '}'); 64 | 65 | /** 66 | * {isMobileDevice 'device_name'} 67 | */ 68 | $me->addMacro('isMobileDevice', [$me, 'macroIsDevice'], '}'); 69 | 70 | /** 71 | * {isMobileOs 'device_name'} 72 | */ 73 | $me->addMacro('isMobileOs', [$me, 'macroIsOS'], '}'); 74 | 75 | /** 76 | * {isFullView /}, {isMobileView /}, {isTabletView /}, {isNotMobileView /} 77 | */ 78 | $me->addMacro('isFullView', [$me, 'macroIsFullView'], '}'); 79 | $me->addMacro('isMobileView', [$me, 'macroIsMobileView'], '}'); 80 | $me->addMacro('isPhoneView', [$me, 'macroIsPhoneView'], '}'); 81 | $me->addMacro('isTabletView', [$me, 'macroIsTabletView'], '}'); 82 | $me->addMacro('isNotMobileView', [$me, 'macroIsNotMobileView'], '}'); 83 | 84 | return $me; 85 | } 86 | 87 | /** 88 | * {isMobile /} 89 | * 90 | * @param MacroNode $node 91 | * @param PhpWriter $writer 92 | * 93 | * @return string 94 | */ 95 | public function macroIsMobile(MacroNode $node, PhpWriter $writer) : string 96 | { 97 | return $writer->write(' 98 | $_resultMD = property_exists($this, "filters") ? %escape(call_user_func($this->filters->isMobile)) : $template->getMobileDetectService()->isMobile() && !$template->getMobileDetectService()->isTablet(); 99 | if ($_resultMD) { 100 | '); 101 | } 102 | 103 | /** 104 | * {isNotMobile /} 105 | * 106 | * @param MacroNode $node 107 | * @param PhpWriter $writer 108 | * 109 | * @return string 110 | */ 111 | public function macroIsNotMobile(MacroNode $node, PhpWriter $writer) : string 112 | { 113 | return $writer->write(' 114 | $_resultMD = property_exists($this, "filters") ? %escape(!call_user_func($this->filters->isMobile)) : !$template->getMobileDetectService()->isMobile(); 115 | if ($_resultMD) { 116 | '); 117 | } 118 | 119 | /** 120 | * {isPhone /} 121 | * 122 | * @param MacroNode $node 123 | * @param PhpWriter $writer 124 | * 125 | * @return string 126 | */ 127 | public function macroIsPhone(MacroNode $node, PhpWriter $writer) : string 128 | { 129 | return $writer->write(' 130 | $_resultMD = property_exists($this, "filters") ? %escape(call_user_func($this->filters->isMobile) && !call_user_func($this->filters->isTablet)) : $template->getMobileDetectService()->isMobile() && !$template->getMobileDetectService()->isTablet(); 131 | if ($_resultMD) { 132 | '); 133 | } 134 | 135 | /** 136 | * {isNotPhone /} 137 | * 138 | * @param MacroNode $node 139 | * @param PhpWriter $writer 140 | * 141 | * @return string 142 | */ 143 | public function macroIsNotPhone(MacroNode $node, PhpWriter $writer) : string 144 | { 145 | return $writer->write(' 146 | $_resultMD = property_exists($this, "filters") ? %escape((call_user_func($this->filters->isMobile) && call_user_func($this->filters->isTablet)) || !call_user_func($this->filters->isMobile)) : ($template->getMobileDetectService()->isMobile() && $template->getMobileDetectService()->isTablet()) || !$template->getMobileDetectService()->isMobile(); 147 | if ($_resultMD) { 148 | '); 149 | } 150 | 151 | /** 152 | * {isTablet /} 153 | * 154 | * @param MacroNode $node 155 | * @param PhpWriter $writer 156 | * 157 | * @return string 158 | */ 159 | public function macroIsTablet(MacroNode $node, PhpWriter $writer) : string 160 | { 161 | return $writer->write(' 162 | $_resultMD = property_exists($this, "filters") ? %escape(call_user_func($this->filters->isTablet)) : $template->getMobileDetectService()->isTablet(); 163 | if ($_resultMD) { 164 | '); 165 | } 166 | 167 | /** 168 | * {isNotTablet /} 169 | * 170 | * @param MacroNode $node 171 | * @param PhpWriter $writer 172 | * 173 | * @return string 174 | */ 175 | public function macroIsNotTablet(MacroNode $node, PhpWriter $writer) : string 176 | { 177 | return $writer->write(' 178 | $_resultMD = property_exists($this, "filters") ? %escape(!call_user_func($this->filters->isTablet)) : !$template->getMobileDetectService()->isTablet(); 179 | if ($_resultMD) { 180 | '); 181 | } 182 | 183 | /** 184 | * @param MacroNode $node 185 | * @param PhpWriter $writer 186 | * 187 | * @return string 188 | * 189 | * @throws Exceptions\CompileException 190 | */ 191 | public function macroIsDevice(MacroNode $node, PhpWriter $writer) : string 192 | { 193 | $arguments = self::prepareMacroArguments($node->args); 194 | 195 | if ($arguments['device'] === NULL) { 196 | throw new Exceptions\CompileException('Please provide device name.'); 197 | } 198 | 199 | // Create magic method name 200 | $magicMethodName = 'is' . ucfirst(strtolower((string) $arguments["device"])); 201 | 202 | return $writer->write(' 203 | $_resultMD = property_exists($this, "filters") ? %escape(call_user_func($this->filters->isDevice, "' . $arguments['device'] . '")) : $template->getMobileDetectService()->'. $magicMethodName.'(); 204 | if ($_resultMD) { 205 | '); 206 | } 207 | 208 | /** 209 | * @param MacroNode $node 210 | * @param PhpWriter $writer 211 | * 212 | * @return string 213 | * 214 | * @throws Exceptions\CompileException 215 | */ 216 | public function macroIsOS(MacroNode $node, PhpWriter $writer) : string 217 | { 218 | $arguments = self::prepareMacroArguments($node->args); 219 | 220 | if ($arguments['os'] === NULL) { 221 | throw new Exceptions\CompileException('Please provide OS name.'); 222 | } 223 | 224 | // Create magic method name 225 | $magicMethodName = 'is' . ucfirst(strtolower((string) $arguments["os"])); 226 | 227 | return $writer->write(' 228 | $_resultMD = property_exists($this, "filters") ? %escape(call_user_func($this->filters->isOs, "' . $arguments['os'] . '")) : $template->getMobileDetectService()->'. $magicMethodName.'(); 229 | if ($_resultMD) { 230 | '); 231 | } 232 | 233 | /** 234 | * {isFullView /} 235 | * 236 | * @param MacroNode $node 237 | * @param PhpWriter $writer 238 | * 239 | * @return string 240 | */ 241 | public function macroIsFullView(MacroNode $node, PhpWriter $writer) : string 242 | { 243 | return $writer->write(' 244 | $_resultMD = property_exists($this, "filters") ? %escape(!call_user_func($this->filters->isFullView)) : $template->getDeviceViewService()->isFullView(); 245 | if ($_resultMD) { 246 | '); 247 | } 248 | 249 | /** 250 | * {isMobileView /} 251 | * 252 | * @param MacroNode $node 253 | * @param PhpWriter $writer 254 | * 255 | * @return string 256 | */ 257 | public function macroIsMobileView(MacroNode $node, PhpWriter $writer) : string 258 | { 259 | return $writer->write(' 260 | $_resultMD = property_exists($this, "filters") ? %escape(!call_user_func($this->filters->isMobileView)) : $template->getDeviceViewService()->isMobileView(); 261 | if ($_resultMD) { 262 | '); 263 | } 264 | 265 | /** 266 | * {isPhoneView /} 267 | * 268 | * @param MacroNode $node 269 | * @param PhpWriter $writer 270 | * 271 | * @return string 272 | */ 273 | public function macroIsPhoneView(MacroNode $node, PhpWriter $writer) : string 274 | { 275 | return $writer->write(' 276 | $_resultMD = property_exists($this, "filters") ? %escape(!call_user_func($this->filters->isPhoneView)) : $template->getDeviceViewService()->isPhoneView(); 277 | if ($_resultMD) { 278 | '); 279 | } 280 | 281 | /** 282 | * {isTabletView /} 283 | * 284 | * @param MacroNode $node 285 | * @param PhpWriter $writer 286 | * 287 | * @return string 288 | */ 289 | public function macroIsTabletView(MacroNode $node, PhpWriter $writer) : string 290 | { 291 | return $writer->write(' 292 | $_resultMD = property_exists($this, "filters") ? %escape(!call_user_func($this->filters->isTabletView)) : $template->getDeviceViewService()->isTabletView(); 293 | if ($_resultMD) { 294 | '); 295 | } 296 | 297 | /** 298 | * {isNotMobileView /} 299 | * 300 | * @param MacroNode $node 301 | * @param PhpWriter $writer 302 | * 303 | * @return string 304 | */ 305 | public function macroIsNotMobileView(MacroNode $node, PhpWriter $writer) : string 306 | { 307 | return $writer->write(' 308 | $_resultMD = property_exists($this, "filters") ? %escape(!call_user_func($this->filters->isNotMobileView)) : $template->getDeviceViewService()->isNotMobileView(); 309 | if ($_resultMD) { 310 | '); 311 | } 312 | 313 | /** 314 | * @param string $macro 315 | * 316 | * @return array 317 | */ 318 | public static function prepareMacroArguments($macro) : array 319 | { 320 | $arguments = array_map(function ($value) : string { 321 | return trim($value); 322 | }, explode(',', $macro)); 323 | 324 | $device = $os = $arguments[0]; 325 | 326 | return [ 327 | 'device' => $device, 328 | 'os' => $os, 329 | ]; 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /src/IPub/MobileDetect/Events/OnRequestHandler.php: -------------------------------------------------------------------------------- 1 | 8 | * @package iPublikuj:MobileDetect! 9 | * @subpackage Events 10 | * @since 1.0.0 11 | * 12 | * @date 22.04.14 13 | */ 14 | 15 | declare(strict_types = 1); 16 | 17 | namespace IPub\MobileDetect\Events; 18 | 19 | use Nette\Application; 20 | use Nette\Application\Responses; 21 | use Nette\Http; 22 | 23 | use IPub\MobileDetect; 24 | use IPub\MobileDetect\Helpers; 25 | 26 | /** 27 | * On request event handler 28 | * 29 | * @package iPublikuj:MobileDetect! 30 | * @subpackage Events 31 | * 32 | * @author Adam Kadlec 33 | */ 34 | final class OnRequestHandler 35 | { 36 | private const REDIRECT = 'redirect'; 37 | private const NO_REDIRECT = 'noRedirect'; 38 | private const REDIRECT_WITHOUT_PATH = 'redirectWithoutPath'; 39 | 40 | private const MOBILE = 'mobile'; 41 | private const TABLET = 'tablet'; 42 | private const PHONE = 'phone'; 43 | 44 | /** 45 | * @var array 46 | */ 47 | public $redirectConf = []; 48 | 49 | /** 50 | * @var bool 51 | */ 52 | public $isFullPath = true; 53 | 54 | /** 55 | * @var Http\IRequest 56 | */ 57 | private $httpRequest; 58 | 59 | /** 60 | * @var Http\IResponse 61 | */ 62 | private $httpResponse; 63 | 64 | /** 65 | * @var Application\IRouter 66 | */ 67 | private $router; 68 | 69 | /** 70 | * @var MobileDetect\MobileDetect 71 | */ 72 | private $mobileDetect; 73 | 74 | /** 75 | * @var Helpers\DeviceView 76 | */ 77 | private $deviceView; 78 | 79 | /** 80 | * @var OnResponseHandler 81 | */ 82 | private $onResponseHandler; 83 | 84 | /** 85 | * @param Http\IRequest $httpRequest 86 | * @param Http\IResponse $httpResponse 87 | * @param Application\IRouter $router 88 | * @param OnResponseHandler $onResponseHandler 89 | * @param MobileDetect\MobileDetect $mobileDetect 90 | * @param Helpers\DeviceView $deviceView 91 | */ 92 | public function __construct( 93 | Http\IRequest $httpRequest, 94 | Http\IResponse $httpResponse, 95 | Application\IRouter $router, 96 | OnResponseHandler $onResponseHandler, 97 | MobileDetect\MobileDetect $mobileDetect, 98 | Helpers\DeviceView $deviceView 99 | ) { 100 | $this->httpRequest = $httpRequest; 101 | $this->httpResponse = $httpResponse; 102 | 103 | $this->router = $router; 104 | 105 | $this->onResponseHandler = $onResponseHandler; 106 | 107 | $this->mobileDetect = $mobileDetect; 108 | $this->deviceView = $deviceView; 109 | } 110 | 111 | /** 112 | * @param Application\Application $application 113 | * 114 | * @return void 115 | */ 116 | public function __invoke(Application\Application $application) : void 117 | { 118 | // Redirect only normal request 119 | if ($this->httpRequest->isAjax()) { 120 | return; 121 | } 122 | 123 | // Sets the flag for the response handled by the GET switch param and the type of the view. 124 | if ($this->deviceView->hasSwitchParameter()) { 125 | if ($response = $this->getRedirectResponseBySwitchParam()) { 126 | $response->send($this->httpRequest, $this->httpResponse); 127 | exit(); 128 | } 129 | 130 | return; 131 | } 132 | 133 | // If the device view is either the full view or not the mobile view 134 | if ($this->deviceView->isFullView() || $this->deviceView->isNotMobileView()) { 135 | return; 136 | } 137 | 138 | // Redirects to the phone version and set the 'phone' device view in a cookie. 139 | if ($this->hasPhoneRedirect()) { 140 | if ($response = $this->getDeviceRedirectResponse(self::PHONE)) { 141 | $response->send($this->httpRequest, $this->httpResponse); 142 | exit(); 143 | } 144 | 145 | return; 146 | } 147 | 148 | // Redirects to the tablet version and set the 'tablet' device view in a cookie. 149 | if ($this->hasTabletRedirect()) { 150 | if ($response = $this->getDeviceRedirectResponse(self::TABLET)) { 151 | $response->send($this->httpRequest, $this->httpResponse); 152 | exit(); 153 | } 154 | 155 | return; 156 | } 157 | 158 | // Redirects to the mobile version and set the 'mobile' device view in a cookie. 159 | if ($this->hasMobileRedirect()) { 160 | if ($response = $this->getDeviceRedirectResponse(self::MOBILE)) { 161 | $response->send($this->httpRequest, $this->httpResponse); 162 | exit(); 163 | } 164 | 165 | return; 166 | } 167 | 168 | // No need to redirect 169 | 170 | // Sets the flag for the response handler 171 | $this->onResponseHandler->needModifyResponse(); 172 | 173 | // Checking the need to modify the Response and set closure 174 | if ($this->needPhoneResponseModify()) { 175 | $this->deviceView->setPhoneView(); 176 | 177 | return; 178 | } 179 | 180 | // Checking the need to modify the Response and set closure 181 | if ($this->needTabletResponseModify()) { 182 | $this->deviceView->setTabletView(); 183 | 184 | return; 185 | } 186 | 187 | // Sets the closure modifier mobile Response 188 | if ($this->needMobileResponseModify()) { 189 | $this->deviceView->setMobileView(); 190 | 191 | return; 192 | } 193 | 194 | // Sets the closure modifier not_mobile Response 195 | if ($this->needNotMobileResponseModify()) { 196 | $this->deviceView->setNotMobileView(); 197 | 198 | return; 199 | } 200 | } 201 | 202 | /** 203 | * Detects phone redirections 204 | * 205 | * @return bool 206 | */ 207 | private function hasPhoneRedirect() : bool 208 | { 209 | if (!$this->redirectConf['phone']['isEnabled']) { 210 | return false; 211 | } 212 | 213 | $isPhone = $this->mobileDetect->isPhone(); 214 | 215 | if ($this->redirectConf['detectPhoneAsMobile'] === false) { 216 | $isPhoneHost = ($this->getCurrentHost() === $this->redirectConf['phone']['host']); 217 | 218 | if ($isPhone && !$isPhoneHost && ($this->getRoutingOption(self::PHONE) != self::NO_REDIRECT)) { 219 | return true; 220 | } 221 | 222 | } else { 223 | $isMobileHost = ($this->getCurrentHost() === $this->redirectConf['mobile']['host']); 224 | 225 | if ($isPhone && !$isMobileHost && ($this->getRoutingOption(self::PHONE) != self::NO_REDIRECT)) { 226 | return true; 227 | } 228 | } 229 | 230 | return false; 231 | } 232 | 233 | /** 234 | * Detects tablet redirections 235 | * 236 | * @return bool 237 | */ 238 | private function hasTabletRedirect() : bool 239 | { 240 | if (!$this->redirectConf['tablet']['isEnabled']) { 241 | return false; 242 | } 243 | 244 | $isTablet = $this->mobileDetect->isTablet(); 245 | 246 | if ($this->redirectConf['detectTabletAsMobile'] === false) { 247 | $isTabletHost = ($this->getCurrentHost() === $this->redirectConf['tablet']['host']); 248 | 249 | if ($isTablet && !$isTabletHost && ($this->getRoutingOption(self::TABLET) != self::NO_REDIRECT)) { 250 | return true; 251 | } 252 | 253 | } else { 254 | $isMobileHost = ($this->getCurrentHost() === $this->redirectConf['mobile']['host']); 255 | 256 | if ($isTablet && !$isMobileHost && ($this->getRoutingOption(self::TABLET) != self::NO_REDIRECT)) { 257 | return true; 258 | } 259 | } 260 | 261 | return false; 262 | } 263 | 264 | /** 265 | * Detects mobile redirections 266 | * 267 | * @return bool 268 | */ 269 | private function hasMobileRedirect() : bool 270 | { 271 | if (!$this->redirectConf['mobile']['isEnabled']) { 272 | return false; 273 | } 274 | 275 | if ($this->redirectConf['detectPhoneAsMobile'] === false) { 276 | $isMobile = ($this->mobileDetect->isTablet() || ($this->mobileDetect->isMobile()) && !$this->mobileDetect->isPhone()); 277 | 278 | } elseif ($this->redirectConf['detectTabletAsMobile'] === false) { 279 | $isMobile = ($this->mobileDetect->isPhone() || ($this->mobileDetect->isMobile()) && !$this->mobileDetect->isTablet()); 280 | 281 | } else { 282 | $isMobile = $this->mobileDetect->isMobile(); 283 | } 284 | 285 | $isMobileHost = ($this->getCurrentHost() === $this->redirectConf['mobile']['host']); 286 | 287 | if ($isMobile && !$isMobileHost && ($this->getRoutingOption(self::MOBILE) != self::NO_REDIRECT)) { 288 | return true; 289 | } 290 | 291 | return false; 292 | } 293 | 294 | /** 295 | * If a modified Response for phone devices is needed 296 | * 297 | * @return bool 298 | */ 299 | private function needPhoneResponseModify() : bool 300 | { 301 | if (($this->deviceView->getViewType() === null || $this->deviceView->isPhoneView()) && $this->mobileDetect->isMobile() && !$this->mobileDetect->isTablet()) { 302 | $this->onResponseHandler->modifyResponseClosure = function (Helpers\DeviceView $deviceView) : Http\IResponse { 303 | return $deviceView->modifyPhoneResponse(); 304 | }; 305 | 306 | return true; 307 | } 308 | 309 | return false; 310 | } 311 | 312 | /** 313 | * If a modified Response for tablet devices is needed 314 | * 315 | * @return bool 316 | */ 317 | private function needTabletResponseModify() : bool 318 | { 319 | if (($this->deviceView->getViewType() === null || $this->deviceView->isTabletView()) && $this->mobileDetect->isTablet()) { 320 | $this->onResponseHandler->modifyResponseClosure = function (Helpers\DeviceView $deviceView) : Http\IResponse { 321 | return $deviceView->modifyTabletResponse(); 322 | }; 323 | 324 | return true; 325 | } 326 | 327 | return false; 328 | } 329 | 330 | /** 331 | * If a modified Response for mobile devices is needed 332 | * 333 | * @return bool 334 | */ 335 | private function needMobileResponseModify() : bool 336 | { 337 | if (($this->deviceView->getViewType() === null || $this->deviceView->isMobileView()) && $this->mobileDetect->isMobile()) { 338 | $this->onResponseHandler->modifyResponseClosure = function (Helpers\DeviceView $deviceView) : Http\IResponse { 339 | return $deviceView->modifyMobileResponse(); 340 | }; 341 | 342 | return true; 343 | } 344 | 345 | return false; 346 | } 347 | 348 | /** 349 | * If a modified Response for non-mobile devices is needed 350 | * 351 | * @return bool 352 | */ 353 | private function needNotMobileResponseModify() : bool 354 | { 355 | if ($this->deviceView->getViewType() === null || $this->deviceView->isNotMobileView()) { 356 | $this->onResponseHandler->modifyResponseClosure = function (Helpers\DeviceView $deviceView) : Http\IResponse { 357 | return $deviceView->modifyNotMobileResponse(); 358 | }; 359 | 360 | return true; 361 | } 362 | 363 | return false; 364 | } 365 | 366 | /** 367 | * Gets the RedirectResponse by switch param 368 | * 369 | * @return Responses\RedirectResponse 370 | */ 371 | private function getRedirectResponseBySwitchParam() : Responses\RedirectResponse 372 | { 373 | // Generate full url path 374 | if ($this->isFullPath === true) { 375 | // Get actual url 376 | $url = $this->httpRequest->getUrl(); 377 | 378 | // Remove switch param 379 | $url->setQueryParameter($this->deviceView->getSwitchParameterName(), null); 380 | 381 | // Create full path url 382 | $redirectUrl = $this->getCurrentHost() . $url->getRelativeUrl(); 383 | 384 | // Generate only domain path 385 | } else { 386 | $redirectUrl = $this->getCurrentHost(); 387 | } 388 | 389 | return $this->deviceView->getRedirectResponseBySwitchParam($redirectUrl); 390 | } 391 | 392 | /** 393 | * Gets the device RedirectResponse 394 | * 395 | * @param string $device 396 | * 397 | * @return Responses\RedirectResponse|NULL 398 | */ 399 | private function getDeviceRedirectResponse(string $device) : ?Responses\RedirectResponse 400 | { 401 | if ($host = $this->getRedirectUrl($device)) { 402 | return $this->deviceView->getMobileRedirectResponse( 403 | $host, 404 | $this->redirectConf[$device]['statusCode'] 405 | ); 406 | } 407 | 408 | return null; 409 | } 410 | 411 | /** 412 | * Gets the redirect url 413 | * 414 | * @param string $platform 415 | * 416 | * @return string|NULL 417 | */ 418 | private function getRedirectUrl(string $platform) : ?string 419 | { 420 | if ($routingOption = $this->getRoutingOption($platform)) { 421 | switch ($routingOption) { 422 | case self::REDIRECT: 423 | return rtrim($this->redirectConf[$platform]['host'], '/') . '/' . ltrim($this->httpRequest->getUrl()->getRelativeUrl(), '/'); 424 | 425 | case self::REDIRECT_WITHOUT_PATH: 426 | return $this->redirectConf[$platform]['host']; 427 | } 428 | } 429 | 430 | return null; 431 | } 432 | 433 | /** 434 | * Gets named option from current route 435 | * 436 | * @param string $name 437 | * 438 | * @return string|NULL 439 | */ 440 | private function getRoutingOption(string $name) : ?string 441 | { 442 | $option = null; 443 | 444 | // Get actual route 445 | $request = $this->router->match($this->httpRequest); 446 | 447 | if ($request instanceof Application\Request) { 448 | $params = $request->getParameters(); 449 | $option = isset($params[$name]) ? $params[$name] : null; 450 | } 451 | 452 | if (!$option) { 453 | $option = $this->redirectConf[$name]['action']; 454 | } 455 | 456 | if (in_array($option, [self::REDIRECT, self::REDIRECT_WITHOUT_PATH, self::NO_REDIRECT])) { 457 | return $option; 458 | } 459 | 460 | return null; 461 | } 462 | 463 | /** 464 | * Gets the current host 465 | * 466 | * @return string 467 | */ 468 | private function getCurrentHost() : string 469 | { 470 | return $this->httpRequest->getUrl()->getHostUrl() . $this->httpRequest->getUrl()->getScriptPath(); 471 | } 472 | } 473 | --------------------------------------------------------------------------------