├── .editorconfig ├── .github └── workflows │ └── phpunit-test.yml ├── .gitignore ├── Block └── Matomo.php ├── CustomerData ├── Checkout │ └── CartPlugin.php └── Customer │ └── CustomerPlugin.php ├── Helper ├── Data.php └── Tracker.php ├── LICENSE ├── Model ├── Config │ └── Source │ │ └── UserId │ │ └── Provider.php ├── Tracker.php └── Tracker │ └── Action.php ├── Observer ├── BeforeTrackPageViewObserver.php ├── CartViewObserver.php ├── CategoryViewObserver.php ├── CheckoutSuccessObserver.php ├── ProductViewObserver.php └── SearchResultObserver.php ├── README.md ├── Test └── Unit │ ├── CustomerData │ ├── Checkout │ │ └── CartPluginTest.php │ └── Customer │ │ └── CustomerPluginTest.php │ ├── Helper │ ├── DataTest.php │ └── TrackerTest.php │ ├── Model │ └── TrackerTest.php │ └── Observer │ ├── BeforeTrackPageViewObserverTest.php │ ├── CartViewObserverTest.php │ ├── CategoryViewObserverTest.php │ ├── CheckoutSuccessObserverTest.php │ ├── ProductViewObserverTest.php │ └── SearchResultObserverTest.php ├── UserId └── Provider │ ├── EmailProvider.php │ ├── EntityIdProvider.php │ ├── Pool.php │ └── ProviderInterface.php ├── composer.json ├── dev └── ci │ ├── BUILDVARS.conf.dist │ └── build.sh ├── etc ├── acl.xml ├── adminhtml │ └── system.xml ├── config.xml ├── di.xml ├── frontend │ ├── di.xml │ └── events.xml └── module.xml ├── i18n ├── en_US.csv ├── it_IT.csv └── sv_SE.csv ├── registration.php └── view └── frontend ├── layout └── default.xml ├── templates └── matomo.phtml └── web └── js └── tracker.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [.travis.yml] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.github/workflows/phpunit-test.yml: -------------------------------------------------------------------------------- 1 | name: PHPUnit Test 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | include: 18 | - magento-version: '2.4.5-p1' 19 | php-version: '8.1' 20 | - magento-version: '2.4.4-p2' 21 | php-version: '8.1' 22 | - magento-version: '2.4.3-p3' 23 | php-version: '7.4' 24 | - magento-version: '2.4.2-p2' 25 | php-version: '7.4' 26 | - magento-version: '2.4.1-p1' 27 | php-version: '7.4' 28 | composer-version: '1' 29 | - magento-version: '2.4.0-p1' 30 | php-version: '7.3' 31 | composer-version: '1' 32 | - magento-version: '2.3.6-p1' 33 | php-version: '7.3' 34 | composer-version: '1' 35 | 36 | steps: 37 | - name: Checkout Code 38 | uses: actions/checkout@v3 39 | 40 | - name: Set PHP Version 41 | uses: shivammathur/setup-php@v2 42 | with: 43 | php-version: ${{ matrix.php-version }} 44 | 45 | - name: Set composer Version 46 | if: ${{ matrix.composer-version == '1' }} 47 | run: | 48 | composer --verbose self-update --${{ matrix.composer-version }} 49 | 50 | - name: Run PHPUnit Test 51 | run: | 52 | ./dev/ci/build.sh 53 | env: 54 | M2_VERSION: ${{ matrix.magento-version }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dev/ci/BUILDVARS.conf 2 | -------------------------------------------------------------------------------- /Block/Matomo.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Block; 23 | 24 | /** 25 | * Matomo page block 26 | * 27 | */ 28 | class Matomo extends \Magento\Framework\View\Element\Template 29 | { 30 | 31 | /** 32 | * JSON encoder 33 | * 34 | * @var \Magento\Framework\Json\EncoderInterface 35 | */ 36 | protected $_jsonEncoder; 37 | 38 | /** 39 | * Matomo tracker model 40 | * 41 | * @var \Chessio\Matomo\Model\Tracker $_tracker 42 | */ 43 | protected $_tracker; 44 | 45 | /** 46 | * Matomo data helper 47 | * 48 | * @var \Chessio\Matomo\Helper\Data 49 | */ 50 | protected $_dataHelper = null; 51 | 52 | /** 53 | * Constructor 54 | * 55 | * @param \Magento\Framework\View\Element\Template\Context $context 56 | * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder 57 | * @param \Chessio\Matomo\Model\Tracker $tracker 58 | * @param \Chessio\Matomo\Helper\Data $dataHelper 59 | * @param array $data 60 | */ 61 | public function __construct( 62 | \Magento\Framework\View\Element\Template\Context $context, 63 | \Magento\Framework\Json\EncoderInterface $jsonEncoder, 64 | \Chessio\Matomo\Model\Tracker $tracker, 65 | \Chessio\Matomo\Helper\Data $dataHelper, 66 | array $data = [] 67 | ) { 68 | $this->_jsonEncoder = $jsonEncoder; 69 | $this->_tracker = $tracker; 70 | $this->_dataHelper = $dataHelper; 71 | parent::__construct($context, $data); 72 | } 73 | 74 | /** 75 | * Get Matomo tracker actions 76 | * 77 | * @return \Chessio\Matomo\Model\Tracker 78 | */ 79 | public function getTracker() 80 | { 81 | return $this->_tracker; 82 | } 83 | 84 | /** 85 | * Populate tracker with actions before rendering 86 | * 87 | * @return void 88 | */ 89 | protected function _prepareTracker() 90 | { 91 | $tracker = $this->getTracker(); 92 | 93 | $this->_eventManager->dispatch( 94 | 'matomo_track_page_view_before', 95 | ['block' => $this, 'tracker' => $tracker] 96 | ); 97 | 98 | if (!$this->getSkipTrackPageView()) { 99 | $tracker->trackPageView(); 100 | } 101 | 102 | $this->_eventManager->dispatch( 103 | 'matomo_track_page_view_after', 104 | ['block' => $this, 'tracker' => $tracker] 105 | ); 106 | } 107 | 108 | /** 109 | * Get javascript tracker options 110 | * 111 | * @return array 112 | */ 113 | public function getJsOptions() 114 | { 115 | return [ 116 | 'scriptUrl' => $this->getScriptUrl(), 117 | 'trackerUrl' => $this->getTrackerUrl(), 118 | 'siteId' => $this->getSiteId(), 119 | 'actions' => $this->getTracker()->toArray() 120 | ]; 121 | } 122 | 123 | /** 124 | * Get Matomo JS URL 125 | * 126 | * @return string 127 | */ 128 | public function getScriptUrl() 129 | { 130 | return $this->_dataHelper->getJsScriptUrl(); 131 | } 132 | 133 | /** 134 | * Get Matomo tracker URL 135 | * 136 | * @return string 137 | */ 138 | public function getTrackerUrl() 139 | { 140 | return $this->_dataHelper->getPhpScriptUrl(); 141 | } 142 | 143 | /** 144 | * Get Matomo site ID 145 | * 146 | * @return int 147 | */ 148 | public function getSiteId() 149 | { 150 | return $this->_dataHelper->getSiteId(); 151 | } 152 | 153 | /** 154 | * Get tracking pixel URL 155 | * 156 | * @return string 157 | */ 158 | public function getTrackingPixelUrl() 159 | { 160 | $params = [ 161 | 'idsite' => $this->getSiteId(), 162 | 'rec' => 1, 163 | 'url' => $this->_urlBuilder->getCurrentUrl() 164 | ]; 165 | return $this->getTrackerUrl() . '?' . http_build_query($params); 166 | } 167 | 168 | /** 169 | * Encode data to a JSON string 170 | * 171 | * @param mixed $data 172 | * @return string 173 | */ 174 | public function jsonEncode($data) 175 | { 176 | return $this->_jsonEncoder->encode($data); 177 | } 178 | 179 | /** 180 | * Generate Matomo tracking script 181 | * 182 | * @return string 183 | */ 184 | protected function _toHtml() 185 | { 186 | if ($this->_dataHelper->isTrackingEnabled()) { 187 | $this->_prepareTracker(); 188 | return parent::_toHtml(); 189 | } 190 | return ''; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /CustomerData/Checkout/CartPlugin.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\CustomerData\Checkout; 23 | 24 | /** 25 | * Plugin for \Magento\Checkout\CustomerData\Cart 26 | * 27 | */ 28 | class CartPlugin 29 | { 30 | 31 | /** 32 | * Checkout session instance 33 | * 34 | * @var \Magento\Checkout\Model\Session $_checkoutSession 35 | */ 36 | protected $_checkoutSession; 37 | 38 | /** 39 | * Matomo data helper 40 | * 41 | * @var \Chessio\Matomo\Helper\Data $_dataHelper 42 | */ 43 | protected $_dataHelper; 44 | 45 | /** 46 | * Tracker helper 47 | * 48 | * @var \Chessio\Matomo\Helper\Tracker $_trackerHelper 49 | */ 50 | protected $_trackerHelper; 51 | 52 | /** 53 | * Tracker factory 54 | * 55 | * @var \Chessio\Matomo\Model\TrackerFactory $_trackerFactory 56 | */ 57 | protected $_trackerFactory; 58 | 59 | /** 60 | * Constructor 61 | * 62 | * @param \Magento\Checkout\Model\Session\Proxy $checkoutSession 63 | * @param \Chessio\Matomo\Helper\Data $dataHelper 64 | * @param \Chessio\Matomo\Helper\Tracker $trackerHelper 65 | * @param \Chessio\Matomo\Model\TrackerFactory $trackerFactory 66 | */ 67 | public function __construct( 68 | \Magento\Checkout\Model\Session\Proxy $checkoutSession, 69 | \Chessio\Matomo\Helper\Data $dataHelper, 70 | \Chessio\Matomo\Helper\Tracker $trackerHelper, 71 | \Chessio\Matomo\Model\TrackerFactory $trackerFactory 72 | ) { 73 | $this->_checkoutSession = $checkoutSession; 74 | $this->_dataHelper = $dataHelper; 75 | $this->_trackerHelper = $trackerHelper; 76 | $this->_trackerFactory = $trackerFactory; 77 | } 78 | 79 | /** 80 | * Add `trackEcommerceCartUpdate' checkout cart customer data 81 | * 82 | * @param \Magento\Checkout\CustomerData\Cart $subject 83 | * @param array $result 84 | * @return array 85 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 86 | */ 87 | public function afterGetSectionData( 88 | \Magento\Checkout\CustomerData\Cart $subject, 89 | $result 90 | ) { 91 | if ($this->_dataHelper->isTrackingEnabled()) { 92 | $quote = $this->_checkoutSession->getQuote(); 93 | if ($quote->getId()) { 94 | $tracker = $this->_trackerFactory->create(); 95 | $this->_trackerHelper->addQuote($quote, $tracker); 96 | $result['matomoActions'] = $tracker->toArray(); 97 | } 98 | } 99 | return $result; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /CustomerData/Customer/CustomerPlugin.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\CustomerData\Customer; 23 | 24 | /** 25 | * Plugin for \Magento\Customer\CustomerData\Customer 26 | * 27 | */ 28 | class CustomerPlugin 29 | { 30 | 31 | /** 32 | * Current customer helper 33 | * 34 | * @var \Magento\Customer\Helper\Session\CurrentCustomer $_currentCustomer 35 | */ 36 | protected $_currentCustomer; 37 | 38 | /** 39 | * Matomo data helper 40 | * 41 | * @var \Chessio\Matomo\Helper\Data $_dataHelper 42 | */ 43 | protected $_dataHelper; 44 | 45 | /** 46 | * User ID provider pool 47 | * 48 | * @var \Chessio\Matomo\UserId\Provider\Pool $_uidProviderPool 49 | */ 50 | protected $_uidProviderPool; 51 | 52 | /** 53 | * Constructor 54 | * 55 | * @param \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer 56 | * @param \Chessio\Matomo\Helper\Data $dataHelper 57 | * @param \Chessio\Matomo\UserId\Provider\Pool $uidProviderPool 58 | */ 59 | public function __construct( 60 | \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer, 61 | \Chessio\Matomo\Helper\Data $dataHelper, 62 | \Chessio\Matomo\UserId\Provider\Pool $uidProviderPool 63 | ) { 64 | $this->_currentCustomer = $currentCustomer; 65 | $this->_dataHelper = $dataHelper; 66 | $this->_uidProviderPool = $uidProviderPool; 67 | } 68 | 69 | /** 70 | * Get configured Matomo User ID provider or NULL 71 | * 72 | * @return \Chessio\Matomo\UserId\Provider\ProviderInterface|null 73 | */ 74 | protected function _getUserIdProvider() 75 | { 76 | $code = $this->_dataHelper->getUserIdProviderCode(); 77 | return $code ? $this->_uidProviderPool->getProviderByCode($code) : null; 78 | } 79 | 80 | /** 81 | * Get Matomo User ID for current customer 82 | * 83 | * @return string 84 | */ 85 | protected function _getUserId() 86 | { 87 | $provider = $this->_getUserIdProvider(); 88 | $customerId = $this->_currentCustomer->getCustomerId(); 89 | return ($provider && $customerId) 90 | ? (string) $provider->getUserId($customerId) 91 | : ''; 92 | } 93 | 94 | /** 95 | * Add visitor related tracker information to customer section data. 96 | * 97 | * @param \Magento\Customer\CustomerData\Customer $subject 98 | * @param array $result 99 | * @return array 100 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 101 | */ 102 | public function afterGetSectionData( 103 | \Magento\Customer\CustomerData\Customer $subject, 104 | $result 105 | ) { 106 | if ($this->_dataHelper->isTrackingEnabled()) { 107 | $userId = $this->_getUserId(); 108 | if ($userId !== '') { 109 | $result['matomoUserId'] = $userId; 110 | } 111 | } 112 | return $result; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Helper/Data.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Helper; 23 | 24 | use Magento\Store\Model\Store; 25 | 26 | /** 27 | * Matomo data helper 28 | * 29 | */ 30 | class Data extends \Magento\Framework\App\Helper\AbstractHelper 31 | { 32 | 33 | /** 34 | * System config XML paths 35 | * Prefix "piwik/" left for compatibility with Henhed_Piwik 36 | */ 37 | const XML_PATH_ENABLED = 'piwik/tracking/enabled'; 38 | const XML_PATH_HOSTNAME = 'piwik/tracking/hostname'; 39 | const XML_PATH_CDN_HOSTNAME = 'piwik/tracking/cdn_hostname'; 40 | const XML_PATH_JS_SCRIPT_PATH = 'piwik/tracking/js_script_path'; 41 | const XML_PATH_PHP_SCRIPT_PATH = 'piwik/tracking/php_script_path'; 42 | const XML_PATH_SITE_ID = 'piwik/tracking/site_id'; 43 | const XML_PATH_LINK_ENABLED = 'piwik/tracking/link_enabled'; 44 | const XML_PATH_LINK_DELAY = 'piwik/tracking/link_delay'; 45 | const XML_PATH_UID_PROVIDER = 'piwik/tracking/uid_provider'; 46 | 47 | /** 48 | * Check if Matomo is enabled 49 | * 50 | * @param null|string|bool|int|Store $store 51 | * @return bool 52 | */ 53 | public function isTrackingEnabled($store = null) 54 | { 55 | $hostname = $this->getHostname($store); 56 | $siteId = $this->getSiteId($store); 57 | return $hostname && $siteId && $this->scopeConfig->isSetFlag( 58 | self::XML_PATH_ENABLED, 59 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 60 | $store 61 | ); 62 | } 63 | 64 | /** 65 | * Retrieve Matomo hostname 66 | * 67 | * @param null|string|bool|int|Store $store 68 | * @return string 69 | */ 70 | public function getHostname($store = null) 71 | { 72 | return trim($this->scopeConfig->getValue( 73 | self::XML_PATH_HOSTNAME, 74 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 75 | $store 76 | ) ?? ""); 77 | } 78 | 79 | /** 80 | * Retrieve Matomo CDN hostname 81 | * 82 | * @param null|string|bool|int|Store $store 83 | * @return string 84 | */ 85 | public function getCdnHostname($store = null) 86 | { 87 | return trim($this->scopeConfig->getValue( 88 | self::XML_PATH_CDN_HOSTNAME, 89 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 90 | $store 91 | ) ?? ""); 92 | } 93 | 94 | /** 95 | * Retrieve base URL for given hostname 96 | * 97 | * @param string $host 98 | * @param null|bool $secure 99 | * @return string 100 | */ 101 | protected function _getBaseUrl($host, $secure = null) 102 | { 103 | if ($secure === null) { 104 | $secure = $this->_getRequest()->isSecure(); 105 | } 106 | if (false !== ($scheme = strpos($host, '://'))) { 107 | $host = substr($host, $scheme + 3); 108 | } 109 | return ($secure ? 'https://' : 'http://') . rtrim($host, '/') . '/'; 110 | } 111 | 112 | /** 113 | * Retrieve Matomo base URL 114 | * 115 | * @param null|string|bool|int|Store $store 116 | * @param null|bool $secure 117 | * @return string 118 | */ 119 | public function getBaseUrl($store = null, $secure = null) 120 | { 121 | return $this->_getBaseUrl($this->getHostname($store), $secure); 122 | } 123 | 124 | /** 125 | * Retrieve Matomo CDN URL 126 | * 127 | * @param null|string|bool|int|Store $store 128 | * @param null|bool $secure 129 | * @return string 130 | */ 131 | public function getCdnBaseUrl($store = null, $secure = null) 132 | { 133 | $host = $this->getCdnHostname($store); 134 | return ('' !== $host) 135 | ? $this->_getBaseUrl($host, $secure) 136 | : $this->getBaseUrl($store, $secure); 137 | } 138 | 139 | /** 140 | * Retrieve Matomo tracker JS script path 141 | * 142 | * @param null|string|bool|int|Store $store 143 | * @return string 144 | */ 145 | public function getJsScriptPath($store = null) 146 | { 147 | return ltrim(trim($this->scopeConfig->getValue( 148 | self::XML_PATH_JS_SCRIPT_PATH, 149 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 150 | $store 151 | ) ?? ""), '/') ?: 'matomo.js'; 152 | } 153 | 154 | /** 155 | * Retrieve Matomo tracker JS script URL 156 | * 157 | * @param null|string|bool|int|Store $store 158 | * @param null|bool $secure 159 | * @return string 160 | */ 161 | public function getJsScriptUrl($store = null, $secure = null) 162 | { 163 | return $this->getCdnBaseUrl($store, $secure) 164 | . $this->getJsScriptPath($store); 165 | } 166 | 167 | /** 168 | * Retrieve Matomo tracker PHP script path 169 | * 170 | * @param null|string|bool|int|Store $store 171 | * @return string 172 | */ 173 | public function getPhpScriptPath($store = null) 174 | { 175 | return ltrim(trim($this->scopeConfig->getValue( 176 | self::XML_PATH_PHP_SCRIPT_PATH, 177 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 178 | $store 179 | ) ?? ""), '/') ?: 'matomo.php'; 180 | } 181 | 182 | /** 183 | * Retrieve Matomo tracker PHP script URL 184 | * 185 | * @param null|string|bool|int|Store $store 186 | * @param null|bool $secure 187 | * @return string 188 | */ 189 | public function getPhpScriptUrl($store = null, $secure = null) 190 | { 191 | return $this->getBaseUrl($store, $secure) 192 | . $this->getPhpScriptPath($store); 193 | } 194 | 195 | /** 196 | * Retrieve Matomo site ID 197 | * 198 | * @param null|string|bool|int|Store $store 199 | * @return int 200 | */ 201 | public function getSiteId($store = null) 202 | { 203 | return (int) $this->scopeConfig->getValue( 204 | self::XML_PATH_SITE_ID, 205 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 206 | $store 207 | ); 208 | } 209 | 210 | /** 211 | * Check if Matomo link tracking is enabled 212 | * 213 | * @param null|string|bool|int|Store $store 214 | * @return bool 215 | */ 216 | public function isLinkTrackingEnabled($store = null) 217 | { 218 | return $this->scopeConfig->isSetFlag( 219 | self::XML_PATH_LINK_ENABLED, 220 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 221 | $store 222 | ) && $this->isTrackingEnabled($store); 223 | } 224 | 225 | /** 226 | * Retrieve Matomo link tracking delay in milliseconds 227 | * 228 | * @param null|string|bool|int|Store $store 229 | * @return int 230 | */ 231 | public function getLinkTrackingDelay($store = null) 232 | { 233 | return (int) $this->scopeConfig->getValue( 234 | self::XML_PATH_LINK_DELAY, 235 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 236 | $store 237 | ); 238 | } 239 | 240 | /** 241 | * Get provider code for Matomo user ID tracking 242 | * 243 | * @param null|string|bool|int|Store $store 244 | * @return string 245 | */ 246 | public function getUserIdProviderCode($store = null) 247 | { 248 | return $this->scopeConfig->getValue( 249 | self::XML_PATH_UID_PROVIDER, 250 | \Magento\Store\Model\ScopeInterface::SCOPE_STORE, 251 | $store 252 | ); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /Helper/Tracker.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Helper; 23 | 24 | use Chessio\Matomo\Model\Tracker as TrackerModel; 25 | use Magento\Quote\Model\Quote; 26 | use Magento\Sales\Api\Data\OrderInterface; 27 | use Magento\Sales\Api\Data\OrderItemInterface; 28 | 29 | /** 30 | * Matomo tracker helper 31 | * 32 | * @see http://matomo.org/docs/ecommerce-analytics/ 33 | */ 34 | class Tracker extends \Magento\Framework\App\Helper\AbstractHelper 35 | { 36 | 37 | /** 38 | * Push `addEcommerceItem' with quote item data to given tracker 39 | * 40 | * @param \Magento\Quote\Model\Quote\Item $item 41 | * @param \Chessio\Matomo\Model\Tracker $tracker 42 | * @return \Chessio\Matomo\Helper\Tracker 43 | */ 44 | public function addQuoteItem(Quote\Item $item, TrackerModel $tracker) 45 | { 46 | $tracker->addEcommerceItem( 47 | $item->getSku(), 48 | $item->getName(), 49 | false, 50 | $item->hasCustomPrice() 51 | ? (float) $item->getCustomPrice() 52 | : (float) $item->getBasePriceInclTax(), 53 | (float) $item->getQty() 54 | ); 55 | return $this; 56 | } 57 | 58 | /** 59 | * Push `trackEcommerceCartUpdate' with quote data to given tracker 60 | * 61 | * @param \Magento\Quote\Model\Quote $quote 62 | * @param \Chessio\Matomo\Model\Tracker $tracker 63 | * @return \Chessio\Matomo\Helper\Tracker 64 | */ 65 | public function addQuoteTotal(Quote $quote, TrackerModel $tracker) 66 | { 67 | $tracker->trackEcommerceCartUpdate((float) $quote->getBaseGrandTotal()); 68 | return $this; 69 | } 70 | 71 | /** 72 | * Push quote contents to given tracker 73 | * 74 | * @param \Magento\Quote\Model\Quote $quote 75 | * @param \Chessio\Matomo\Model\Tracker $tracker 76 | * @return \Chessio\Matomo\Helper\Tracker 77 | */ 78 | public function addQuote(Quote $quote, TrackerModel $tracker) 79 | { 80 | foreach ($quote->getAllVisibleItems() as $item) { 81 | $this->addQuoteItem($item, $tracker); 82 | } 83 | $this->addQuoteTotal($quote, $tracker); 84 | return $this; 85 | } 86 | 87 | /** 88 | * Push order contents to given tracker 89 | * 90 | * @param \Magento\Sales\Api\Data\OrderInterface[]|\Traversable $orders 91 | * @param \Chessio\Matomo\Model\Tracker $tracker 92 | * @return \Chessio\Matomo\Helper\Tracker 93 | */ 94 | public function addOrders($orders, TrackerModel $tracker) 95 | { 96 | $matomoItems = []; 97 | $matomoOrder = []; 98 | 99 | // Collect tracking data 100 | foreach ($orders as $order) { 101 | foreach ($order->getItems() as $item) { 102 | if (!$item->getParentItemId()) { 103 | $this->_appendOrderItemData($item, $matomoItems); 104 | } 105 | } 106 | $this->_appendOrderData($order, $matomoOrder); 107 | } 108 | 109 | // Push `addEcommerceItem' 110 | foreach ($matomoItems as $matomoItem) { 111 | list($sku, $name, $rowTotal, $qty) = $matomoItem; 112 | 113 | $tracker->addEcommerceItem( 114 | $sku, 115 | $name, 116 | false, 117 | ($qty > 0) // div-by-zero protection 118 | ? $rowTotal / $qty // restore to unit price 119 | : 0, 120 | $qty 121 | ); 122 | } 123 | 124 | // Push `trackEcommerceOrder' 125 | if (!empty($matomoOrder)) { 126 | list($orderId, $grandTotal, $subTotal, $tax, $shipping, $discount) 127 | = $matomoOrder; 128 | 129 | $tracker->trackEcommerceOrder( 130 | $orderId, 131 | $grandTotal, 132 | $subTotal, 133 | $tax, 134 | $shipping, 135 | ($discount > 0) 136 | ? $discount 137 | : false 138 | ); 139 | } 140 | 141 | return $this; 142 | } 143 | 144 | /** 145 | * @param OrderItemInterface $item 146 | * @param array &$data 147 | * @return void 148 | */ 149 | protected function _appendOrderItemData(OrderItemInterface $item, &$data) 150 | { 151 | $sku = $item->getSku(); 152 | $name = $item->getName(); 153 | $price = (float) $item->getBasePriceInclTax(); 154 | $qty = (float) $item->getQtyOrdered(); 155 | 156 | // Group order items by SKU since Matomo doesn't seem to handle multiple 157 | // `addEcommerceItem' with the same SKU. 158 | if (!isset($data[$sku])) { 159 | $data[$sku] = [$sku, $name, $price * $qty, $qty]; 160 | } else { 161 | // Aggregate row total instead of unit price in case there 162 | // are different prices for the same SKU. 163 | $data[$sku][2] += $price * $qty; 164 | $data[$sku][3] += $qty; 165 | } 166 | } 167 | 168 | /** 169 | * @param OrderInterface $order 170 | * @param array &$data 171 | * @return void 172 | */ 173 | protected function _appendOrderData(OrderInterface $order, &$data) 174 | { 175 | $orderId = $order->getIncrementId(); 176 | $grandTotal = (float) $order->getBaseGrandTotal(); 177 | $subTotal = (float) $order->getBaseSubtotalInclTax(); 178 | $tax = (float) $order->getBaseTaxAmount(); 179 | $shipping = (float) $order->getBaseShippingInclTax(); 180 | $discount = abs((float) $order->getBaseDiscountAmount()); 181 | 182 | // Aggregate all placed orders into one since Matomo seems to only 183 | // register one `trackEcommerceOrder' per request. (For multishipping) 184 | if (empty($data)) { 185 | $data = [ 186 | $orderId, $grandTotal, $subTotal, $tax, $shipping, $discount 187 | ]; 188 | } else { 189 | $data[0] .= ', ' . $orderId; 190 | $data[1] += $grandTotal; 191 | $data[2] += $subTotal; 192 | $data[3] += $tax; 193 | $data[4] += $shipping; 194 | $data[5] += $discount; 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /Model/Config/Source/UserId/Provider.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Model\Config\Source\UserId; 23 | 24 | /** 25 | * User ID provider config source model 26 | * 27 | */ 28 | class Provider implements \Magento\Framework\Option\ArrayInterface 29 | { 30 | 31 | /** 32 | * User ID provider pool 33 | * 34 | * @var \Chessio\Matomo\UserId\Provider\Pool $_pool 35 | */ 36 | protected $_pool; 37 | 38 | /** 39 | * Constructor 40 | * 41 | * @param \Chessio\Matomo\UserId\Provider\Pool $pool 42 | */ 43 | public function __construct(\Chessio\Matomo\UserId\Provider\Pool $pool) 44 | { 45 | $this->_pool = $pool; 46 | } 47 | 48 | /** 49 | * Return array of user ID providers as value-label pairs 50 | * 51 | * @return array 52 | */ 53 | public function toOptionArray() 54 | { 55 | $options = [['value' => '', 'label' => __('No')]]; 56 | foreach ($this->_pool->getAllProviders() as $code => $provider) { 57 | $options[] = [ 58 | 'value' => $code, 59 | 'label' => sprintf('%s (%s)', __('Yes'), $provider->getTitle()) 60 | ]; 61 | } 62 | return $options; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Model/Tracker.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Model; 23 | 24 | /** 25 | * Matomo tracker model 26 | * 27 | * @method Tracker trackEvent(string $category, string $action, 28 | * string $name = null, int $value = null) 29 | * Logs an event with an event category (Videos, Music, Games...), an event 30 | * action (Play, Pause, Duration, Add Playlist, Downloaded, Clicked...), and an 31 | * optional event name and optional numeric value. 32 | * 33 | * @method Tracker trackPageView(string $customTitle = null) 34 | * Logs a visit to this page 35 | * 36 | * @method Tracker trackSiteSearch(string $keyword, string $category = null, 37 | * int $resultsCount = null) 38 | * Log an internal site search for a specific keyword, in an optional category, 39 | * specifying the optional count of search results in the page. 40 | * 41 | * @method Tracker trackGoal(int $idGoal, float $customRevenue = null) 42 | * Manually log a conversion for the numeric goal ID, with an optional numeric 43 | * custom revenue customRevenue. 44 | * 45 | * @method Tracker trackLink(string $url, string $linkType) 46 | * Manually log a click from your own code. url is the full URL which is to be 47 | * tracked as a click. linkType can either be 'link' for an outlink or 48 | * 'download' for a download. 49 | * 50 | * @method Tracker trackAllContentImpressions() 51 | * Scans the entire DOM for all content blocks and tracks all impressions once 52 | * the DOM ready event has been triggered. 53 | * 54 | * @method Tracker trackVisibleContentImpressions(bool $checkOnSroll, 55 | * int $timeIntervalInMs) 56 | * Scans the entire DOM for all content blocks as soon as the page is loaded. 57 | * It tracks an impression only if a content block is actually visible. 58 | * 59 | * @method Tracker trackContentImpression(string $contentName, 60 | * string $contentPiece, 61 | * string $contentTarget) 62 | * Tracks a content impression using the specified values. 63 | * 64 | * @method Tracker trackContentInteraction(string $contentInteraction, 65 | * string $contentName, 66 | * string $contentPiece, 67 | * string $contentTarget) 68 | * Tracks a content interaction using the specified values. 69 | * 70 | * @method Tracker logAllContentBlocksOnPage() 71 | * Log all found content blocks within a page to the console. This is useful to 72 | * debug / test content tracking. 73 | * 74 | * @method Tracker enableLinkTracking(bool $enable = null) 75 | * Install link tracking on all applicable link elements. Set the enable 76 | * parameter to true to use pseudo click-handler (treat middle click and open 77 | * contextmenu as left click). A right click (or any click that opens the 78 | * context menu) on a link will be tracked as clicked even if "Open in new tab" 79 | * is not selected. If "false" (default), nothing will be tracked on open 80 | * context menu or middle click. 81 | * 82 | * @method Tracker enableHeartBeatTimer(int $delayInSeconds) 83 | * Install a Heart beat timer that will regularly send requests to Matomo (every 84 | * delayInSeconds seconds) in order to better measure the time spent on the 85 | * page. These requests will be sent only when the user is actively viewing the 86 | * page (when the tab is active and in focus). These requests will not track 87 | * additional actions or pageviews. 88 | * 89 | * @method Tracker setDocumentTitle(string $title) 90 | * Override document.title 91 | * 92 | * @method Tracker setDomains(array $domains) 93 | * Set array of hostnames or domains to be treated as local. For wildcard 94 | * subdomains, you can use: '.example.com' or '*.example.com'. You can also 95 | * specify a path along a domain: '*.example.com/subsite1' 96 | * 97 | * @method Tracker setCustomUrl(string $customUrl) 98 | * Override the page's reported URL 99 | * 100 | * @method Tracker setReferrerUrl(string $referrerUrl) 101 | * Override the detected Http-Referer 102 | * 103 | * @method Tracker setSiteId(int $siteId) 104 | * Specify the website ID 105 | * 106 | * @method Tracker setApiUrl(string $apiUrl) 107 | * Specify the Matomo HTTP API URL endpoint. Points to the root directory of 108 | * matomo, e.g. http://matomo.example.org/ or https://example.org/matomo/. This 109 | * function is only useful when the 'Overlay' report is not working. By default 110 | * you do not need to use this function. 111 | * 112 | * @method Tracker setTrackerUrl(string $trackerUrl) 113 | * Specify the Matomo server URL. 114 | * 115 | * @method Tracker setDownloadClasses(string|array $downloadClasses) 116 | * Set classes to be treated as downloads (in addition to matomo_download) 117 | * 118 | * @method Tracker setDownloadExtensions(string|array $downloadExtensions) 119 | * Set list of file extensions to be recognized as downloads. Example: 'doc' or 120 | * ['doc', 'xls'] 121 | * 122 | * @method Tracker addDownloadExtensions(string|array $downloadExtensions) 123 | * Specify additional file extensions to be recognized as downloads. Example: 124 | * 'doc' or ['doc', 'xls'] 125 | * 126 | * @method Tracker removeDownloadExtensions(string|array $downloadExtensions) 127 | * Specify file extensions to be removed from the list of download file 128 | * extensions. Example: 'doc' or ['doc', 'xls'] 129 | * 130 | * @method Tracker setIgnoreClasses(string|array $ignoreClasses) 131 | * Set classes to be ignored if present in link (in addition to matomo_ignore) 132 | * 133 | * @method Tracker setLinkClasses(string|array $linkClasses) 134 | * Set classes to be treated as outlinks (in addition to matomo_link) 135 | * 136 | * @method Tracker setLinkTrackingTimer(int $linkTrackingTimer) 137 | * Set delay for link tracking in milliseconds. 138 | * 139 | * @method Tracker discardHashTag(bool $flag) 140 | * Set to true to not record the hash tag (anchor) portion of URLs 141 | * 142 | * @method Tracker setGenerationTimeMs(int $generationTime) 143 | * By default Matomo uses the browser DOM Timing API to accurately determine the 144 | * time it takes to generate and download the page. You may overwrite the value 145 | * by specifying a milliseconds value here. 146 | * 147 | * @method Tracker appendToTrackingUrl(string $appendToUrl) 148 | * Appends a custom string to the end of the HTTP request to matomo.php? 149 | * 150 | * @method Tracker setDoNotTrack(bool $flag) 151 | * Set to true to not track users who opt out of tracking using Mozilla's 152 | * (proposed) Do Not Track setting. 153 | * 154 | * @method Tracker disableCookies() 155 | * Disables all first party cookies. Existing Matomo cookies for this websites 156 | * will be deleted on the next page view. 157 | * 158 | * @method Tracker deleteCookies() 159 | * Deletes the tracking cookies currently set (this is useful when creating new 160 | * visits) 161 | * 162 | * @method Tracker killFrame() 163 | * Enables a frame-buster to prevent the tracked web page from being 164 | * framed/iframed. 165 | * 166 | * @method Tracker redirectFile(string $url) 167 | * Forces the browser load the live URL if the tracked web page is loaded from a 168 | * local file (e.g., saved to someone's desktop). 169 | * 170 | * @method Tracker setHeartBeatTimer(int $minimumVisitLength, 171 | * int $heartBeatDelay) 172 | * Records how long the page has been viewed if the $minimumVisitLength (in 173 | * seconds) is attained; the $heartBeatDelay determines how frequently to update 174 | * the server. 175 | * 176 | * @method Tracker setUserId(string $userId) 177 | * Sets a User ID to this user (such as an email address or a username). 178 | * 179 | * @method Tracker setCustomVariable(int $index, string $name, string $value, 180 | * string $scope) 181 | * Set a custom variable. 182 | * 183 | * @method Tracker deleteCustomVariable(int $index, string $scope) 184 | * Delete a custom variable. 185 | * 186 | * @method Tracker storeCustomVariablesInCookie() 187 | * When called then the Custom Variables of scope "visit" will be stored 188 | * (persisted) in a first party cookie for the duration of the visit. This is 189 | * useful if you want to call getCustomVariable later in the visit. (by default 190 | * custom variables are not stored on the visitor's computer.) 191 | * 192 | * @method Tracker setCustomDimension(int $customDimensionId, 193 | * string $customDimensionValue) 194 | * Set a custom dimension. (requires Matomo 2.15.1 + Custom Dimensions plugin) 195 | * 196 | * @method Tracker deleteCustomDimension(int customDimensionId) 197 | * Delete a custom dimension. (requires Matomo 2.15.1 + Custom Dimensions plugin) 198 | * 199 | * @method Tracker setCampaignNameKey(string $name) 200 | * Set campaign name parameter(s). 201 | * 202 | * @method Tracker setCampaignKeywordKey(string $keyword) 203 | * Set campaign keyword parameter(s). 204 | * 205 | * @method Tracker setConversionAttributionFirstReferrer(bool $flag) 206 | * Set to true to attribute a conversion to the first referrer. By default, 207 | * conversion is attributed to the most recent referrer. 208 | * 209 | * @method Tracker setEcommerceView(string $sku, string $productName, 210 | * string $categoryName, float $price) 211 | * Sets the current page view as a product or category page view. When you call 212 | * setEcommerceView it must be followed by a call to trackPageView to record the 213 | * product or category page view. 214 | * 215 | * @method Tracker addEcommerceItem(string $sku, string $productName = null, 216 | * string $categoryName = null, 217 | * float $price = null, float $quantity = null) 218 | * Adds a product into the ecommerce order. Must be called for each product in 219 | * the order. 220 | * 221 | * @method Tracker trackEcommerceCartUpdate(float $grandTotal) 222 | * Tracks a shopping cart. Call this function every time a user is adding, 223 | * updating or deleting a product from the cart. 224 | * 225 | * @method Tracker trackEcommerceOrder(string $orderId, float $grandTotal, 226 | * float $subTotal = null, 227 | * float $tax = null, 228 | * float $shipping = null, 229 | * float $discount = null) 230 | * Tracks an Ecommerce order, including any ecommerce item previously added to 231 | * the order. orderId and grandTotal (ie. revenue) are required parameters. 232 | * 233 | * @method Tracker setCookieNamePrefix(string $prefix) 234 | * The default prefix is 'pk'. 235 | * 236 | * @method Tracker setCookieDomain(string $domain) 237 | * The default is the document domain; if your web site can be visited at both 238 | * www.example.com and example.com, you would use: '.example.com' or 239 | * '*.example.com' 240 | * 241 | * @method Tracker setCookiePath(string $path) 242 | * The default is '/'. 243 | * 244 | * @method Tracker setVisitorCookieTimeout(int $seconds) 245 | * The default is 13 months 246 | * 247 | * @method Tracker setReferralCookieTimeout(int $seconds) 248 | * The default is 6 months 249 | * 250 | * @method Tracker setSessionCookieTimeout(int $seconds) 251 | * The default is 30 minutes 252 | * 253 | * @see http://developer.matomo.org/api-reference/tracking-javascript 254 | */ 255 | class Tracker 256 | { 257 | 258 | /** 259 | * Action items 260 | * 261 | * @var \Chessio\Matomo\Model\Tracker\Action[] $_actions 262 | */ 263 | protected $_actions = []; 264 | 265 | /** 266 | * Tracker action factory instance 267 | * 268 | * @var \Chessio\Matomo\Model\Tracker\ActionFactory $_actionFactory 269 | */ 270 | protected $_actionFactory; 271 | 272 | /** 273 | * Constructor 274 | * 275 | * @param \Chessio\Matomo\Model\Tracker\ActionFactory $actionFactory 276 | */ 277 | public function __construct( 278 | \Chessio\Matomo\Model\Tracker\ActionFactory $actionFactory 279 | ) { 280 | $this->_actionFactory = $actionFactory; 281 | } 282 | 283 | /** 284 | * Push an action to this tracker 285 | * 286 | * @param \Chessio\Matomo\Model\Tracker\Action $action 287 | * @return \Chessio\Matomo\Model\Tracker 288 | */ 289 | public function push(Tracker\Action $action) 290 | { 291 | $this->_actions[] = $action; 292 | return $this; 293 | } 294 | 295 | /** 296 | * Get all actions in this tracker 297 | * 298 | * @return \Chessio\Matomo\Model\Tracker\Action[] 299 | */ 300 | public function getActions() 301 | { 302 | return $this->_actions; 303 | } 304 | 305 | /** 306 | * Get an array representation of this tracker 307 | * 308 | * @return array 309 | */ 310 | public function toArray() 311 | { 312 | $array = []; 313 | foreach ($this->getActions() as $action) { 314 | $array[] = $action->toArray(); 315 | } 316 | return $array; 317 | } 318 | 319 | /** 320 | * Magic action push function 321 | * 322 | * @param string $name 323 | * @param array $arguments 324 | * @return \Chessio\Matomo\Model\Tracker 325 | */ 326 | public function __call($name, $arguments) 327 | { 328 | return $this->push($this->_actionFactory->create([ 329 | 'name' => $name, 330 | 'args' => $arguments 331 | ])); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /Model/Tracker/Action.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Model\Tracker; 23 | 24 | /** 25 | * Matomo tracker action 26 | * 27 | */ 28 | class Action 29 | { 30 | 31 | /** 32 | * Action name 33 | * 34 | * @var string $_name 35 | */ 36 | protected $_name; 37 | 38 | /** 39 | * Action arguments 40 | * 41 | * @var array $_args 42 | */ 43 | protected $_args; 44 | 45 | /** 46 | * Constructor 47 | * 48 | * @param string $name 49 | * @param array $args 50 | */ 51 | public function __construct($name, array $args = []) 52 | { 53 | $this->_name = $name; 54 | $this->_args = $args; 55 | } 56 | 57 | /** 58 | * Get action name 59 | * 60 | * @return string 61 | */ 62 | public function getName() 63 | { 64 | return $this->_name; 65 | } 66 | 67 | /** 68 | * Get action arguments 69 | * 70 | * @return array 71 | */ 72 | public function getArgs() 73 | { 74 | return $this->_args; 75 | } 76 | 77 | /** 78 | * Get an array representation of this action 79 | * 80 | * @return array 81 | */ 82 | public function toArray() 83 | { 84 | $array = $this->getArgs(); 85 | array_unshift($array, $this->getName()); 86 | return $array; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Observer/BeforeTrackPageViewObserver.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Observer; 23 | 24 | use Magento\Framework\Event\ObserverInterface; 25 | 26 | /** 27 | * Observer for `matomo_track_page_view_before' 28 | * 29 | */ 30 | class BeforeTrackPageViewObserver implements ObserverInterface 31 | { 32 | 33 | /** 34 | * Matomo data helper 35 | * 36 | * @var \Chessio\Matomo\Helper\Data 37 | */ 38 | protected $_dataHelper; 39 | 40 | /** 41 | * Constructor 42 | * 43 | * @param \Chessio\Matomo\Helper\Data $dataHelper 44 | */ 45 | public function __construct(\Chessio\Matomo\Helper\Data $dataHelper) 46 | { 47 | $this->_dataHelper = $dataHelper; 48 | } 49 | 50 | /** 51 | * Push additional actions to tracker before `trackPageView' is added 52 | * 53 | * @param \Magento\Framework\Event\Observer $observer 54 | * @return \Chessio\Matomo\Observer\BeforeTrackPageViewObserver 55 | */ 56 | public function execute(\Magento\Framework\Event\Observer $observer) 57 | { 58 | $tracker = $observer->getEvent()->getTracker(); 59 | /** @var \Chessio\Matomo\Model\Tracker $tracker */ 60 | 61 | $this->_pushLinkTracking($tracker); 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * Push link tracking options to given tracker 68 | * 69 | * @param \Chessio\Matomo\Model\Tracker $tracker 70 | * @return \Chessio\Matomo\Observer\BeforeTrackPageViewObserver 71 | */ 72 | protected function _pushLinkTracking(\Chessio\Matomo\Model\Tracker $tracker) 73 | { 74 | if ($this->_dataHelper->isLinkTrackingEnabled()) { 75 | $tracker->enableLinkTracking(true); 76 | $delay = $this->_dataHelper->getLinkTrackingDelay(); 77 | if ($delay > 0) { 78 | $tracker->setLinkTrackingTimer($delay); 79 | } 80 | } 81 | return $this; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Observer/CartViewObserver.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Observer; 23 | 24 | use Magento\Framework\Event\ObserverInterface; 25 | 26 | /** 27 | * Observer for `controller_action_predispatch_checkout_cart_index' 28 | * 29 | */ 30 | class CartViewObserver implements ObserverInterface 31 | { 32 | 33 | /** 34 | * Matomo tracker instance 35 | * 36 | * @var \Chessio\Matomo\Model\Tracker 37 | */ 38 | protected $_matomoTracker; 39 | 40 | /** 41 | * Tracker helper 42 | * 43 | * @var \Chessio\Matomo\Helper\Tracker $_trackerHelper 44 | */ 45 | protected $_trackerHelper; 46 | 47 | /** 48 | * Matomo data helper 49 | * 50 | * @var \Chessio\Matomo\Helper\Data $_dataHelper 51 | */ 52 | protected $_dataHelper; 53 | 54 | /** 55 | * Checkout session instance 56 | * 57 | * @var \Magento\Checkout\Model\Session $_checkoutSession 58 | */ 59 | protected $_checkoutSession; 60 | 61 | /** 62 | * Constructor 63 | * 64 | * @param \Chessio\Matomo\Model\Tracker $matomoTracker 65 | * @param \Chessio\Matomo\Helper\Tracker $trackerHelper 66 | * @param \Chessio\Matomo\Helper\Data $dataHelper 67 | * @param \Magento\Checkout\Model\Session\Proxy $checkoutSession 68 | */ 69 | public function __construct( 70 | \Chessio\Matomo\Model\Tracker $matomoTracker, 71 | \Chessio\Matomo\Helper\Tracker $trackerHelper, 72 | \Chessio\Matomo\Helper\Data $dataHelper, 73 | \Magento\Checkout\Model\Session\Proxy $checkoutSession 74 | ) { 75 | $this->_matomoTracker = $matomoTracker; 76 | $this->_trackerHelper = $trackerHelper; 77 | $this->_dataHelper = $dataHelper; 78 | $this->_checkoutSession = $checkoutSession; 79 | } 80 | 81 | /** 82 | * Push trackEcommerceCartUpdate to tracker on cart view page 83 | * 84 | * @param \Magento\Framework\Event\Observer $observer 85 | * @return \Chessio\Matomo\Observer\CartViewObserver 86 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 87 | */ 88 | public function execute(\Magento\Framework\Event\Observer $observer) 89 | { 90 | if ($this->_dataHelper->isTrackingEnabled()) { 91 | $this->_trackerHelper->addQuote( 92 | $this->_checkoutSession->getQuote(), 93 | $this->_matomoTracker 94 | ); 95 | } 96 | return $this; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Observer/CategoryViewObserver.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Observer; 23 | 24 | use Magento\Framework\Event\ObserverInterface; 25 | 26 | /** 27 | * Observer for `catalog_controller_category_init_after' 28 | * 29 | */ 30 | class CategoryViewObserver implements ObserverInterface 31 | { 32 | 33 | /** 34 | * Matomo tracker instance 35 | * 36 | * @var \Chessio\Matomo\Model\Tracker 37 | */ 38 | protected $_matomoTracker; 39 | 40 | /** 41 | * Matomo data helper 42 | * 43 | * @var \Chessio\Matomo\Helper\Data $_dataHelper 44 | */ 45 | protected $_dataHelper; 46 | 47 | /** 48 | * Constructor 49 | * 50 | * @param \Chessio\Matomo\Model\Tracker $matomoTracker 51 | * @param \Chessio\Matomo\Helper\Data $dataHelper 52 | */ 53 | public function __construct( 54 | \Chessio\Matomo\Model\Tracker $matomoTracker, 55 | \Chessio\Matomo\Helper\Data $dataHelper 56 | ) { 57 | $this->_matomoTracker = $matomoTracker; 58 | $this->_dataHelper = $dataHelper; 59 | } 60 | 61 | /** 62 | * Push EcommerceView to tracker on category view page 63 | * 64 | * @param \Magento\Framework\Event\Observer $observer 65 | * @return \Chessio\Matomo\Observer\CategoryViewObserver 66 | */ 67 | public function execute(\Magento\Framework\Event\Observer $observer) 68 | { 69 | if (!$this->_dataHelper->isTrackingEnabled()) { 70 | return $this; 71 | } 72 | 73 | $category = $observer->getEvent()->getCategory(); 74 | /** @var \Magento\Catalog\Model\Category $category */ 75 | 76 | $this->_matomoTracker->setEcommerceView( 77 | false, 78 | false, 79 | $category->getName() 80 | ); 81 | 82 | return $this; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Observer/CheckoutSuccessObserver.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Observer; 23 | 24 | use Magento\Framework\Event\ObserverInterface; 25 | 26 | /** 27 | * Observer for `controller_action_predispatch_checkout_cart_index' 28 | * 29 | * @see https://matomo.org/docs/ecommerce-analytics/ 30 | */ 31 | class CheckoutSuccessObserver implements ObserverInterface 32 | { 33 | 34 | /** 35 | * Matomo tracker instance 36 | * 37 | * @var \Chessio\Matomo\Model\Tracker $_matomoTracker 38 | */ 39 | protected $_matomoTracker; 40 | 41 | /** 42 | * Matomo data helper 43 | * 44 | * @var \Chessio\Matomo\Helper\Data $_dataHelper 45 | */ 46 | protected $_dataHelper; 47 | 48 | /** 49 | * Matomo tracker helper 50 | * 51 | * @var \Chessio\Matomo\Helper\Tracker $_trackerHelper 52 | */ 53 | protected $_trackerHelper; 54 | 55 | /** 56 | * Sales order repository 57 | * 58 | * @var \Magento\Sales\Api\OrderRepositoryInterface $_orderRepository 59 | */ 60 | protected $_orderRepository; 61 | 62 | /** 63 | * Search criteria builder 64 | * 65 | * @var \Magento\Framework\Api\SearchCriteriaBuilder $_searchCriteriaBuilder 66 | */ 67 | protected $_searchCriteriaBuilder; 68 | 69 | /** 70 | * Constructor 71 | * 72 | * @param \Chessio\Matomo\Model\Tracker $matomoTracker 73 | * @param \Chessio\Matomo\Helper\Data $dataHelper 74 | * @param \Chessio\Matomo\Helper\Tracker $trackerHelper 75 | * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository 76 | * @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder 77 | */ 78 | public function __construct( 79 | \Chessio\Matomo\Model\Tracker $matomoTracker, 80 | \Chessio\Matomo\Helper\Data $dataHelper, 81 | \Chessio\Matomo\Helper\Tracker $trackerHelper, 82 | \Magento\Sales\Api\OrderRepositoryInterface $orderRepository, 83 | \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder 84 | ) { 85 | $this->_matomoTracker = $matomoTracker; 86 | $this->_dataHelper = $dataHelper; 87 | $this->_trackerHelper = $trackerHelper; 88 | $this->_orderRepository = $orderRepository; 89 | $this->_searchCriteriaBuilder = $searchCriteriaBuilder; 90 | } 91 | 92 | /** 93 | * Push trackEcommerceOrder to tracker on checkout success page 94 | * 95 | * @param \Magento\Framework\Event\Observer $observer 96 | * @return \Chessio\Matomo\Observer\CheckoutSuccessObserver 97 | */ 98 | public function execute(\Magento\Framework\Event\Observer $observer) 99 | { 100 | $orderIds = $observer->getEvent()->getOrderIds(); 101 | if (!$this->_dataHelper->isTrackingEnabled() 102 | || empty($orderIds) || !is_array($orderIds) 103 | ) { 104 | return $this; 105 | } 106 | 107 | $searchCriteria = $this->_searchCriteriaBuilder 108 | ->addFilter('entity_id', $orderIds, 'in') 109 | ->create(); 110 | 111 | $searchResult = $this->_orderRepository->getList($searchCriteria); 112 | 113 | $this->_trackerHelper->addOrders( 114 | $searchResult->getItems(), 115 | $this->_matomoTracker 116 | ); 117 | 118 | return $this; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Observer/ProductViewObserver.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Observer; 23 | 24 | use Magento\Framework\Event\ObserverInterface; 25 | 26 | /** 27 | * Observer for `catalog_controller_product_view' 28 | * 29 | */ 30 | class ProductViewObserver implements ObserverInterface 31 | { 32 | 33 | /** 34 | * Matomo tracker instance 35 | * 36 | * @var \Chessio\Matomo\Model\Tracker 37 | */ 38 | protected $_matomoTracker; 39 | 40 | /** 41 | * Matomo data helper 42 | * 43 | * @var \Chessio\Matomo\Helper\Data $_dataHelper 44 | */ 45 | protected $_dataHelper; 46 | 47 | /** 48 | * Constructor 49 | * 50 | * @param \Chessio\Matomo\Model\Tracker $matomoTracker 51 | * @param \Chessio\Matomo\Helper\Data $dataHelper 52 | */ 53 | public function __construct( 54 | \Chessio\Matomo\Model\Tracker $matomoTracker, 55 | \Chessio\Matomo\Helper\Data $dataHelper 56 | ) { 57 | $this->_matomoTracker = $matomoTracker; 58 | $this->_dataHelper = $dataHelper; 59 | } 60 | 61 | /** 62 | * Push EcommerceView to tracker on product view page 63 | * 64 | * @param \Magento\Framework\Event\Observer $observer 65 | * @return \Chessio\Matomo\Observer\ProductViewObserver 66 | */ 67 | public function execute(\Magento\Framework\Event\Observer $observer) 68 | { 69 | if (!$this->_dataHelper->isTrackingEnabled()) { 70 | return $this; 71 | } 72 | 73 | $product = $observer->getEvent()->getProduct(); 74 | /** @var \Magento\Catalog\Model\Product $product */ 75 | 76 | $category = $product->getCategory(); 77 | $this->_matomoTracker->setEcommerceView( 78 | $product->getSku(), 79 | $product->getName(), 80 | $category 81 | ? $category->getName() 82 | : false, 83 | $product->getFinalPrice() 84 | ); 85 | 86 | return $this; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Observer/SearchResultObserver.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Observer; 23 | 24 | use Magento\Framework\Event\ObserverInterface; 25 | 26 | /** 27 | * Observer for `controller_action_layout_render_before_catalogsearch_result_index' 28 | * 29 | * @see http://developer.matomo.org/guides/tracking-javascript-guide#internal-search-tracking 30 | */ 31 | class SearchResultObserver implements ObserverInterface 32 | { 33 | 34 | /** 35 | * Matomo tracker instance 36 | * 37 | * @var \Chessio\Matomo\Model\Tracker 38 | */ 39 | protected $_matomoTracker; 40 | 41 | /** 42 | * Matomo data helper 43 | * 44 | * @var \Chessio\Matomo\Helper\Data $_dataHelper 45 | */ 46 | protected $_dataHelper; 47 | 48 | /** 49 | * Search query factory 50 | * 51 | * @var \Magento\Search\Model\QueryFactory $_queryFactory 52 | */ 53 | protected $_queryFactory; 54 | 55 | /** 56 | * Current view 57 | * 58 | * @var \Magento\Framework\App\ViewInterface $_view 59 | */ 60 | protected $_view; 61 | 62 | /** 63 | * Constructor 64 | * 65 | * @param \Chessio\Matomo\Model\Tracker $matomoTracker 66 | * @param \Chessio\Matomo\Helper\Data $dataHelper 67 | * @param \Magento\Search\Model\QueryFactory $queryFactory 68 | * @param \Magento\Framework\App\ViewInterface $view 69 | */ 70 | public function __construct( 71 | \Chessio\Matomo\Model\Tracker $matomoTracker, 72 | \Chessio\Matomo\Helper\Data $dataHelper, 73 | \Magento\Search\Model\QueryFactory $queryFactory, 74 | \Magento\Framework\App\ViewInterface $view 75 | ) { 76 | $this->_matomoTracker = $matomoTracker; 77 | $this->_dataHelper = $dataHelper; 78 | $this->_queryFactory = $queryFactory; 79 | $this->_view = $view; 80 | } 81 | 82 | /** 83 | * Push `trackSiteSearch' to tracker on search result page 84 | * 85 | * @param \Magento\Framework\Event\Observer $observer 86 | * @return \Chessio\Matomo\Observer\SearchResultObserver 87 | * @SuppressWarnings(PHPMD.UnusedFormalParameter) 88 | */ 89 | public function execute(\Magento\Framework\Event\Observer $observer) 90 | { 91 | if (!$this->_dataHelper->isTrackingEnabled()) { 92 | return $this; 93 | } 94 | 95 | $query = $this->_queryFactory->get(); 96 | $matomoBlock = $this->_view->getLayout()->getBlock('matomo.tracker'); 97 | /** @var \Magento\Search\Model\Query $query */ 98 | /** @var \Chessio\Matomo\Block\Matomo $matomoBlock */ 99 | 100 | $keyword = $query->getQueryText(); 101 | $resultsCount = $query->getNumResults(); 102 | 103 | if ($resultsCount === null) { 104 | // If this is a new search query the result count hasn't been saved 105 | // yet so we have to fetch it from the search result block instead. 106 | $resultBock = $this->_view->getLayout()->getBlock('search.result'); 107 | /** @var \Magento\CatalogSearch\Block\Result $resultBock */ 108 | if ($resultBock) { 109 | $resultsCount = $resultBock->getResultCount(); 110 | } 111 | } 112 | 113 | if ($resultsCount === null) { 114 | $this->_matomoTracker->trackSiteSearch($keyword); 115 | } else { 116 | $this->_matomoTracker->trackSiteSearch( 117 | $keyword, 118 | false, 119 | (int) $resultsCount 120 | ); 121 | } 122 | 123 | if ($matomoBlock) { 124 | // Don't push `trackPageView' when `trackSiteSearch' is set 125 | $matomoBlock->setSkipTrackPageView(true); 126 | } 127 | 128 | return $this; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Matomo Integration for Magento 2 2 | 3 | *Chessio_Matomo* is a [Matomo](https://matomo.org/) web analytics module for the [Magento 2](https://magento.com/) eCommerce platform. Matomo is an extensible free/libre analytics tool that can be self-hosted, giving you complete data ownership. Chessio_Matomo lets you integrate Matomo with your Magento 2 store front. 4 | 5 | This module is the successor of [*Henhed_Piwik*](https://packagist.org/packages/henhed/module-piwik) and thus continues with its semantic versioning, beginning with version `v2.1.0` . If you're using a Magento version prior to 2.2, you'll need to stick to the 1.x releases of the original Henhed_Piwik. For manual installation, check out the [Releases archive](https://github.com/fnogatz/magento2-matomo/releases). For installation using [Composer](https://getcomposer.org/), you can use the *tilde* or *caret* version constraint operators (e.g. `~1.3` or `^1.3.1`). 6 | 7 | ## Installation 8 | 9 | To install Chessio_Matomo, download and extract the [main zip archive](https://github.com/fnogatz/magento2-matomo/archive/main.zip) and move the extracted folder to *app/code/Chessio/Matomo* in your Magento 2 installation directory. 10 | 11 | ```sh 12 | unzip magento2-matomo-main.zip 13 | mkdir app/code/Chessio 14 | mv magento2-matomo-main app/code/Chessio/Matomo 15 | ``` 16 | 17 | Alternatively, you can clone the Chessio_Matomo Git repository into *app/code/Chessio_Matomo*. 18 | 19 | ```sh 20 | git clone https://github.com/fnogatz/magento2-matomo.git app/code/Chessio/Matomo 21 | ``` 22 | 23 | Or, if you prefer, install it using [Composer](https://getcomposer.org/). 24 | 25 | ```sh 26 | composer require chessio/module-matomo 27 | ``` 28 | 29 | Finally, enable the module with the Magento CLI tool. 30 | 31 | ```sh 32 | php bin/magento module:enable Chessio_Matomo --clear-static-content 33 | ``` 34 | 35 | ## Configuration 36 | 37 | Once installed, configuration options can be found in the Magento 2 administration panel under *Stores/Configuration/Sales/Matomo API*. 38 | To start tracking, set *Enable Tracking* to *Yes*, enter the *Hostname* of your Matomo installation and click *Save Config*. If you have multiple websites in the same Matomo installation, make sure the *Site ID* configured in Magento is correct. 39 | 40 | ## Customization 41 | 42 | If you need to send some custom information to your Matomo server, Chessio_Matomo lets you do so using event observers. 43 | 44 | To set custom data on each page, use the `matomo_track_page_view_before` event. A tracker instance will be passed along with the event object to your observer's `execute` method. 45 | 46 | ```php 47 | public function execute(\Magento\Framework\Event\Observer $observer) 48 | { 49 | $tracker = $observer->getEvent()->getTracker(); 50 | /** @var \Chessio\Matomo\Model\Tracker $tracker */ 51 | $tracker->setDocumentTitle('My Custom Title'); 52 | } 53 | ``` 54 | 55 | If you only want to add data under some specific circumstance, find a suitable event and request the tracker singleton in your observer's constructor. Store the tracker in a class member variable for later use in the `execute` method. 56 | 57 | ```php 58 | public function __construct(\Chessio\Matomo\Model\Tracker $matomoTracker) 59 | { 60 | $this->_matomoTracker = $matomoTracker; 61 | } 62 | ``` 63 | 64 | Beware of tracking user specific information on the server side as it will most likely cause caching problems. Instead, use Javascript to retrieve the user data from a cookie, localStorage or some Ajax request and then push the data to Matomo using either the Chessio_Matomo JS component... 65 | 66 | ```js 67 | require(['Chessio_Matomo/js/tracker'], function (trackerComponent) { 68 | trackerComponent.getTracker().done(function (tracker) { 69 | // Do something with tracker 70 | }); 71 | }); 72 | ``` 73 | 74 | ... or the vanilla Matomo approach: 75 | 76 | ```js 77 | var _paq = _paq || []; 78 | _paq.push(['setDocumentTitle', 'My Custom Title']); 79 | ``` 80 | 81 | See the [Matomo Developer Docs](https://developer.matomo.org/api-reference/tracking-javascript) or the [\Chessio\Matomo\Model\Tracker](https://github.com/fnogatz/magento2-matomo/blob/main/Model/Tracker.php) source code for a list of all methods available in the Tracking API. 82 | -------------------------------------------------------------------------------- /Test/Unit/CustomerData/Checkout/CartPluginTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\CustomerData\Checkout; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | 26 | /** 27 | * Test for \Chessio\Matomo\CustomerData\Checkout\CartPlugin 28 | * 29 | */ 30 | class CartPluginTest extends \PHPUnit\Framework\TestCase 31 | { 32 | 33 | /** 34 | * Customer data checkout cart plugin (test subject) instance 35 | * 36 | * @var \Chessio\Matomo\CustomerData\Checkout\CartPlugin $_cartPlugin 37 | */ 38 | protected $_cartPlugin; 39 | 40 | /** 41 | * Cart customer data mock object 42 | * 43 | * @var \PHPUnit_Framework_MockObject_MockObject $_cartMock 44 | */ 45 | protected $_cartMock; 46 | 47 | /** 48 | * Quote model mock object 49 | * 50 | * @var \PHPUnit_Framework_MockObject_MockObject $_quoteMock 51 | */ 52 | protected $_quoteMock; 53 | 54 | /** 55 | * Matomo data helper mock object 56 | * 57 | * @var \PHPUnit_Framework_MockObject_MockObject $_dataHelperMock 58 | */ 59 | protected $_dataHelperMock; 60 | 61 | /** 62 | * Tracker model mock object 63 | * 64 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerMock 65 | */ 66 | protected $_trackerMock; 67 | 68 | /** 69 | * Tracker helper mock object 70 | * 71 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerHelperMock 72 | */ 73 | protected $_trackerHelperMock; 74 | 75 | /** 76 | * Set up 77 | * 78 | * @return void 79 | */ 80 | public function setUp(): void 81 | { 82 | $className = \Chessio\Matomo\CustomerData\Checkout\CartPlugin::class; 83 | $objectManager = new ObjectManager($this); 84 | $sessionProxyClass = \Magento\Checkout\Model\Session\Proxy::class; 85 | $arguments = $objectManager->getConstructArguments($className, [ 86 | 'checkoutSession' => $this->getMockBuilder($sessionProxyClass) 87 | ->disableOriginalConstructor() 88 | ->setMethods(['getQuote']) 89 | ->getMock(), 90 | 'trackerFactory' => $this->createPartialMock( 91 | \Chessio\Matomo\Model\TrackerFactory::class, 92 | ['create'] 93 | ) 94 | ]); 95 | $this->_cartPlugin = $objectManager->getObject($className, $arguments); 96 | 97 | $this->_quoteMock = $this->createPartialMock( 98 | \Magento\Quote\Model\Quote::class, 99 | ['getId'] 100 | ); 101 | $arguments['checkoutSession'] 102 | ->expects($this->any()) 103 | ->method('getQuote') 104 | ->willReturn($this->_quoteMock); 105 | 106 | $this->_dataHelperMock = $arguments['dataHelper']; 107 | $this->_trackerMock = $this->createMock( 108 | \Chessio\Matomo\Model\Tracker::class 109 | ); 110 | $arguments['trackerFactory'] 111 | ->expects($this->any()) 112 | ->method('create') 113 | ->willReturn($this->_trackerMock); 114 | 115 | $this->_trackerHelperMock = $arguments['trackerHelper']; 116 | 117 | $this->_cartMock = $this->createMock( 118 | \Magento\Checkout\CustomerData\Cart::class 119 | ); 120 | } 121 | 122 | /** 123 | * Test \Chessio\Matomo\CustomerData\Checkout\CartPlugin::afterGetSectionData 124 | * with existing quote. 125 | * 126 | * @return void 127 | */ 128 | public function testafterGetSectionData() 129 | { 130 | $expectedResult = ['matomoActions' => ['someKey' => 'someValue']]; 131 | 132 | // Enable tracking 133 | $this->_dataHelperMock 134 | ->expects($this->once()) 135 | ->method('isTrackingEnabled') 136 | ->willReturn(true); 137 | 138 | // Give ID to quote mock 139 | $this->_quoteMock 140 | ->expects($this->once()) 141 | ->method('getId') 142 | ->willReturn(1); 143 | 144 | // Make sure tracker helpers' `addQuote' is called exactly once 145 | $this->_trackerHelperMock 146 | ->expects($this->once()) 147 | ->method('addQuote') 148 | ->with($this->_quoteMock, $this->_trackerMock) 149 | ->willReturn($this->_trackerHelperMock); 150 | 151 | // Make tracker mock return expected data 152 | $this->_trackerMock 153 | ->expects($this->once()) 154 | ->method('toArray') 155 | ->willReturn($expectedResult['matomoActions']); 156 | 157 | // Assert that result of plugin equals expected result 158 | $this->assertEquals( 159 | $expectedResult, 160 | $this->_cartPlugin->afterGetSectionData($this->_cartMock, []) 161 | ); 162 | } 163 | 164 | /** 165 | * Test \Chessio\Matomo\CustomerData\Checkout\CartPlugin::afterGetSectionData 166 | * with empty quote. 167 | * 168 | * @return void 169 | */ 170 | public function testafterGetSectionDataWithEmptyQuote() 171 | { 172 | // Enable tracking 173 | $this->_dataHelperMock 174 | ->expects($this->once()) 175 | ->method('isTrackingEnabled') 176 | ->willReturn(true); 177 | 178 | // Make sure tracker methods are never called 179 | $this->_trackerHelperMock 180 | ->expects($this->never()) 181 | ->method('addQuote'); 182 | $this->_trackerMock 183 | ->expects($this->never()) 184 | ->method('toArray'); 185 | 186 | // Assert that result of plugin is same as input 187 | $result = ['someKey' => 'someValue']; 188 | $this->assertEquals( 189 | $result, 190 | $this->_cartPlugin->afterGetSectionData($this->_cartMock, $result) 191 | ); 192 | } 193 | 194 | /** 195 | * Test \Chessio\Matomo\CustomerData\Checkout\CartPlugin::afterGetSectionData 196 | * with tracking disabled. 197 | * 198 | * @return void 199 | */ 200 | public function testafterGetSectionDataWithTrackingDisabled() 201 | { 202 | // Disable tracking 203 | $this->_dataHelperMock 204 | ->expects($this->once()) 205 | ->method('isTrackingEnabled') 206 | ->willReturn(false); 207 | 208 | // Give ID to quote mock 209 | $this->_quoteMock 210 | ->expects($this->any()) 211 | ->method('getId') 212 | ->willReturn(1); 213 | 214 | // Make sure tracker methods are never called 215 | $this->_trackerHelperMock 216 | ->expects($this->never()) 217 | ->method('addQuote'); 218 | $this->_trackerMock 219 | ->expects($this->never()) 220 | ->method('toArray'); 221 | 222 | // Assert that result of plugin is same as input 223 | $result = ['someKey' => 'someValue']; 224 | $this->assertEquals( 225 | $result, 226 | $this->_cartPlugin->afterGetSectionData($this->_cartMock, $result) 227 | ); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /Test/Unit/CustomerData/Customer/CustomerPluginTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\CustomerData\Customer; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | 26 | /** 27 | * Test for \Chessio\Matomo\CustomerData\Customer\CustomerPlugin 28 | * 29 | */ 30 | class CustomerPluginTest extends \PHPUnit\Framework\TestCase 31 | { 32 | 33 | /** 34 | * Customer data plugin (test subject) instance 35 | * 36 | * @var \Chessio\Matomo\CustomerData\Customer\CustomerPlugin $_customerPlugin 37 | */ 38 | protected $_customerPlugin; 39 | 40 | /** 41 | * Current customer helper mock object 42 | * 43 | * @var \PHPUnit_Framework_MockObject_MockObject $_currentCustomerMock 44 | */ 45 | protected $_currentCustomerMock; 46 | 47 | /** 48 | * Matomo data helper mock object 49 | * 50 | * @var \PHPUnit_Framework_MockObject_MockObject $_dataHelperMock 51 | */ 52 | protected $_dataHelperMock; 53 | 54 | /** 55 | * Matomo user ID provider pool mock object 56 | * 57 | * @var \PHPUnit_Framework_MockObject_MockObject $_uidProviderPoolMock 58 | */ 59 | protected $_uidProviderPoolMock; 60 | 61 | /** 62 | * Matomo user ID provider mock object 63 | * 64 | * @var \PHPUnit_Framework_MockObject_MockObject $_uidProviderMock 65 | */ 66 | protected $_uidProviderMock; 67 | 68 | /** 69 | * Customer data mock object 70 | * 71 | * @var \PHPUnit_Framework_MockObject_MockObject $_customerDataMock 72 | */ 73 | protected $_customerDataMock; 74 | 75 | /** 76 | * Set up 77 | * 78 | * @return void 79 | */ 80 | public function setUp(): void 81 | { 82 | $className = \Chessio\Matomo\CustomerData\Customer\CustomerPlugin::class; 83 | $objectManager = new ObjectManager($this); 84 | $args = $objectManager->getConstructArguments($className); 85 | $this->_customerPlugin = $objectManager->getObject($className, $args); 86 | $this->_currentCustomerMock = $args['currentCustomer']; 87 | $this->_dataHelperMock = $args['dataHelper']; 88 | $this->_uidProviderPoolMock = $args['uidProviderPool']; 89 | $this->_uidProviderMock = $this->createPartialMock( 90 | \Chessio\Matomo\UserId\Provider\ProviderInterface::class, 91 | ['getUserId', 'getTitle'] 92 | ); 93 | $this->_customerDataMock = $this->createMock( 94 | \Magento\Customer\CustomerData\Customer::class 95 | ); 96 | } 97 | 98 | /** 99 | * Data provider for `testafterGetSectionData' 100 | * 101 | * @return array 102 | */ 103 | public function testafterGetSectionDataDataProvider() 104 | { 105 | return [ 106 | [false, 1, 'p', 'UID1'], 107 | [true, null, 'p', 'UID2'], 108 | [true, 3, 'p', ''], 109 | [true, 4, null, 'UID4'], 110 | [true, 5, 'p', 'UID5'] 111 | ]; 112 | } 113 | 114 | /** 115 | * Test `afterGetSectionData' 116 | * 117 | * @param bool $enabled 118 | * @param int $customerId 119 | * @param string|null $provider 120 | * @param string $userId 121 | * @return void 122 | * @dataProvider testafterGetSectionDataDataProvider 123 | */ 124 | public function testafterGetSectionData( 125 | $enabled, 126 | $customerId, 127 | $provider, 128 | $userId 129 | ) { 130 | $expectedResult = []; 131 | if ($enabled && $customerId && $provider && $userId) { 132 | $expectedResult['matomoUserId'] = $userId; 133 | } 134 | 135 | $this->_dataHelperMock 136 | ->expects($this->once()) 137 | ->method('isTrackingEnabled') 138 | ->willReturn($enabled); 139 | 140 | $this->_dataHelperMock 141 | ->expects($enabled ? $this->once() : $this->never()) 142 | ->method('getUserIdProviderCode') 143 | ->willReturn($provider); 144 | 145 | $this->_currentCustomerMock 146 | ->expects($enabled ? $this->once() : $this->never()) 147 | ->method('getCustomerId') 148 | ->willReturn($customerId); 149 | 150 | $this->_uidProviderPoolMock 151 | ->expects( 152 | ($enabled && $provider) 153 | ? $this->once() 154 | : $this->never() 155 | ) 156 | ->method('getProviderByCode') 157 | ->with($provider) 158 | ->willReturn($this->_uidProviderMock); 159 | 160 | $this->_uidProviderMock 161 | ->expects( 162 | ($enabled && $customerId && $provider) 163 | ? $this->once() 164 | : $this->never() 165 | ) 166 | ->method('getUserId') 167 | ->with($customerId) 168 | ->willReturn($userId); 169 | 170 | // Assert that result of plugin equals expected result 171 | $this->assertEquals( 172 | $expectedResult, 173 | $this->_customerPlugin->afterGetSectionData( 174 | $this->_customerDataMock, 175 | [] 176 | ) 177 | ); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Test/Unit/Helper/DataTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\Helper; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | use Magento\Store\Model\ScopeInterface; 26 | 27 | /** 28 | * Test for \Chessio\Matomo\Helper\Data 29 | * 30 | */ 31 | class DataTest extends \PHPUnit\Framework\TestCase 32 | { 33 | 34 | /** 35 | * Matomo data helper (test subject) instance 36 | * 37 | * @var \Chessio\Matomo\Helper\Data $_helper 38 | */ 39 | protected $_helper; 40 | 41 | /** 42 | * Scope config mock object 43 | * 44 | * @var \PHPUnit_Framework_MockObject_MockObject 45 | */ 46 | protected $_scopeConfigMock; 47 | 48 | /** 49 | * Request mock object 50 | * 51 | * @var \PHPUnit_Framework_MockObject_MockObject 52 | */ 53 | protected $_requestMock; 54 | 55 | /** 56 | * Setup 57 | * 58 | * @return void 59 | */ 60 | public function setUp(): void 61 | { 62 | $className = \Chessio\Matomo\Helper\Data::class; 63 | $objectManager = new ObjectManager($this); 64 | $arguments = $objectManager->getConstructArguments($className); 65 | $this->_helper = $objectManager->getObject($className, $arguments); 66 | $context = $arguments['context']; 67 | /** @var \Magento\Framework\App\Helper\Context $context */ 68 | $this->_scopeConfigMock = $context->getScopeConfig(); 69 | $this->_requestMock = $context->getRequest(); 70 | } 71 | 72 | /** 73 | * Prepare scope config mock with given values 74 | * 75 | * @param string $enabled 76 | * @param string $hostname 77 | * @param string $siteId 78 | * @param string $linkEnabled 79 | * @param string $linkDelay 80 | * @param string $phpScriptPath 81 | * @param string $jsScriptPath 82 | * @param string $cdnHostname 83 | * @param string $scope 84 | * @param null|string|bool|int|\Magento\Store\Model\Store $store 85 | */ 86 | protected function _prepareScopeConfigMock( 87 | $enabled = null, 88 | $hostname = null, 89 | $siteId = null, 90 | $linkEnabled = null, 91 | $linkDelay = null, 92 | $phpScriptPath = null, 93 | $jsScriptPath = null, 94 | $cdnHostname = null, 95 | $scope = ScopeInterface::SCOPE_STORE, 96 | $store = null 97 | ) { 98 | $this->_scopeConfigMock 99 | ->expects($this->any()) 100 | ->method('isSetFlag') 101 | ->will($this->returnValueMap([ 102 | [ 103 | \Chessio\Matomo\Helper\Data::XML_PATH_ENABLED, 104 | $scope, $store, $enabled 105 | ], 106 | [ 107 | \Chessio\Matomo\Helper\Data::XML_PATH_LINK_ENABLED, 108 | $scope, $store, $linkEnabled 109 | ] 110 | ])); 111 | 112 | $this->_scopeConfigMock 113 | ->expects($this->any()) 114 | ->method('getValue') 115 | ->will($this->returnValueMap([ 116 | [ 117 | \Chessio\Matomo\Helper\Data::XML_PATH_HOSTNAME, 118 | $scope, $store, $hostname 119 | ], 120 | [ 121 | \Chessio\Matomo\Helper\Data::XML_PATH_SITE_ID, 122 | $scope, $store, $siteId 123 | ], 124 | [ 125 | \Chessio\Matomo\Helper\Data::XML_PATH_LINK_DELAY, 126 | $scope, $store, $linkDelay 127 | ], 128 | [ 129 | \Chessio\Matomo\Helper\Data::XML_PATH_PHP_SCRIPT_PATH, 130 | $scope, $store, $phpScriptPath 131 | ], 132 | [ 133 | \Chessio\Matomo\Helper\Data::XML_PATH_JS_SCRIPT_PATH, 134 | $scope, $store, $jsScriptPath 135 | ], 136 | [ 137 | \Chessio\Matomo\Helper\Data::XML_PATH_CDN_HOSTNAME, 138 | $scope, $store, $cdnHostname 139 | ] 140 | ])); 141 | } 142 | 143 | /** 144 | * Data provider for `testIsTrackingEnabled' 145 | * 146 | * @return array 147 | */ 148 | public function isTrackingEnabledDataProvider() 149 | { 150 | return [ 151 | [true, 'matomo.example.org', 1, true], 152 | [true, '', 1, false], 153 | [true, ' ', 1, false], 154 | [true, 'example.org/matomo', 0, false], 155 | [false, 'matomo.org', 1, false] 156 | ]; 157 | } 158 | 159 | /** 160 | * Test \Chessio\Matomo\Helper\Data::isTrackingEnabled 161 | * 162 | * Also covers `getHostname' and `getSiteId' 163 | * 164 | * @param bool $enabled 165 | * @param string $hostname 166 | * @param int $siteId 167 | * @param bool $returnValue 168 | * @return void 169 | * @dataProvider isTrackingEnabledDataProvider 170 | */ 171 | public function testIsTrackingEnabled( 172 | $enabled, 173 | $hostname, 174 | $siteId, 175 | $returnValue 176 | ) { 177 | $this->_prepareScopeConfigMock($enabled, $hostname, $siteId); 178 | $this->assertEquals($returnValue, $this->_helper->isTrackingEnabled()); 179 | } 180 | 181 | /** 182 | * Data provider for `testGetPhpScriptUrl' 183 | * 184 | * @return array 185 | */ 186 | public function phpScriptUrlDataProvider() 187 | { 188 | return [ 189 | [ 190 | 'matomo.org', 191 | false, // should prepend `http://' 192 | null, // should fall back on `matomo.php' 193 | // Expected result 194 | 'http://matomo.org/matomo.php' 195 | ], 196 | [ 197 | 'example.com/matomo', 198 | true, // should prepend `https://' 199 | 'tracker.php', // should override `matomo.php' 200 | // Expected result 201 | 'https://example.com/matomo/tracker.php' 202 | ], 203 | [ 204 | ' https://example.com/ ', // should be trimmed 205 | false, // should replace `https://' with `http://' 206 | ' /matomo/tracker.php ', // should be trimmed 207 | // Expected result 208 | 'http://example.com/matomo/tracker.php' 209 | ] 210 | ]; 211 | } 212 | 213 | /** 214 | * Test \Chessio\Matomo\Helper\Data::getPhpScriptUrl 215 | * 216 | * @param string $hostname 217 | * @param bool $isSecure 218 | * @param string $phpScriptPath 219 | * @param string $returnValue 220 | * @dataProvider phpScriptUrlDataProvider 221 | */ 222 | public function testGetPhpScriptUrl( 223 | $hostname, 224 | $isSecure, 225 | $phpScriptPath, 226 | $returnValue 227 | ) { 228 | $this->_prepareScopeConfigMock( 229 | null, 230 | $hostname, 231 | null, 232 | null, 233 | null, 234 | $phpScriptPath 235 | ); 236 | 237 | // Test explicit `isSecure' 238 | $this->assertEquals( 239 | $returnValue, 240 | $this->_helper->getPhpScriptUrl(null, $isSecure) 241 | ); 242 | 243 | // Test implicit `isSecure' 244 | $this->_requestMock 245 | ->expects($this->once()) 246 | ->method('isSecure') 247 | ->will($this->returnValue($isSecure)); 248 | 249 | $this->assertEquals($returnValue, $this->_helper->getPhpScriptUrl()); 250 | } 251 | 252 | /** 253 | * Data provider for `testGetJsScriptUrl' 254 | * 255 | * @return array 256 | */ 257 | public function jsScriptUrlDataProvider() 258 | { 259 | return [ 260 | [ 261 | 'matomo.org', 262 | false, // should prepend `http://' 263 | null, // should fall back on `matomo.js' 264 | null, // should fall back on regular hostname 265 | // Expected result 266 | 'http://matomo.org/matomo.js' 267 | ], 268 | [ 269 | ' matomo.org/path/ ', // should be trimmed 270 | true, // should prepend `https://' 271 | 'example.js', // should override `matomo.js' 272 | null, // should fall back on hostname 273 | // Expected result 274 | 'https://matomo.org/path/example.js' 275 | ], 276 | [ 277 | 'matomo.org', // should be ignored 278 | true, // should replace `http://' with `https://'' 279 | ' /to/tracker.js ', // should be trimmed 280 | 'http://cdn.example.com/path/', // should override hostname 281 | // Expected result 282 | 'https://cdn.example.com/path/to/tracker.js' 283 | ] 284 | ]; 285 | } 286 | 287 | /** 288 | * Test \Chessio\Matomo\Helper\Data::getJsScriptUrl 289 | * 290 | * @param string $hostname 291 | * @param bool $isSecure 292 | * @param string $jsScriptPath 293 | * @param string $cdnHostname 294 | * @param string $returnValue 295 | * @dataProvider jsScriptUrlDataProvider 296 | */ 297 | public function testGetJsScriptUrl( 298 | $hostname, 299 | $isSecure, 300 | $jsScriptPath, 301 | $cdnHostname, 302 | $returnValue 303 | ) { 304 | $this->_prepareScopeConfigMock( 305 | null, 306 | $hostname, 307 | null, 308 | null, 309 | null, 310 | null, 311 | $jsScriptPath, 312 | $cdnHostname 313 | ); 314 | 315 | // Test explicit `isSecure' 316 | $this->assertEquals( 317 | $returnValue, 318 | $this->_helper->getJsScriptUrl(null, $isSecure) 319 | ); 320 | 321 | // Test implicit `isSecure' 322 | $this->_requestMock 323 | ->expects($this->once()) 324 | ->method('isSecure') 325 | ->will($this->returnValue($isSecure)); 326 | 327 | $this->assertEquals($returnValue, $this->_helper->getJsScriptUrl()); 328 | } 329 | 330 | /** 331 | * Data provider for `testIsLinkTrackingEnabled' 332 | * 333 | * @return array 334 | */ 335 | public function isLinkTrackingEnabledDataProvider() 336 | { 337 | return [ 338 | [true, true, 'matomo.example.org', 1, true], 339 | [false, true, 'matomo.example.org', 2, false], 340 | [true, true, '', 1, false], 341 | [true, true, ' ', 1, false], 342 | [false, true, 'example.org/matomo', 0, false], 343 | [true, false, 'matomo.org', 1, false] 344 | ]; 345 | } 346 | 347 | /** 348 | * Test \Chessio\Matomo\Helper\Data::isLinkTrackingEnabled 349 | * 350 | * Also covers `isTrackingEnabled' 351 | * 352 | * @param bool $linkEnabled 353 | * @param bool $enabled 354 | * @param string $hostname 355 | * @param int $siteId 356 | * @param bool $returnValue 357 | * @dataProvider isLinkTrackingEnabledDataProvider 358 | */ 359 | public function testIsLinkTrackingEnabled( 360 | $linkEnabled, 361 | $enabled, 362 | $hostname, 363 | $siteId, 364 | $returnValue 365 | ) { 366 | $this->_prepareScopeConfigMock( 367 | $enabled, 368 | $hostname, 369 | $siteId, 370 | $linkEnabled 371 | ); 372 | 373 | $this->assertEquals( 374 | $returnValue, 375 | $this->_helper->isLinkTrackingEnabled() 376 | ); 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /Test/Unit/Helper/TrackerTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\Helper; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | 26 | /** 27 | * Test for \Chessio\Matomo\Helper\Tracker 28 | * 29 | */ 30 | class TrackerTest extends \PHPUnit\Framework\TestCase 31 | { 32 | 33 | /** 34 | * Matomo tracker helper (test subject) instance 35 | * 36 | * @var \Chessio\Matomo\Helper\Tracker $_helper 37 | */ 38 | protected $_helper; 39 | 40 | /** 41 | * Tracker instance 42 | * 43 | * @var \Chessio\Matomo\Model\Tracker $_tracker 44 | */ 45 | protected $_tracker; 46 | 47 | /** 48 | * Setup 49 | * 50 | * @return void 51 | */ 52 | public function setUp(): void 53 | { 54 | $objectManager = new ObjectManager($this); 55 | 56 | // Create test subject 57 | $this->_helper = $objectManager->getObject( 58 | \Chessio\Matomo\Helper\Tracker::class 59 | ); 60 | 61 | // Create tracker instance 62 | $class = \Chessio\Matomo\Model\Tracker::class; 63 | $arguments = $objectManager->getConstructArguments($class, [ 64 | 'actionFactory' => $this->createPartialMock( 65 | \Chessio\Matomo\Model\Tracker\ActionFactory::class, 66 | ['create'] 67 | ) 68 | ]); 69 | $arguments['actionFactory'] 70 | ->expects($this->any()) 71 | ->method('create') 72 | ->willReturnCallback(function ($data) { 73 | return new \Chessio\Matomo\Model\Tracker\Action( 74 | $data['name'], 75 | $data['args'] 76 | ); 77 | }); 78 | $this->_tracker = $objectManager->getObject($class, $arguments); 79 | } 80 | 81 | /** 82 | * Quote item data provider 83 | * 84 | * @return array 85 | */ 86 | public function addQuoteDataProvider() 87 | { 88 | return [ 89 | [ 90 | [ 91 | ['SKUA', 'First product', 123.45, 1], 92 | ['SKUB', 'Second product', 6780, 2] 93 | ], 94 | 123456.78 95 | ], 96 | [ 97 | [ 98 | ['', '', '123.45', '0'], 99 | [null, true, 0, false], 100 | [false, 0, -123, -2] 101 | ], 102 | null 103 | ] 104 | ]; 105 | } 106 | 107 | /** 108 | * Test for \Chessio\Matomo\Helper\Tracker::addQuote 109 | * 110 | * Also covers `addQuoteItem' and `addQuoteTotal' 111 | * 112 | * @param array $items 113 | * @param float $total 114 | * @dataProvider addQuoteDataProvider 115 | */ 116 | public function testAddQuote($items, $total) 117 | { 118 | // Build expected tracker result from $items and $total 119 | $expectedResult = []; 120 | foreach ($items as $item) { 121 | list($sku, $name, $price, $qty) = $item; 122 | $expectedResult[] = [ 123 | 'addEcommerceItem', 124 | $sku, 125 | $name, 126 | false, 127 | (float) $price, 128 | (float) $qty 129 | ]; 130 | } 131 | $expectedResult[] = ['trackEcommerceCartUpdate', (float) $total]; 132 | 133 | // Test `addQuote' with mock quote created from $items and $total 134 | $this->_helper->addQuote( 135 | $this->_getQuoteMock($items, $total), 136 | $this->_tracker 137 | ); 138 | $this->assertSame($expectedResult, $this->_tracker->toArray()); 139 | } 140 | 141 | /** 142 | * Order collection data provider 143 | * 144 | * @return array 145 | */ 146 | public function addOrdersDataProvider() 147 | { 148 | return [ 149 | [ 150 | // Sample orders data 151 | [ 152 | [ 153 | '100001', 123.45, 101, 10, 15, -5, 154 | [ 155 | ['sku1', 'Name 1', 50, 2, null], 156 | ['sku2', 'Name 2', 50, 3, null] 157 | ] 158 | ], 159 | [ 160 | '100002', '234.56', '201', '15', '20', '-50', 161 | [ 162 | ['sku2', 'Name 2 (2)', '60', '1', null], 163 | ['sku3', 'Name 3', '70', '2', null], 164 | ['sku4', 'Name 4', '0', '1', 1] 165 | ] 166 | ] 167 | ], 168 | // Expected tracker data 169 | [ 170 | // Same as `sku1' from `100001' 171 | ['addEcommerceItem', 'sku1', 'Name 1', false, 50.0, 2.0], 172 | 173 | // Aggregated data for `sku2' from `100001' *and* `100002' 174 | [ 175 | 'addEcommerceItem', 176 | 'sku2', 177 | 'Name 2', // Name from first occurance of `sku2' 178 | false, // No category name 179 | 52.5, // Sum of price / sum of qty (50*3 + 60)/4 180 | 4.0 // Sum of qty 181 | ], 182 | 183 | // Same as `sku3' from `100002' 184 | ['addEcommerceItem', 'sku3', 'Name 3', false, 70.0, 2.0], 185 | 186 | // `sku4' should be skipped as it's a child item 187 | 188 | // Aggregated order data from `100001' and `100002' 189 | [ 190 | 'trackEcommerceOrder', 191 | '100001, 100002', // Concat increment IDs 192 | 358.01, // 123.45 + 234.56 193 | 302.0, // 101 + 201 194 | 25.0, // 10 + 15 195 | 35.0, // 15 + 20 196 | 55.0 // abs(-5 + -50) 197 | ] 198 | ] 199 | ] 200 | ]; 201 | } 202 | 203 | /** 204 | * Test for \Chessio\Matomo\Helper\Tracker::addOrders 205 | * 206 | * @param array $ordersData 207 | * @param array $expectedResult 208 | * @return void 209 | * @dataProvider addOrdersDataProvider 210 | */ 211 | public function testAddOrders($ordersData, $expectedResult) 212 | { 213 | $orders = []; 214 | foreach ($ordersData as $orderData) { 215 | list($incrementId, $grandTotal, $subTotal, $tax, $shipping, 216 | $discount, $itemsData) = $orderData; 217 | $orders[] = $this->_getOrderMock( 218 | $incrementId, 219 | $grandTotal, 220 | $subTotal, 221 | $tax, 222 | $shipping, 223 | $discount, 224 | $itemsData 225 | ); 226 | } 227 | 228 | $this->assertSame( 229 | $this->_helper, 230 | $this->_helper->addOrders($orders, $this->_tracker) 231 | ); 232 | $this->assertEquals($expectedResult, $this->_tracker->toArray()); 233 | } 234 | 235 | /** 236 | * Create a mock quote object with given data 237 | * 238 | * @param array $items 239 | * @param float $total 240 | * @return \PHPUnit_Framework_MockObject_MockObject 241 | */ 242 | protected function _getQuoteMock($items, $total) 243 | { 244 | $quoteItems = []; 245 | foreach ($items as $itemData) { 246 | list($sku, $name, $price, $qty) = $itemData; 247 | $item = $this->createPartialMock( 248 | \Magento\Quote\Model\Quote\Item::class, 249 | ['getData'] 250 | ); 251 | $item 252 | ->expects($this->any()) 253 | ->method('getData') 254 | ->willReturnMap([ 255 | ['sku', null, $sku], 256 | ['name', null, $name], 257 | ['base_price_incl_tax', null, $price], 258 | ['qty', null, $qty] 259 | ]); 260 | $quoteItems[] = $item; 261 | } 262 | 263 | $quote = $this->createPartialMock( 264 | \Magento\Quote\Model\Quote::class, 265 | ['getAllVisibleItems', 'getData'] 266 | ); 267 | $quote 268 | ->expects($this->any()) 269 | ->method('getAllVisibleItems') 270 | ->willReturn($quoteItems); 271 | $quote 272 | ->expects($this->any()) 273 | ->method('getData') 274 | ->with('base_grand_total') 275 | ->willReturn($total); 276 | 277 | return $quote; 278 | } 279 | 280 | /** 281 | * Create order mock object 282 | * 283 | * @param string $incrementId 284 | * @param float $grandTotal 285 | * @param float $subTotal 286 | * @param float $tax 287 | * @param float $shipping 288 | * @param float $discount 289 | * @param array $itemsData 290 | * @return \PHPUnit_Framework_MockObject_MockObject 291 | */ 292 | protected function _getOrderMock( 293 | $incrementId, 294 | $grandTotal, 295 | $subTotal, 296 | $tax, 297 | $shipping, 298 | $discount, 299 | $itemsData 300 | ) { 301 | $items = []; 302 | foreach ($itemsData as $itemData) { 303 | list($sku, $name, $price, $qty, $parentId) = $itemData; 304 | $items[] = $this->createConfiguredMock( 305 | \Magento\Sales\Api\Data\OrderItemInterface::class, 306 | [ 307 | 'getSku' => $sku, 308 | 'getName' => $name, 309 | 'getBasePriceInclTax' => $price, 310 | 'getQtyOrdered' => $qty, 311 | 'getParentItemId' => $parentId 312 | ] 313 | ); 314 | } 315 | $orderMock = $this->createConfiguredMock( 316 | \Magento\Sales\Api\Data\OrderInterface::class, 317 | [ 318 | 'getIncrementId' => $incrementId, 319 | 'getBaseGrandTotal' => $grandTotal, 320 | 'getBaseSubtotalInclTax' => $subTotal, 321 | 'getBaseTaxAmount' => $tax, 322 | 'getBaseShippingInclTax' => $shipping, 323 | 'getBaseDiscountAmount' => $discount, 324 | 'getItems' => $items 325 | ] 326 | ); 327 | return $orderMock; 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /Test/Unit/Model/TrackerTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\Model; 23 | 24 | use \Chessio\Matomo\Model\Tracker; 25 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 26 | 27 | /** 28 | * Test for \Chessio\Matomo\Model\Tracker 29 | * 30 | */ 31 | class TrackerTest extends \PHPUnit\Framework\TestCase 32 | { 33 | 34 | /** 35 | * Tracker instance 36 | * 37 | * @var \Chessio\Matomo\Model\Tracker $_tracker 38 | */ 39 | protected $_tracker; 40 | 41 | /** 42 | * Action factory mock 43 | * 44 | * @var \PHPUnit_Framework_MockObject_MockObject $_actionFactory 45 | */ 46 | protected $_actionFactory; 47 | 48 | /** 49 | * Setup 50 | * 51 | * @return void 52 | */ 53 | public function setUp(): void 54 | { 55 | $className = \Chessio\Matomo\Model\Tracker::class; 56 | $objectManager = new ObjectManager($this); 57 | $arguments = $objectManager->getConstructArguments($className, [ 58 | 'actionFactory' => $this->createPartialMock( 59 | \Chessio\Matomo\Model\Tracker\ActionFactory::class, 60 | ['create'] 61 | ) 62 | ]); 63 | $this->_tracker = $objectManager->getObject($className, $arguments); 64 | $this->_actionFactory = $arguments['actionFactory']; 65 | } 66 | 67 | /** 68 | * Test tracker action push 69 | * 70 | * Covers Tracker::push and Tracker::toArray 71 | * 72 | * @param string $name 73 | * @param array $args 74 | * @dataProvider trackerActionDataProvider 75 | */ 76 | public function testPush($name, $args) 77 | { 78 | $this->_tracker->push(new Tracker\Action($name, $args)); 79 | $this->assertEquals( 80 | [array_merge([$name], $args)], 81 | $this->_tracker->toArray() 82 | ); 83 | } 84 | 85 | /** 86 | * Test magic tracker action push 87 | * 88 | * Covers Tracker::__call and Tracker::toArray 89 | * 90 | * @param string $name 91 | * @param array $args 92 | * @dataProvider trackerActionDataProvider 93 | */ 94 | public function testMagicPush($name, $args) 95 | { 96 | $this->_actionFactory 97 | ->expects($this->once()) 98 | ->method('create') 99 | ->with([ 100 | 'name' => $name, 101 | 'args' => $args 102 | ]) 103 | ->will($this->returnValue(new Tracker\Action($name, $args))); 104 | 105 | // @codingStandardsIgnoreStart 106 | call_user_func_array([$this->_tracker, $name], $args); 107 | // @codingStandardsIgnoreEnd 108 | 109 | $this->assertEquals( 110 | [array_merge([$name], $args)], 111 | $this->_tracker->toArray() 112 | ); 113 | } 114 | 115 | /** 116 | * Tracker action data provider 117 | * 118 | * @return array 119 | */ 120 | public function trackerActionDataProvider() 121 | { 122 | return [ 123 | ['trackEvent', ['category', 'action', 'name', 1]], 124 | ['trackPageView', ['customTitle']], 125 | ['trackSiteSearch', ['keyword', 'category', 0]], 126 | ['trackGoal', [1, 1.1]], 127 | ['trackLink', ['url', 'linkType']], 128 | ['disableCookies', []] 129 | ]; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /Test/Unit/Observer/BeforeTrackPageViewObserverTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\Observer; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | 26 | /** 27 | * Test for \Chessio\Matomo\Observer\BeforeTrackPageViewObserver 28 | * 29 | */ 30 | class BeforeTrackPageViewObserverTest extends \PHPUnit\Framework\TestCase 31 | { 32 | 33 | /** 34 | * Before track page view observer (test subject) instance 35 | * 36 | * @var \Chessio\Matomo\Observer\BeforeTrackPageViewObserver $_observer 37 | */ 38 | protected $_observer; 39 | 40 | /** 41 | * Matomo data helper mock object 42 | * 43 | * @var \PHPUnit_Framework_MockObject_MockObject $_dataHelperMock 44 | */ 45 | protected $_dataHelperMock; 46 | 47 | /** 48 | * Matomo tracker mock object 49 | * 50 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerMock 51 | */ 52 | protected $_trackerMock; 53 | 54 | /** 55 | * Event mock object 56 | * 57 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventMock 58 | */ 59 | protected $_eventMock; 60 | 61 | /** 62 | * Event observer mock object 63 | * 64 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventObserverMock 65 | */ 66 | protected $_eventObserverMock; 67 | 68 | /** 69 | * Set up 70 | * 71 | * @return void 72 | */ 73 | public function setUp(): void 74 | { 75 | $className = \Chessio\Matomo\Observer\BeforeTrackPageViewObserver::class; 76 | $objectManager = new ObjectManager($this); 77 | $arguments = $objectManager->getConstructArguments($className); 78 | $this->_observer = $objectManager->getObject($className, $arguments); 79 | $this->_dataHelperMock = $arguments['dataHelper']; 80 | $this->_trackerMock = $this->createPartialMock( 81 | \Chessio\Matomo\Model\Tracker::class, 82 | ['enableLinkTracking', 'setLinkTrackingTimer'] 83 | ); 84 | $this->_eventMock = $this->createPartialMock( 85 | \Magento\Framework\Event::class, 86 | ['getTracker'] 87 | ); 88 | $this->_eventObserverMock = $this->createMock( 89 | \Magento\Framework\Event\Observer::class 90 | ); 91 | } 92 | 93 | /** 94 | * Data provider for `testExecute' 95 | * 96 | * @return array 97 | */ 98 | public function executeDataProvider() 99 | { 100 | return [ 101 | [true, 500, $this->once(), $this->once()], 102 | [true, 0, $this->once(), $this->never()], 103 | [true, -1, $this->once(), $this->never()], 104 | [false, 500, $this->never(), $this->never()], 105 | [false, 0, $this->never(), $this->never()] 106 | ]; 107 | } 108 | 109 | /** 110 | * Test for \Chessio\Matomo\Observer\BeforeTrackPageViewObserver::execute 111 | * 112 | * @param bool $linkTrackingEnabled 113 | * @param int $linkTrackingDelay 114 | * @param \PHPUnit_Framework_MockObject_Matcher_Invocation $enableLinkTrackingMatcher 115 | * @param \PHPUnit_Framework_MockObject_Matcher_Invocation $setLinkTrackingTimerMatcher 116 | * @return void 117 | * @dataProvider executeDataProvider 118 | */ 119 | public function testExecute( 120 | $linkTrackingEnabled, 121 | $linkTrackingDelay, 122 | $enableLinkTrackingMatcher, 123 | $setLinkTrackingTimerMatcher 124 | ) { 125 | // Prepare observer mock 126 | $this->_eventObserverMock 127 | ->expects($this->once()) 128 | ->method('getEvent') 129 | ->willReturn($this->_eventMock); 130 | $this->_eventMock 131 | ->expects($this->once()) 132 | ->method('getTracker') 133 | ->willReturn($this->_trackerMock); 134 | 135 | // Prepare data helper mock 136 | $this->_dataHelperMock 137 | ->expects($this->once()) 138 | ->method('isLinkTrackingEnabled') 139 | ->willReturn($linkTrackingEnabled); 140 | $this->_dataHelperMock 141 | ->expects($this->any()) 142 | ->method('getLinkTrackingDelay') 143 | ->willReturn($linkTrackingDelay); 144 | 145 | // Prepare tracker mock 146 | $this->_trackerMock 147 | ->expects($enableLinkTrackingMatcher) 148 | ->method('enableLinkTracking') 149 | ->with(true) 150 | ->willReturn($this->_trackerMock); 151 | $this->_trackerMock 152 | ->expects($setLinkTrackingTimerMatcher) 153 | ->method('setLinkTrackingTimer') 154 | ->with($linkTrackingDelay) 155 | ->willReturn($this->_trackerMock); 156 | 157 | // Assert that `execute' returns $this 158 | $this->assertSame( 159 | $this->_observer, 160 | $this->_observer->execute($this->_eventObserverMock) 161 | ); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Test/Unit/Observer/CartViewObserverTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\Observer; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | 26 | /** 27 | * Test for \Chessio\Matomo\Observer\CartViewObserver 28 | * 29 | */ 30 | class CartViewObserverTest extends \PHPUnit\Framework\TestCase 31 | { 32 | 33 | /** 34 | * Cart view observer (test subject) instance 35 | * 36 | * @var \Chessio\Matomo\Observer\CartViewObserver 37 | */ 38 | protected $_observer; 39 | 40 | /** 41 | * Event observer mock object 42 | * 43 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventObserverMock 44 | */ 45 | protected $_eventObserverMock; 46 | 47 | /** 48 | * Tracker mock object 49 | * 50 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerMock 51 | */ 52 | protected $_trackerMock; 53 | 54 | /** 55 | * Tracker helper mock object 56 | * 57 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerHelperMock 58 | */ 59 | protected $_trackerHelperMock; 60 | 61 | /** 62 | * Matomo data helper mock object 63 | * 64 | * @var \PHPUnit_Framework_MockObject_MockObject $_dataHelperMock 65 | */ 66 | protected $_dataHelperMock; 67 | 68 | /** 69 | * Checkout session mock object 70 | * 71 | * @var \PHPUnit_Framework_MockObject_MockObject $_checkoutSessionMock 72 | */ 73 | protected $_checkoutSessionMock; 74 | 75 | /** 76 | * Quote mock object 77 | * 78 | * @var \PHPUnit_Framework_MockObject_MockObject $_quoteMock 79 | */ 80 | protected $_quoteMock; 81 | 82 | /** 83 | * Set up 84 | * 85 | * @return void 86 | */ 87 | public function setUp(): void 88 | { 89 | $className = \Chessio\Matomo\Observer\CartViewObserver::class; 90 | $objectManager = new ObjectManager($this); 91 | $sessionProxyClass = \Magento\Checkout\Model\Session\Proxy::class; 92 | $arguments = $objectManager->getConstructArguments($className, [ 93 | 'checkoutSession' => $this->getMockBuilder($sessionProxyClass) 94 | ->disableOriginalConstructor() 95 | ->setMethods(['getQuote']) 96 | ->getMock() 97 | ]); 98 | $this->_observer = $objectManager->getObject($className, $arguments); 99 | $this->_trackerMock = $arguments['matomoTracker']; 100 | $this->_trackerHelperMock = $arguments['trackerHelper']; 101 | $this->_dataHelperMock = $arguments['dataHelper']; 102 | $this->_checkoutSessionMock = $arguments['checkoutSession']; 103 | $this->_eventObserverMock = $this->createMock( 104 | \Magento\Framework\Event\Observer::class 105 | ); 106 | $this->_quoteMock = $this->createMock( 107 | \Magento\Quote\Model\Quote::class 108 | ); 109 | } 110 | 111 | /** 112 | * Test for \Chessio\Matomo\Observer\CartViewObserver::execute where 113 | * tracking is enabled. 114 | * 115 | * @return void 116 | */ 117 | public function testExecuteWithTrackingEnabled() 118 | { 119 | // Enable tracking 120 | $this->_dataHelperMock 121 | ->expects($this->once()) 122 | ->method('isTrackingEnabled') 123 | ->willReturn(true); 124 | 125 | // Provide quote mock access from checkout session mock 126 | $this->_checkoutSessionMock 127 | ->expects($this->any()) 128 | ->method('getQuote') 129 | ->willReturn($this->_quoteMock); 130 | 131 | // Make sure the tracker helpers `addQuote' is called exactly once with 132 | // provided quote and tracker. Actual behavior of `addQuote' is covered 133 | // by \Chessio\Matomo\Test\Unit\Helper\TrackerTest. 134 | $this->_trackerHelperMock 135 | ->expects($this->once()) 136 | ->method('addQuote') 137 | ->with($this->_quoteMock, $this->_trackerMock) 138 | ->willReturn($this->_trackerHelperMock); 139 | 140 | // Assert that `execute' returns $this 141 | $this->assertSame( 142 | $this->_observer, 143 | $this->_observer->execute($this->_eventObserverMock) 144 | ); 145 | } 146 | 147 | /** 148 | * Test for \Chessio\Matomo\Observer\CartViewObserver::execute where 149 | * tracking is disabled. 150 | * 151 | * @return void 152 | */ 153 | public function testExecuteWithTrackingDisabled() 154 | { 155 | // Disable tracking 156 | $this->_dataHelperMock 157 | ->expects($this->once()) 158 | ->method('isTrackingEnabled') 159 | ->willReturn(false); 160 | 161 | // Provide quote mock access from checkout session mock 162 | $this->_checkoutSessionMock 163 | ->expects($this->any()) 164 | ->method('getQuote') 165 | ->willReturn($this->_quoteMock); 166 | 167 | // Make sure the tracker helpers `addQuote' is never called 168 | $this->_trackerHelperMock 169 | ->expects($this->never()) 170 | ->method('addQuote'); 171 | 172 | // Assert that `execute' returns $this 173 | $this->assertSame( 174 | $this->_observer, 175 | $this->_observer->execute($this->_eventObserverMock) 176 | ); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /Test/Unit/Observer/CategoryViewObserverTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\Observer; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | 26 | /** 27 | * Test for \Chessio\Matomo\Observer\CategoryViewObserver 28 | * 29 | */ 30 | class CategoryViewObserverTest extends \PHPUnit\Framework\TestCase 31 | { 32 | 33 | /** 34 | * Category view observer (test subject) instance 35 | * 36 | * @var \Chessio\Matomo\Observer\CategoryViewObserver $_observer 37 | */ 38 | protected $_observer; 39 | 40 | /** 41 | * Matomo tracker mock object 42 | * 43 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerMock 44 | */ 45 | protected $_trackerMock; 46 | 47 | /** 48 | * Matomo data helper mock object 49 | * 50 | * @var \PHPUnit_Framework_MockObject_MockObject $_dataHelperMock 51 | */ 52 | protected $_dataHelperMock; 53 | 54 | /** 55 | * Event mock object 56 | * 57 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventMock 58 | */ 59 | protected $_eventMock; 60 | 61 | /** 62 | * Event observer mock object 63 | * 64 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventObserverMock 65 | */ 66 | protected $_eventObserverMock; 67 | 68 | /** 69 | * Category model mock object 70 | * 71 | * @var \PHPUnit_Framework_MockObject_MockObject $_categoryMock 72 | */ 73 | protected $_categoryMock; 74 | 75 | /** 76 | * Set up 77 | * 78 | * @return void 79 | */ 80 | public function setUp(): void 81 | { 82 | $className = \Chessio\Matomo\Observer\CategoryViewObserver::class; 83 | $objectManager = new ObjectManager($this); 84 | $arguments = $objectManager->getConstructArguments($className); 85 | $this->_trackerMock = $this->createPartialMock( 86 | \Chessio\Matomo\Model\Tracker::class, 87 | ['setEcommerceView'] 88 | ); 89 | $arguments['matomoTracker'] = $this->_trackerMock; 90 | $this->_observer = $objectManager->getObject($className, $arguments); 91 | $this->_dataHelperMock = $arguments['dataHelper']; 92 | $this->_eventMock = $this->createPartialMock( 93 | \Magento\Framework\Event::class, 94 | ['getCategory'] 95 | ); 96 | $this->_eventObserverMock = $this->createMock( 97 | \Magento\Framework\Event\Observer::class 98 | ); 99 | $this->_categoryMock = $this->createPartialMock( 100 | \Magento\Catalog\Model\Category::class, 101 | ['getName'] 102 | ); 103 | } 104 | 105 | /** 106 | * Test for \Chessio\Matomo\Observer\CategoryViewObserver::execute when Matomo 107 | * tracking is enabled. 108 | * 109 | * @return void 110 | */ 111 | public function testExecuteWithTrackingEnabled() 112 | { 113 | $categoryName = 'Some category name'; 114 | 115 | // Prepare mock objects 116 | $this->_dataHelperMock 117 | ->expects($this->once()) 118 | ->method('isTrackingEnabled') 119 | ->willReturn(true); 120 | $this->_eventObserverMock 121 | ->expects($this->once()) 122 | ->method('getEvent') 123 | ->willReturn($this->_eventMock); 124 | $this->_eventMock 125 | ->expects($this->once()) 126 | ->method('getCategory') 127 | ->willReturn($this->_categoryMock); 128 | $this->_categoryMock 129 | ->expects($this->once()) 130 | ->method('getName') 131 | ->willReturn($categoryName); 132 | 133 | // Make sure trackers' `setEcommerceView' is called exactly once 134 | $this->_trackerMock 135 | ->expects($this->once()) 136 | ->method('setEcommerceView') 137 | ->with(false, false, $categoryName) 138 | ->willReturn($this->_trackerMock); 139 | 140 | // Assert that `execute' returns $this 141 | $this->assertSame( 142 | $this->_observer, 143 | $this->_observer->execute($this->_eventObserverMock) 144 | ); 145 | } 146 | 147 | /** 148 | * Test for \Chessio\Matomo\Observer\CategoryViewObserver::execute when Matomo 149 | * tracking is disabled. 150 | * 151 | * @return void 152 | */ 153 | public function testExecuteWithTrackingDisabled() 154 | { 155 | // Prepare mock objects 156 | $this->_dataHelperMock 157 | ->expects($this->once()) 158 | ->method('isTrackingEnabled') 159 | ->willReturn(false); 160 | $this->_eventObserverMock 161 | ->expects($this->any()) 162 | ->method('getEvent') 163 | ->willReturn($this->_eventMock); 164 | $this->_eventMock 165 | ->expects($this->any()) 166 | ->method('getCategory') 167 | ->willReturn($this->_categoryMock); 168 | 169 | // Make sure trackers' `setEcommerceView' is never called 170 | $this->_trackerMock 171 | ->expects($this->never()) 172 | ->method('setEcommerceView'); 173 | 174 | // Assert that `execute' returns $this 175 | $this->assertSame( 176 | $this->_observer, 177 | $this->_observer->execute($this->_eventObserverMock) 178 | ); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Test/Unit/Observer/CheckoutSuccessObserverTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\Observer; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | 26 | /** 27 | * Test for \Chessio\Matomo\Observer\CheckoutSuccessObserver 28 | * 29 | */ 30 | class CheckoutSuccessObserverTest extends \PHPUnit\Framework\TestCase 31 | { 32 | 33 | /** 34 | * Checkout success observer (test subject) instance 35 | * 36 | * @var \Chessio\Matomo\Observer\CheckoutSuccessObserver $_testSubject 37 | */ 38 | protected $_testSubject; 39 | 40 | /** 41 | * Tracker instance 42 | * 43 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerMock 44 | */ 45 | protected $_trackerMock; 46 | 47 | /** 48 | * Matomo data helper mock object 49 | * 50 | * @var \PHPUnit_Framework_MockObject_MockObject $_dataHelperMock 51 | */ 52 | protected $_dataHelperMock; 53 | 54 | /** 55 | * Matomo tracker helper mock object 56 | * 57 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerHelperMock 58 | */ 59 | protected $_trackerHelperMock; 60 | 61 | /** 62 | * Sales order repository mock object 63 | * 64 | * @var \PHPUnit_Framework_MockObject_MockObject $_orderRepositoryMock 65 | */ 66 | protected $_orderRepositoryMock; 67 | 68 | /** 69 | * Search criteria builder mock object 70 | * 71 | * @var \PHPUnit_Framework_MockObject_MockObject $_searchCriteriaBuilderMock 72 | */ 73 | protected $_searchCriteriaBuilderMock; 74 | 75 | /** 76 | * Event observer mock object 77 | * 78 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventObserverMock 79 | */ 80 | protected $_eventObserverMock; 81 | 82 | /** 83 | * Event mock object 84 | * 85 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventMock 86 | */ 87 | protected $_eventMock; 88 | 89 | /** 90 | * Setup 91 | * 92 | * @return void 93 | */ 94 | public function setUp(): void 95 | { 96 | $objectMgr = new ObjectManager($this); 97 | 98 | // Create test subject 99 | $className = \Chessio\Matomo\Observer\CheckoutSuccessObserver::class; 100 | $arguments = $objectMgr->getConstructArguments($className); 101 | $this->_testSubject = $objectMgr->getObject($className, $arguments); 102 | $this->_trackerMock = $arguments['matomoTracker']; 103 | $this->_dataHelperMock = $arguments['dataHelper']; 104 | $this->_trackerHelperMock = $arguments['trackerHelper']; 105 | $this->_orderRepositoryMock = $arguments['orderRepository']; 106 | $this->_searchCriteriaBuilderMock = $arguments['searchCriteriaBuilder']; 107 | 108 | $this->_eventMock = $this->createPartialMock( 109 | \Magento\Framework\Event::class, 110 | ['getOrderIds'] 111 | ); 112 | $this->_eventObserverMock = $this->createMock( 113 | \Magento\Framework\Event\Observer::class 114 | ); 115 | $this->_eventObserverMock 116 | ->expects($this->any()) 117 | ->method('getEvent') 118 | ->willReturn($this->_eventMock); 119 | } 120 | 121 | /** 122 | * Test for \Chessio\Matomo\Observer\CheckoutSuccessObserver::execute where 123 | * tracking is enabled. 124 | * 125 | * @return void 126 | */ 127 | public function testExecuteWithTrackingEnabled() 128 | { 129 | $orders = [ 130 | 1 => new \stdClass(), 131 | 2 => new \stdClass() 132 | ]; 133 | 134 | $this->_dataHelperMock 135 | ->expects($this->once()) 136 | ->method('isTrackingEnabled') 137 | ->willReturn(true); 138 | 139 | $this->_eventMock 140 | ->expects($this->atLeastOnce()) 141 | ->method('getOrderIds') 142 | ->willReturn(array_keys($orders)); 143 | 144 | $this->_searchCriteriaBuilderMock 145 | ->expects($this->once()) 146 | ->method('addFilter') 147 | ->with('entity_id', array_keys($orders), 'in') 148 | ->willReturn($this->_searchCriteriaBuilderMock); 149 | 150 | $searchCriteriaMock = $this->createMock( 151 | \Magento\Framework\Api\SearchCriteriaInterface::class 152 | ); 153 | 154 | $this->_searchCriteriaBuilderMock 155 | ->expects($this->once()) 156 | ->method('create') 157 | ->willReturn($searchCriteriaMock); 158 | 159 | $searchResultMock = $this->createConfiguredMock( 160 | \Magento\Sales\Api\Data\OrderSearchResultInterface::class, 161 | ['getItems' => $orders] 162 | ); 163 | 164 | $this->_orderRepositoryMock 165 | ->expects($this->once()) 166 | ->method('getList') 167 | ->with($searchCriteriaMock) 168 | ->willReturn($searchResultMock); 169 | 170 | $this->_trackerHelperMock 171 | ->expects($this->once()) 172 | ->method('addOrders') 173 | ->with($orders, $this->_trackerMock) 174 | ->willReturn($this->_trackerHelperMock); 175 | 176 | $this->assertSame( 177 | $this->_testSubject, 178 | $this->_testSubject->execute($this->_eventObserverMock) 179 | ); 180 | } 181 | 182 | /** 183 | * Test for \Chessio\Matomo\Observer\CheckoutSuccessObserver::execute where 184 | * tracking is disabled. 185 | * 186 | * @return void 187 | */ 188 | public function testExecuteWithTrackingDisabled() 189 | { 190 | $this->_dataHelperMock 191 | ->expects($this->once()) 192 | ->method('isTrackingEnabled') 193 | ->willReturn(false); 194 | 195 | $this->_eventMock 196 | ->expects($this->any()) 197 | ->method('getOrderIds') 198 | ->willReturn([1]); 199 | 200 | $this->_searchCriteriaBuilderMock 201 | ->expects($this->never()) 202 | ->method('addFilter'); 203 | 204 | $this->_searchCriteriaBuilderMock 205 | ->expects($this->never()) 206 | ->method('create'); 207 | 208 | $this->_orderRepositoryMock 209 | ->expects($this->never()) 210 | ->method('getList'); 211 | 212 | $this->_trackerHelperMock 213 | ->expects($this->never()) 214 | ->method('addOrders'); 215 | 216 | $this->assertSame( 217 | $this->_testSubject, 218 | $this->_testSubject->execute($this->_eventObserverMock) 219 | ); 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /Test/Unit/Observer/ProductViewObserverTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\Observer; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | 26 | /** 27 | * Test for \Chessio\Matomo\Observer\ProductViewObserver 28 | * 29 | */ 30 | class ProductViewObserverTest extends \PHPUnit\Framework\TestCase 31 | { 32 | 33 | /** 34 | * Product view observer (test subject) instance 35 | * 36 | * @var \Chessio\Matomo\Observer\ProductViewObserver $_observer 37 | */ 38 | protected $_observer; 39 | 40 | /** 41 | * Matomo tracker mock object 42 | * 43 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerMock 44 | */ 45 | protected $_trackerMock; 46 | 47 | /** 48 | * Matomo data helper mock object 49 | * 50 | * @var \PHPUnit_Framework_MockObject_MockObject $_dataHelperMock 51 | */ 52 | protected $_dataHelperMock; 53 | 54 | /** 55 | * Event mock object 56 | * 57 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventMock 58 | */ 59 | protected $_eventMock; 60 | 61 | /** 62 | * Event observer mock object 63 | * 64 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventObserverMock 65 | */ 66 | protected $_eventObserverMock; 67 | 68 | /** 69 | * Product model mock object 70 | * 71 | * @var \PHPUnit_Framework_MockObject_MockObject $_productMock 72 | */ 73 | protected $_productMock; 74 | 75 | /** 76 | * Category model mock object 77 | * 78 | * @var \PHPUnit_Framework_MockObject_MockObject $_categoryMock 79 | */ 80 | protected $_categoryMock; 81 | 82 | /** 83 | * Set up 84 | * 85 | * @return void 86 | */ 87 | public function setUp(): void 88 | { 89 | $className = \Chessio\Matomo\Observer\ProductViewObserver::class; 90 | $objectManager = new ObjectManager($this); 91 | $arguments = $objectManager->getConstructArguments($className); 92 | $this->_trackerMock = $this->createPartialMock( 93 | \Chessio\Matomo\Model\Tracker::class, 94 | ['setEcommerceView'] 95 | ); 96 | $arguments['matomoTracker'] = $this->_trackerMock; 97 | $this->_observer = $objectManager->getObject($className, $arguments); 98 | $this->_dataHelperMock = $arguments['dataHelper']; 99 | $this->_eventMock = $this->createPartialMock( 100 | \Magento\Framework\Event::class, 101 | ['getProduct'] 102 | ); 103 | $this->_eventObserverMock = $this->createMock( 104 | \Magento\Framework\Event\Observer::class 105 | ); 106 | $this->_productMock = $this->createPartialMock( 107 | \Magento\Catalog\Model\Product::class, 108 | ['getCategory', 'getSku', 'getName', 'getFinalPrice'] 109 | ); 110 | $this->_categoryMock = $this->createPartialMock( 111 | \Magento\Catalog\Model\Category::class, 112 | ['getName'] 113 | ); 114 | } 115 | 116 | /** 117 | * Data provicer for `testExecute' 118 | * 119 | * @return array 120 | */ 121 | public function executeDataProvider() 122 | { 123 | return [ 124 | ['sku1', 'Product Name #1', 123.45], 125 | ['sku2', 'Product Name #2', '234.56', 'Categor Name #1'] 126 | ]; 127 | } 128 | 129 | /** 130 | * Test for \Chessio\Matomo\Observer\ProductViewObserver::execute where 131 | * tracking is enabled. 132 | * 133 | * @param string $sku 134 | * @param string $name 135 | * @param float $price 136 | * @param string|null $category 137 | * @return void 138 | * @dataProvider executeDataProvider 139 | */ 140 | public function testExecuteWithTrackingEnabled( 141 | $sku, 142 | $name, 143 | $price, 144 | $category = null 145 | ) { 146 | // Enable tracking 147 | $this->_dataHelperMock 148 | ->expects($this->once()) 149 | ->method('isTrackingEnabled') 150 | ->willReturn(true); 151 | 152 | // Prepare event observer mock 153 | $this->_eventObserverMock 154 | ->expects($this->once()) 155 | ->method('getEvent') 156 | ->willReturn($this->_eventMock); 157 | $this->_eventMock 158 | ->expects($this->once()) 159 | ->method('getProduct') 160 | ->willReturn($this->_productMock); 161 | 162 | // Prepare product mock 163 | $methodMap = [ 164 | 'getSku' => $sku, 165 | 'getName' => $name, 166 | 'getFinalPrice' => $price 167 | ]; 168 | foreach ($methodMap as $method => $returnValue) { 169 | $this->_productMock 170 | ->expects($this->once()) 171 | ->method($method) 172 | ->willReturn($returnValue); 173 | } 174 | 175 | // Prepare category mock if category name was provided 176 | if ($category !== null) { 177 | $this->_productMock 178 | ->expects($this->once()) 179 | ->method('getCategory') 180 | ->willReturn($this->_categoryMock); 181 | $this->_categoryMock 182 | ->expects($this->once()) 183 | ->method('getName') 184 | ->willReturn($category); 185 | } 186 | 187 | // Make sure trackers' `setEcommerceView' is called exactly once. 188 | $this->_trackerMock 189 | ->expects($this->once()) 190 | ->method('setEcommerceView') 191 | ->with( 192 | $sku, 193 | $name, 194 | // Category should be FALSE if product has no category 195 | ($category === null) ? false : $category, 196 | (float) $price 197 | ) 198 | ->willReturn($this->_trackerMock); 199 | 200 | // Assert that `execute' returns $this 201 | $this->assertSame( 202 | $this->_observer, 203 | $this->_observer->execute($this->_eventObserverMock) 204 | ); 205 | } 206 | 207 | /** 208 | * Test for \Chessio\Matomo\Observer\ProductViewObserver::execute where 209 | * tracking is disabled. 210 | * 211 | * @return void 212 | */ 213 | public function testExecuteWithTrackingDisabled() 214 | { 215 | // Disable tracking 216 | $this->_dataHelperMock 217 | ->expects($this->once()) 218 | ->method('isTrackingEnabled') 219 | ->willReturn(false); 220 | 221 | // Provide access to event and product though they should preferably 222 | // never be touched when tracking is disabled. 223 | $this->_eventObserverMock 224 | ->expects($this->any()) 225 | ->method('getEvent') 226 | ->willReturn($this->_eventMock); 227 | $this->_eventMock 228 | ->expects($this->any()) 229 | ->method('getProduct') 230 | ->willReturn($this->_productMock); 231 | 232 | // Make sure trackers' `setEcommerceView' is never called. 233 | $this->_trackerMock 234 | ->expects($this->never()) 235 | ->method('setEcommerceView'); 236 | 237 | // Assert that `execute' returns $this 238 | $this->assertSame( 239 | $this->_observer, 240 | $this->_observer->execute($this->_eventObserverMock) 241 | ); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /Test/Unit/Observer/SearchResultObserverTest.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\Test\Unit\Observer; 23 | 24 | use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; 25 | 26 | /** 27 | * Test for \Chessio\Matomo\Observer\SearchResultObserver 28 | * 29 | */ 30 | class SearchResultObserverTest extends \PHPUnit\Framework\TestCase 31 | { 32 | 33 | /** 34 | * Search result observer (test subject) instance 35 | * 36 | * @var \Chessio\Matomo\Observer\SearchResultObserver $_observer 37 | */ 38 | protected $_observer; 39 | 40 | /** 41 | * Matomo tracker mock object 42 | * 43 | * @var \PHPUnit_Framework_MockObject_MockObject $_trackerMock 44 | */ 45 | protected $_trackerMock; 46 | 47 | /** 48 | * Matomo data helper mock object 49 | * 50 | * @var \PHPUnit_Framework_MockObject_MockObject $_dataHelperMock 51 | */ 52 | protected $_dataHelperMock; 53 | 54 | /** 55 | * Layout mock object 56 | * 57 | * @var \PHPUnit_Framework_MockObject_MockObject $_layoutMock 58 | */ 59 | protected $_layoutMock; 60 | 61 | /** 62 | * Search query mock object 63 | * 64 | * @var \PHPUnit_Framework_MockObject_MockObject $_queryMock 65 | */ 66 | protected $_queryMock; 67 | 68 | /** 69 | * Matomo block mock object 70 | * 71 | * @var \PHPUnit_Framework_MockObject_MockObject $_matomoBlockMock 72 | */ 73 | protected $_matomoBlockMock; 74 | 75 | /** 76 | * Search result block mock object 77 | * 78 | * @var \PHPUnit_Framework_MockObject_MockObject $_searchResultBlockMock 79 | */ 80 | protected $_searchResultBlockMock; 81 | 82 | /** 83 | * Event observer mock object 84 | * 85 | * @var \PHPUnit_Framework_MockObject_MockObject $_eventObserverMock 86 | */ 87 | protected $_eventObserverMock; 88 | 89 | /** 90 | * Set up 91 | * 92 | * @return void 93 | */ 94 | public function setUp(): void 95 | { 96 | $className = \Chessio\Matomo\Observer\SearchResultObserver::class; 97 | $objectManager = new ObjectManager($this); 98 | $arguments = $objectManager->getConstructArguments($className); 99 | 100 | $this->_trackerMock = $this->createPartialMock( 101 | \Chessio\Matomo\Model\Tracker::class, 102 | ['trackSiteSearch'] 103 | ); 104 | $arguments['matomoTracker'] = $this->_trackerMock; 105 | $this->_dataHelperMock = $arguments['dataHelper']; 106 | 107 | $this->_layoutMock = $this->createMock( 108 | \Magento\Framework\View\Layout::class 109 | ); 110 | $arguments['view'] 111 | ->expects($this->any()) 112 | ->method('getLayout') 113 | ->willReturn($this->_layoutMock); 114 | 115 | $this->_queryMock = $this->createPartialMock( 116 | \Magento\Search\Model\Query::class, 117 | ['getQueryText', 'getNumResults'] 118 | ); 119 | $arguments['queryFactory'] 120 | ->expects($this->any()) 121 | ->method('get') 122 | ->willReturn($this->_queryMock); 123 | 124 | $this->_observer = $objectManager->getObject($className, $arguments); 125 | $this->_matomoBlockMock = $this->createPartialMock( 126 | \Chessio\Matomo\Block\Matomo::class, 127 | ['setSkipTrackPageView'] 128 | ); 129 | $this->_searchResultBlockMock = $this->createMock( 130 | \Magento\CatalogSearch\Block\Result::class 131 | ); 132 | $this->_eventObserverMock = $this->createMock( 133 | \Magento\Framework\Event\Observer::class 134 | ); 135 | } 136 | 137 | /** 138 | * Prepare the search query mock object with given text and result count 139 | * 140 | * @param string $queryText 141 | * @param int|null $numResults 142 | */ 143 | protected function _prepareQueryMock($queryText, $numResults) 144 | { 145 | $this->_queryMock 146 | ->expects($this->once()) 147 | ->method('getQueryText') 148 | ->willReturn($queryText); 149 | $this->_queryMock 150 | ->expects($this->once()) 151 | ->method('getNumResults') 152 | ->willReturn($numResults); 153 | } 154 | 155 | /** 156 | * Prepare layout mock object with given blocks 157 | * 158 | * @param array $blocks 159 | */ 160 | protected function _prepareLayoutMock($blocks = []) 161 | { 162 | $blockMap = [['matomo.tracker', $this->_matomoBlockMock]]; 163 | foreach ($blocks as $name => $block) { 164 | $blockMap[] = [$name, $block]; 165 | } 166 | $this->_layoutMock 167 | ->expects($this->any()) 168 | ->method('getBlock') 169 | ->willReturnMap($blockMap); 170 | $this->_matomoBlockMock 171 | ->expects($this->once()) 172 | ->method('setSkipTrackPageView') 173 | ->with(true) 174 | ->willReturn($this->_matomoBlockMock); 175 | } 176 | 177 | /** 178 | * Test for \Chessio\Matomo\Observer\SearchResultObserver::execute where 179 | * the query object does not have a result count. 180 | * 181 | * @return void 182 | */ 183 | public function testExecuteWithNewQuery() 184 | { 185 | $queryText = 'Some query text'; 186 | $resultsCount = 5; 187 | 188 | // Enable tracking 189 | $this->_dataHelperMock 190 | ->expects($this->once()) 191 | ->method('isTrackingEnabled') 192 | ->willReturn(true); 193 | 194 | $this->_prepareQueryMock($queryText, null); 195 | $this->_prepareLayoutMock([ 196 | 'search.result' => $this->_searchResultBlockMock 197 | ]); 198 | 199 | // Make sure the search result block is called to access a result count 200 | $this->_searchResultBlockMock 201 | ->expects($this->once()) 202 | ->method('getResultCount') 203 | ->willReturn($resultsCount); 204 | 205 | // Make sure the trackers' `trackSiteSearch' is called exactly once 206 | $this->_trackerMock 207 | ->expects($this->once()) 208 | ->method('trackSiteSearch') 209 | ->with($queryText, false, $resultsCount) 210 | ->willReturn($this->_trackerMock); 211 | 212 | // Assert that `execute' returns $this 213 | $this->assertSame( 214 | $this->_observer, 215 | $this->_observer->execute($this->_eventObserverMock) 216 | ); 217 | } 218 | 219 | /** 220 | * Test for \Chessio\Matomo\Observer\SearchResultObserver::execute where 221 | * the query object does not have a result count and there is no search 222 | * result block available. 223 | * 224 | * @return void 225 | */ 226 | public function testExecuteWithNewQueryAndNoResultBlock() 227 | { 228 | $queryText = 'Some query text'; 229 | 230 | // Enable tracking 231 | $this->_dataHelperMock 232 | ->expects($this->once()) 233 | ->method('isTrackingEnabled') 234 | ->willReturn(true); 235 | 236 | $this->_prepareQueryMock($queryText, null); 237 | $this->_prepareLayoutMock(['search.result' => false]); 238 | 239 | // Make sure the trackers' `trackSiteSearch' is called exactly once 240 | $this->_trackerMock 241 | ->expects($this->once()) 242 | ->method('trackSiteSearch') 243 | ->with($queryText) // No results count available 244 | ->willReturn($this->_trackerMock); 245 | 246 | // Assert that `execute' returns $this 247 | $this->assertSame( 248 | $this->_observer, 249 | $this->_observer->execute($this->_eventObserverMock) 250 | ); 251 | } 252 | 253 | /** 254 | * Test for \Chessio\Matomo\Observer\SearchResultObserver::execute where 255 | * the query object has a result count. 256 | * 257 | * @return void 258 | */ 259 | public function testExecuteWithExistingQuery() 260 | { 261 | $queryText = 'Some query text'; 262 | $resultsCount = 5; 263 | 264 | // Enable tracking 265 | $this->_dataHelperMock 266 | ->expects($this->once()) 267 | ->method('isTrackingEnabled') 268 | ->willReturn(true); 269 | 270 | $this->_prepareQueryMock($queryText, $resultsCount); 271 | $this->_prepareLayoutMock([ 272 | 'search.result' => $this->_searchResultBlockMock 273 | ]); 274 | 275 | // Make sure the search result block is not accessed when the query 276 | // itself already has a result count. 277 | $this->_searchResultBlockMock 278 | ->expects($this->never()) 279 | ->method('getResultCount'); 280 | 281 | // Make sure the trackers' `trackSiteSearch' is called exactly once 282 | $this->_trackerMock 283 | ->expects($this->once()) 284 | ->method('trackSiteSearch') 285 | ->with($queryText, false, $resultsCount) 286 | ->willReturn($this->_trackerMock); 287 | 288 | // Assert that `execute' returns $this 289 | $this->assertSame( 290 | $this->_observer, 291 | $this->_observer->execute($this->_eventObserverMock) 292 | ); 293 | } 294 | 295 | /** 296 | * Test for \Chessio\Matomo\Observer\SearchResultObserver::execute where 297 | * tracking is disabled. 298 | * 299 | * @return void 300 | */ 301 | public function testExecuteWithTrackingDisabled() 302 | { 303 | // Disable tracking 304 | $this->_dataHelperMock 305 | ->expects($this->once()) 306 | ->method('isTrackingEnabled') 307 | ->willReturn(false); 308 | 309 | // Make sure the trackers' `trackSiteSearch' is never called 310 | $this->_trackerMock 311 | ->expects($this->never()) 312 | ->method('trackSiteSearch'); 313 | 314 | // Assert that `execute' returns $this 315 | $this->assertSame( 316 | $this->_observer, 317 | $this->_observer->execute($this->_eventObserverMock) 318 | ); 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /UserId/Provider/EmailProvider.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\UserId\Provider; 23 | 24 | use Magento\Customer\Api\CustomerRepositoryInterface; 25 | 26 | /** 27 | * Customer email provider 28 | * 29 | */ 30 | class EmailProvider implements ProviderInterface 31 | { 32 | 33 | /** 34 | * Customer repository 35 | * 36 | * @var CustomerRepositoryInterface $_customerRepository 37 | */ 38 | protected $_customerRepository; 39 | 40 | /** 41 | * Constructor 42 | * 43 | * @param CustomerRepositoryInterface $customerRepository 44 | */ 45 | public function __construct(CustomerRepositoryInterface $customerRepository) 46 | { 47 | $this->_customerRepository = $customerRepository; 48 | } 49 | 50 | /** 51 | * {@inheritDoc} 52 | */ 53 | public function getUserId($customerId) 54 | { 55 | try { 56 | return $this->_customerRepository->getById($customerId)->getEmail(); 57 | } catch (\Exception $e) { 58 | return false; 59 | } 60 | } 61 | 62 | /** 63 | * {@inheritDoc} 64 | */ 65 | public function getTitle() 66 | { 67 | return __('Customer E-mail'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /UserId/Provider/EntityIdProvider.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\UserId\Provider; 23 | 24 | /** 25 | * Customer entity ID provider 26 | * 27 | */ 28 | class EntityIdProvider implements ProviderInterface 29 | { 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function getUserId($customerId) 35 | { 36 | return (string) $customerId; 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public function getTitle() 43 | { 44 | return __('Customer Entity ID'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /UserId/Provider/Pool.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\UserId\Provider; 23 | 24 | /** 25 | * User ID provider pool 26 | * 27 | */ 28 | class Pool 29 | { 30 | 31 | /** 32 | * User ID providers 33 | * 34 | * @var ProviderInterface[] $_providers 35 | */ 36 | protected $_providers = []; 37 | 38 | /** 39 | * Constructor 40 | * 41 | * @param ProviderInterface[] $providers 42 | */ 43 | public function __construct(array $providers = []) 44 | { 45 | $this->_providers = $providers; 46 | } 47 | 48 | /** 49 | * Get User ID provider by code 50 | * 51 | * @param string $code 52 | * @return ProviderInterface|null 53 | */ 54 | public function getProviderByCode($code) 55 | { 56 | if (isset($this->_providers[$code]) 57 | && ($this->_providers[$code] instanceof ProviderInterface) 58 | ) { 59 | return $this->_providers[$code]; 60 | } 61 | return null; 62 | } 63 | 64 | /** 65 | * Get all User ID providers added to this pool 66 | * 67 | * @return ProviderInterface[] 68 | */ 69 | public function getAllProviders() 70 | { 71 | return array_filter($this->_providers, function ($provider) { 72 | return $provider instanceof ProviderInterface; 73 | }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /UserId/Provider/ProviderInterface.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | namespace Chessio\Matomo\UserId\Provider; 23 | 24 | /** 25 | * User ID provider interface 26 | * 27 | */ 28 | interface ProviderInterface 29 | { 30 | 31 | /** 32 | * Returns Matomo user ID for given Magento customer ID 33 | * 34 | * @param int $customerId 35 | * @return string 36 | */ 37 | public function getUserId($customerId); 38 | 39 | /** 40 | * Get User ID provider title 41 | * 42 | * @return string 43 | */ 44 | public function getTitle(); 45 | } 46 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chessio/module-matomo", 3 | "description": "Matomo Analytics module for Magento 2", 4 | "type": "magento2-module", 5 | "license": [ 6 | "AGPL-3.0+" 7 | ], 8 | "require": { 9 | "php": "~7.0.0|~7.1.0|~7.2.0|~7.3.0|~7.4.0|~8.1.0|~8.2.0", 10 | "magento/framework": "~101.0|~102.0|~103.0", 11 | "magento/module-catalog": "~102.0|~103.0|~104.0", 12 | "magento/module-catalog-search": "~100.0|~101.0|~102.0", 13 | "magento/module-checkout": "~100.0", 14 | "magento/module-config": "~101.0", 15 | "magento/module-customer": "~101.0|~102.0|~103.0", 16 | "magento/module-quote": "~101.0", 17 | "magento/module-sales": "~101.0|~102.0|~103.0", 18 | "magento/module-search": "~100.0|~101.0", 19 | "magento/module-store": "~100.0|~101.0" 20 | }, 21 | "autoload": { 22 | "files": ["registration.php"], 23 | "psr-4": { 24 | "Chessio\\Matomo\\": "" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /dev/ci/BUILDVARS.conf.dist: -------------------------------------------------------------------------------- 1 | MODULE_NAME='Chessio_Matomo' 2 | MODULE_SRC_DIR="$(cd "$(dirname "$0")"/../.. && pwd)" 3 | -------------------------------------------------------------------------------- /dev/ci/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Read variables from config file 4 | VARS_FILE="$(cd "$(dirname "$0")" && pwd)"'/BUILDVARS.conf' 5 | if [ -e "$VARS_FILE" ] 6 | then 7 | source "$VARS_FILE" 8 | else 9 | source "$VARS_FILE"'.dist' 10 | fi 11 | 12 | if [ -n "$TRAVIS_BUILD_DIR" ] 13 | then 14 | MODULE_SRC_DIR="$TRAVIS_BUILD_DIR" 15 | fi 16 | 17 | # Check for missing required variables 18 | for envkey in \ 19 | MODULE_NAME \ 20 | MODULE_SRC_DIR \ 21 | M2_VERSION 22 | do 23 | if [ -z "$(eval echo \$$envkey)" ] 24 | then 25 | echo "Missing required variable: $envkey" >&2 26 | exit 1 27 | fi 28 | done 29 | 30 | set -e 31 | 32 | # Set up environment 33 | PHP_BIN="$(which php)" 34 | COMPOSER_BIN="$(which composer)" 35 | COMPOSER_VERSION="$(composer --version | cut -d " " -f3)" 36 | BUILD_DIR="$(mktemp -d /tmp/$MODULE_NAME.XXXXXX)" 37 | if [ -z "$MODULE_DST_DIR" ] 38 | then 39 | MODULE_DST_DIR="$BUILD_DIR/app/code/$(echo $MODULE_NAME | sed 's/_/\//')" 40 | fi 41 | 42 | set -x 43 | 44 | # Fetch Magento 2 source 45 | "$COMPOSER_BIN" create-project \ 46 | --repository=https://repo-magento-mirror.fooman.co.nz/ \ 47 | --add-repository \ 48 | --quiet \ 49 | --ignore-platform-reqs \ 50 | --no-install \ 51 | magento/project-community-edition \ 52 | "$BUILD_DIR" "$M2_VERSION" 53 | 54 | cd "$BUILD_DIR" 55 | "$COMPOSER_BIN" config --unset repo.0 56 | "$COMPOSER_BIN" config repositories.foomanmirror composer https://repo-magento-mirror.fooman.co.nz/ 57 | 58 | COMPOSER_MAJOR_VERSION="$(cut -d '.' -f 1 <<< "$COMPOSER_VERSION")" 59 | if [ "$COMPOSER_MAJOR_VERSION" == "2" ] 60 | then 61 | "$COMPOSER_BIN" config allow-plugins.magento/* true 62 | "$COMPOSER_BIN" config allow-plugins.laminas/laminas-dependency-plugin true 63 | "$COMPOSER_BIN" config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true 64 | fi 65 | 66 | "$COMPOSER_BIN" update 67 | 68 | # Copy module into Magento 69 | mkdir -p "$(dirname "$MODULE_DST_DIR")" 70 | cp -r "$MODULE_SRC_DIR" "$MODULE_DST_DIR" 71 | 72 | # Run module unit tests 73 | "$PHP_BIN" "$BUILD_DIR/vendor/phpunit/phpunit/phpunit" \ 74 | --colors \ 75 | -c "$BUILD_DIR/dev/tests/unit/phpunit.xml.dist" \ 76 | "$MODULE_DST_DIR/Test/Unit" 77 | -------------------------------------------------------------------------------- /etc/acl.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /etc/adminhtml/system.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 25 | 26 | 27 |
34 | 35 | sales 36 | Chessio_Matomo::matomo 37 | 44 | 45 | 52 | 53 | Magento\Config\Model\Config\Source\Yesno 54 | 55 | 62 | 63 | Matomo hostname, optionally including a path e.g. matomo.example.com or example.com/matomo 64 | 65 | 1 66 | 67 | required-entry 68 | 69 | 76 | 77 | Listed under Settings/Website in your Matomo administration panel 78 | 79 | 1 80 | 81 | required-entry validate-digits validate-zero-or-greater 82 | 83 | 90 | 91 | Magento\Config\Model\Config\Source\Yesno 92 | Enable tracking of outlinks and downloads 93 | 94 | 1 95 | 96 | 97 | 104 | 105 | Delay for link tracking in milliseconds 106 | 107 | 1 108 | 1 109 | 110 | validate-digits validate-zero-or-greater 111 | 112 | 119 | 120 | Chessio\Matomo\Model\Config\Source\UserId\Provider 121 | Send logged in customers ID to Matomo 122 | 123 | 1 124 | 125 | 126 | 133 | 134 | 135 | 1 136 | 137 | 144 | 145 | piwik/tracking/php_script_path 146 | Path to the Matomo tracker PHP script. Usually "matomo.php". 147 | required-entry 148 | 149 | 156 | 157 | piwik/tracking/js_script_path 158 | Path to the Matomo tracker Javascript. Usually "matomo.js". 159 | required-entry 160 | 161 | 168 | 169 | piwik/tracking/cdn_hostname 170 | Hostname for serving the Matomo tracker Javascript. May be left empty in which case the regular hostname will be used. 171 | 172 | 173 | 174 |
175 |
176 |
177 | -------------------------------------------------------------------------------- /etc/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 25 | 26 | 27 | 28 | 29 | 0 30 | 31 | 1 32 | 1 33 | 500 34 | 35 | matomo.php 36 | matomo.js 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 25 | 26 | 27 | 28 | 29 | Chessio\Matomo\UserId\Provider\EntityIdProvider 30 | Chessio\Matomo\UserId\Provider\EmailProvider 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /etc/frontend/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /etc/frontend/events.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 25 | 26 | 28 | 29 | 30 | 32 | 33 | 34 | 36 | 37 | 38 | 40 | 41 | 42 | 44 | 45 | 46 | 48 | 49 | 50 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /i18n/en_US.csv: -------------------------------------------------------------------------------- 1 | "Matomo API","Matomo API" 2 | "Tracking API","Tracking API" 3 | "Enable Tracking","Enable Tracking" 4 | "Yes","Yes" 5 | "No","No" 6 | "Hostname","Hostname" 7 | "Matomo hostname, optionally including a path e.g. matomo.example.com or example.com/matomo","Matomo hostname, optionally including a path e.g. matomo.example.com or example.com/matomo" 8 | "Site ID","Site ID" 9 | "Listed under Settings/Website in your Matomo administration panel","Listed under Settings/Website in your Matomo administration panel" 10 | "Enable Link Tracking","Enable Link Tracking" 11 | "Enable tracking of outlinks and downloads","Enable tracking of outlinks and downloads" 12 | "Link Tracking Timer","Link Tracking Timer" 13 | "Delay for link tracking in milliseconds","Delay for link tracking in milliseconds" 14 | "Enable User ID Tracking","Enable User ID Tracking" 15 | "Customer Entity ID","Customer Entity ID" 16 | "Customer E-mail","Customer E-mail" 17 | "Send logged in customers ID to Matomo","Send logged in customers ID to Matomo" 18 | "Advanced Options","Advanced Options" 19 | "Javascript Path","Javascript Path" 20 | "Path to the Matomo tracker Javascript. Usually ""matomo.js"".","Path to the Matomo tracker Javascript. Usually ""matomo.js""." 21 | "PHP Script Path","PHP Script Path" 22 | "Path to the Matomo tracker PHP script. Usually ""matomo.php"".","Path to the Matomo tracker PHP script. Usually ""matomo.php""." 23 | "CDN Hostname","CDN Hostname" 24 | "Hostname for serving the Matomo tracker Javascript. May be left empty in which case the regular hostname will be used.","Hostname for serving the Matomo tracker Javascript. May be left empty in which case the regular hostname will be used." 25 | -------------------------------------------------------------------------------- /i18n/it_IT.csv: -------------------------------------------------------------------------------- 1 | "Matomo API","Matomo API" 2 | "Tracking API","Tracking API" 3 | "Enable Tracking","Abilita Tracking" 4 | Yes,Si 5 | No,No 6 | Hostname,Hostname 7 | "Matomo hostname, optionally including a path e.g. matomo.example.com or example.com/matomo","Matomo hostname, opzionalmente include un percorso es. matomo.example.com o example.com/matomo" 8 | "Site ID","ID Sito" 9 | "Listed under Settings/Website in your Matomo administration panel","Elencati in Impostazioni/Sito nel tuo pannello di amministrazione Matomo" 10 | "Enable Link Tracking","Abilita Link Tracking" 11 | "Enable tracking of outlinks and downloads","Abilita tracking di outlinks e downloads" 12 | "Link Tracking Timer","Link Tracking Timer" 13 | "Delay for link tracking in milliseconds","Ritardo per il link tracking in millisecondi" 14 | -------------------------------------------------------------------------------- /i18n/sv_SE.csv: -------------------------------------------------------------------------------- 1 | "Matomo API","Matomo API" 2 | "Tracking API","API för spårning" 3 | "Enable Tracking","Aktivera spårning" 4 | "Yes","Ja" 5 | "No","Nej" 6 | "Hostname","Värdnamn" 7 | "Matomo hostname, optionally including a path e.g. matomo.example.com or example.com/matomo","Matomo-värdnamn, alternativt inkluderande sökväg t.ex. matomo.example.com eller example.com/matomo" 8 | "Site ID","Webbplatsens ID" 9 | "Listed under Settings/Website in your Matomo administration panel","Finns att hitta under Inställningar/Webbplatser i Matomos administrationspanel" 10 | "Enable Link Tracking","Aktivera länkspårning" 11 | "Enable tracking of outlinks and downloads","Aktivera spårning av utlänkar och nedladdningar" 12 | "Link Tracking Timer","Länkspårningstimer" 13 | "Delay for link tracking in milliseconds","Fördröjning av länkspårning i millisekunder" 14 | "Enable User ID Tracking","Aktivera spårning av användar-ID" 15 | "Customer Entity ID","Kundens entitets-ID" 16 | "Customer E-mail","Kundens E-postadress" 17 | "Send logged in customers ID to Matomo","Skicka inloggade kunders ID till Matomo" 18 | "Advanced Options","Avancerade inställningar" 19 | "Javascript Path","Sökväg till javascript" 20 | "Path to the Matomo tracker Javascript. Usually ""matomo.js"".","Sökväg till Matomo-spårarens javascript. Vanligtvis ""matomo.js""." 21 | "PHP Script Path","Sökväg till PHP-skript" 22 | "Path to the Matomo tracker PHP script. Usually ""matomo.php"".","Sökväg till Matomo-spårarens PHP-skript. Vanligtvis ""matomo.php""." 23 | "CDN Hostname","CDN-värdnamn" 24 | "Hostname for serving the Matomo tracker Javascript. May be left empty in which case the regular hostname will be used.","Värdnamn för Matomo-spårarens javascript. Lämna tomt för att använda det allmänna värdnamnet." 25 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | \Magento\Framework\Component\ComponentRegistrar::register( 23 | \Magento\Framework\Component\ComponentRegistrar::MODULE, 24 | 'Chessio_Matomo', 25 | __DIR__ 26 | ); 27 | -------------------------------------------------------------------------------- /view/frontend/layout/default.xml: -------------------------------------------------------------------------------- 1 | 2 | 23 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /view/frontend/templates/matomo.phtml: -------------------------------------------------------------------------------- 1 | . 20 | */ 21 | 22 | ?> 23 | 24 | 31 | 37 | 51 | 60 | 87 | 94 | -------------------------------------------------------------------------------- /view/frontend/web/js/tracker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016-2018 Henrik Hedelund 3 | * Copyright 2020 Falco Nogatz 4 | * 5 | * This file is part of Chessio_Matomo. 6 | * 7 | * Chessio_Matomo is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU Affero General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * Chessio_Matomo is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU Affero General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU Affero General Public License 18 | * along with Chessio_Matomo. If not, see . 19 | */ 20 | 21 | define([ 22 | 'jquery', 23 | 'underscore', 24 | 'Magento_Customer/js/customer-data', 25 | 'jquery/jquery-storageapi' 26 | ], function ($, _, customerData) { 27 | 'use strict'; 28 | 29 | /** 30 | * Object holding globally accessible properties 31 | * 32 | * @type {Object} 33 | */ 34 | var exports = window; 35 | 36 | /** 37 | * Default Matomo website ID 38 | * 39 | * @type {number} 40 | */ 41 | var defaultSiteId; 42 | 43 | /** 44 | * Default Matomo tracker endpoint 45 | * 46 | * @type {String} 47 | */ 48 | var defaultTrackerUrl; 49 | 50 | /** 51 | * Reference to global `matomoAsyncInit' in case we overwrite something 52 | * 53 | * @type {Function|undefined} 54 | */ 55 | var origMatomoAsyncInit = exports.matomoAsyncInit; 56 | 57 | /** 58 | * Matomo singleton/namespace 59 | * 60 | * @type {Object} 61 | */ 62 | var matomo = exports.Matomo || null; 63 | 64 | /** 65 | * Collection of matomo promises 66 | * 67 | * @type {Array.} 68 | */ 69 | var matomoPromises = []; 70 | 71 | /** 72 | * Client side cache/storage 73 | * 74 | * @type {Object} 75 | */ 76 | var storage = $.initNamespaceStorage('chessio-matomo').localStorage; 77 | 78 | /** 79 | * Cart data access 80 | * 81 | * @type {Object} 82 | */ 83 | var cartObservable = customerData.get('cart'); 84 | 85 | /** 86 | * Customer data access 87 | * 88 | * @type {Object} 89 | */ 90 | var customerObservable = customerData.get('customer'); 91 | 92 | /** 93 | * Append Matomo tracker script URL to head 94 | * 95 | * @param {String} scriptUrl 96 | */ 97 | function injectScript(scriptUrl) { 98 | $('