isMultiLanguage = Multilanguage::isEnabled(); if ($app->isClient('site')) { $this->isScanMode = !!Factory::getApplication()->getSession()->get('n3tcc_scan'); if (!$this->isScanMode && $this->getInput()->get('n3tcc_scan') === ApplicationHelper::getHash('plg_system_n3tcookiesconsent')) { $this->isScanMode = true; $app->getSession()->set('n3tcc_scan', true); } elseif ($this->isScanMode && $this->getInput()->get('n3tcc_scan') === '0') { $this->isScanMode = false; $this->isScanModeFinished = true; $app->getSession()->set('n3tcc_scan', false); } if ($this->isScanMode) { $this->loadLanguage(); $app->enqueueMessage(Text::sprintf('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_MODE_RUNNING', Route::_('index.php?n3tcc_scan=0')), 'warning'); } $reportCookie = $this->getInput()->cookie->get($this->params->get('cookie_name', 'n3t_cc') . '_report', '', 'string'); if ($reportCookie) $this->reportCookie = explode(';', $reportCookie); } } /** * Helper function to get JInput based on Joomla version * * @return \Joomla\Input\Input * * @throws Exception * @since 4.0.0 */ private function getInput(): \Joomla\Input\Input { if (Version::MAJOR_VERSION < 4) return Factory::getApplication()->input; else return Factory::getApplication()->getInput(); } /** * Loads language files * * @param $extension * @param $basePath * * @return bool * * @throws Exception * @since 4.0.0 */ public function loadLanguage($extension = '', $basePath = JPATH_ADMINISTRATOR) { $langFile = 'plg_' . $this->_type . '_' . $this->_name . '.data'; $lang = Factory::getApplication()->getLanguage(); if (!$lang->getPaths($langFile)) $lang->load($langFile, JPATH_ADMINISTRATOR); return parent::loadLanguage($extension, $basePath); } /** * Returns true if plugin should be enabled * @return bool * * @throws Exception * @since 4.0.0 */ private function isEnabled(): bool { $app = Factory::getApplication(); return $app->isClient('site') && $app->getDocument()->getType() === 'html' && (!$this->params->get('hide_from_bots', true) || !Factory::getApplication()->client->robot); } /** * Returns true if current page is privacy page * @return bool * * @throws Exception * @since 4.0.0 */ private function isPrivacyPage(): bool { $input = $this->getInput(); if ($this->params->get('privacy_policy_type', 'menuitem') === 'menuitem' && $this->params->get('privacy_policy') === $input->get('Itemid')) return true; return false; } /** * Returns true if plugin should use autorun * @return bool * * @throws Exception * @since 4.0.0 */ private function isVisible(): bool { static $disabledTmplList = null; if ($disabledTmplList === null) { $tmplList = (array) $this->params->get('disable_tmpl', [['tmpl' => 'component'], ['tmpl' => 'raw']]); $disabledTmplList = []; foreach ($tmplList as $tmpl) $disabledTmplList[] = ((array)$tmpl)['tmpl']; } if (in_array($this->getInput()->get('tmpl'), $disabledTmplList)) return false; return true; } /** * Returns true if trigger should be visible * @return bool * * @throws Exception * @since 4.0.0 */ private function isTriggerVisible(): bool { return $this->params->get('show_trigger', 1) && $this->isVisible(); } /** * Returns current user consent * * @return Registry * * @throws Exception * @since 4.0.0 */ private function getConsent(): Registry { $consent = $this->getInput()->cookie->get($this->params->get('cookie_name', 'n3t_cc'), null, 'raw'); return new Registry($consent); } /** * Returns currently allowed categories by user consent * * @return Registry * * @throws Exception * @since 4.0.0 */ private function allowedCategories(): array { $categories = []; foreach ($this->params->get('blocks', []) as $block) { if ($block->type == self::BLOCK_FUNCTIONAL || $block->type == self::BLOCK_SYSTEM || $block->type == self::BLOCK_CUSTOM && $block->readonly && $block->default_enabled) $categories[] = $block->type; } $consent = $this->getConsent(); $categories = array_merge($categories, $consent->get('level', [])); return array_unique($categories); } /** * Returns if cookie by given name is enabled by current user consent * @return bool * * @throws Exception * @since 4.0.0 */ private function isCookieEnabled(string $cookieName): bool { $cookieName = trim($cookieName); $cookies = $this->loadCookies(); $category = null; foreach ($cookies as $cookie) { if ($cookie['is_regex'] && preg_match('~' . $cookie['name'] . '~', $cookieName)) { $category = $cookie['category']; break; } if (!$cookie['is_regex'] && $cookie['name'] == $cookieName) { $category = $cookie['category']; break; } } if ($category === self::BLOCK_HIDDEN) $category = self::BLOCK_UNKNOWN; if ($category === self::BLOCK_SYSTEM) $category = self::BLOCK_FUNCTIONAL; if ($category === null) { switch ($this->params->get('allow_unknown_cookies', 'settings')) { case 'allow': return true; case 'block': return false; default: $category = self::BLOCK_UNKNOWN; break; } } return in_array($category, $this->allowedCategories()); } /** * Returns translation, if exists (avoids reporting unstranslated strings) * * @param $text * * @return string * * @throws Exception * @since 4.0.0 */ private function translateText(string $text, ?int $count = null): string { if ($text === 'SITENAME') { $appConfig = Factory::getConfig(); return $appConfig->get('sitename'); } static $lang = null; if ($lang === null) $lang = Factory::getApplication()->getLanguage(); if ($count === null) return $lang->hasKey($text) ? Text::_($text) : $text; else return Text::plural($text, $count); } /** * Returns translation of text constant, if site is multilanguage, returns the text constnt * * @param $text * * @return string * * @throws Exception * @since 4.0.0 */ private function multilang(string $text): string { return $this->isMultiLanguage ? $text : $this->translateText($text); } /** * returns array of cookies used in script * @return array * * @since 4.0.0 */ private function loadCookies(): array { if ($this->cookieList == null) { $cookies = []; foreach ($this->params->get('blocks', []) as $block) { if (isset($block->cookies) && !empty($block->cookies)) { foreach ((array)$block->cookies as $cookie) { $cookie = (object)$cookie; if ($block->alias ?? '') $category = $block->alias; else { $category = $block->type ?? ''; if (!$category) $category = self::BLOCK_UNKNOWN; if ($category == self::BLOCK_HIDDEN) $category = self::BLOCK_UNKNOWN; if ($category == self::BLOCK_SYSTEM) $category = self::BLOCK_FUNCTIONAL; } $cookies[] = [ 'name' => $cookie->name, 'is_regex' => !!($cookie->regex ?? false), 'category' => $category, 'required' => $block->type == self::BLOCK_FUNCTIONAL || $block->type == self::BLOCK_SYSTEM || $block->type == self::BLOCK_CUSTOM && $block->readonly && $block->default_enabled, ]; } } } $this->cookieList = $cookies; } return $this->cookieList; } /** * returns true, if cookie is registered, otherwise false * * @param string $cookieName * * @return bool * * @since 4.0.0 */ private function isCookieRegistered(string $cookieName): bool { $cookies = $this->loadCookies(); foreach ($cookies as $cookie) { if ($cookie['is_regex'] && preg_match('~' . $cookie['name'] . '~', $cookieName)) return true; if (!$cookie['is_regex'] && $cookie['name'] == $cookieName) return true; } return false; } /** * returns array containing CookieConsent script settings * @return array * * @throws Exception * @since 4.0.0 */ private function scriptOptions(): array { $appConfig = JFactory::getConfig(); $options = []; if (!$this->params->get('autorun', true) || !$this->isVisible() || $this->isPrivacyPage()) $options['autorun'] = false; if ((int)$this->params->get('delay', 0)) $options['delay'] = (int)$this->params->get('delay', 0); $options['cookie_expiration'] = (int)$this->params->get('cookie_expiration', 395); if ((int)$this->params->get('cookie_necessary_only_expiration')) $options['cookie_necessary_only_expiration'] = (int)$this->params->get('cookie_necessary_only_expiration'); if ($path = $this->params->get('cookie_path', $appConfig->get('cookie_path'))) $options['cookie_path'] = $path; if ($domain = $this->params->get('cookie_domain', $appConfig->get('cookie_domain'))) $options['cookie_domain'] = $domain; if ($this->params->get('cookie_same_site', 'Lax') != 'Lax') $options['cookie_same_site'] = $this->params->get('cookie_same_site'); $options['use_rfc_cookie'] = true; if ($this->params->get('force_consent')) $options['force_consent'] = true; if ((int)$this->params->get('revision')) $options['revision'] = (int)$this->params->get('revision'); $options['current_lang'] = 'default'; $options['autoclear_cookies'] = true; if ($this->params->get('page_scripts')) $options['page_scripts'] = true; if ($this->params->get('mode', 'opt-in') != 'opt-in') $options['mode'] = $this->params->get('mode'); if ($this->params->get('remove_cookie_tables')) $options['remove_cookie_tables'] = true; if ($this->params->get('hide_from_bots', true)) $options['hide_from_bots'] = true; $guiOptions = []; $guiOptions['consent_modal'] = [ 'layout' => $this->params->get('consent_modal_layout', 'box'), 'position' => $this->params->get('consent_modal_position', 'bottom right'), 'transition' => $this->params->get('consent_modal_transition', 'slide'), 'swap_buttons' => !!$this->params->get('consent_modal_swap_buttons', true), ]; $guiOptions['settings_modal'] = [ 'layout' => $this->params->get('settings_modal_layout', 'bar'), 'position' => $this->params->get('settings_modal_position', 'right'), 'transition' => $this->params->get('settings_modal_transition', 'slide'), ]; $options['gui_options'] = $guiOptions; $language = []; $privacyLink = ''; if ($this->params->get('privacy_policy_type', 'menuitem') === 'menuitem' && $this->params->get('privacy_policy')) $privacyLink = Route::_('index.php?Itemid=' . $this->params->get('privacy_policy')); elseif ($this->params->get('privacy_policy_type', 'menuitem') === 'url' && $this->params->get('privacy_policy_url')) $privacyLink = $this->params->get('privacy_policy_url'); $consentText = Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_CONSENT_MODAL_DESCRIPTION'); if ($this->params->get('secondary_button_role', 'settings') !== 'settings' && $this->params->get('tertiary_button_role', 'none') !== 'settings') $consentText .= ' ' . Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_SETTINGS_LINK'); if ($privacyLink) $consentText .= ' ' . Text::sprintf('PLG_SYSTEM_N3TCOOKIECONSENT_PRIVACY_POLICY_LINK', $privacyLink); if ((int)$this->params->get('revision')) $consentText .= '{{revision_message}}'; $consentModallanguage = [ 'title' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_CONSENT_MODAL_TITLE'), 'description' => $consentText, 'revision_message' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_CONSENT_MODAL_REVISION'), 'primary_btn' => [ 'text' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BTN_' . $this->params->get('primary_button_role', 'accept_all')), 'role' => $this->params->get('primary_button_role', 'accept_all'), ] ]; if ($this->params->get('secondary_button_role', 'settings') != 'none') { $consentModallanguage['secondary_btn'] = [ 'text' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BTN_' . $this->params->get('secondary_button_role', 'settings')), 'role' => $this->params->get('secondary_button_role', 'settings'), ]; } $language['consent_modal'] = $consentModallanguage; $settingsModallanguage = [ 'title' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_SETTINGS_MODAL_TITLE'), 'save_settings_btn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BTN_SAVE_SETTINGS'), 'accept_all_btn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BTN_ACCEPT_ALL_SETTINGS'), 'close_btn_label' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BTN_CLOSE_SETTINGS'), ]; if ($this->params->get('show_reject_all', true)) $settingsModallanguage['reject_all_btn'] = Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BTN_REJECT_ALL_SETTINGS'); $settingsModallanguage['cookie_table_headers'] = [ ['name' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_TABLE_NAME')], ['description' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_TABLE_DESCRIPTION')], ]; if ($this->params->get('show_cookie_provider', 1)) $settingsModallanguage['cookie_table_headers'][] = ['provider' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_TABLE_PROVIDER')]; if ($this->params->get('show_cookie_expiration', 1)) $settingsModallanguage['cookie_table_headers'][] = ['expiration' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_TABLE_EXPIRATION')]; $settingsModallanguage['blocks'] = []; if ($this->isScanMode) { $blockOptions = []; $blockOptions['title'] = Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BLOCK_SCAN_TITLE'); $blockOptions['description'] = Text::sprintf('PLG_SYSTEM_N3TCOOKIECONSENT_BLOCK_SCAN_DESCRIPTION', Route::_('index.php?n3tcc_scan=0')); $settingsModallanguage['blocks'][] = $blockOptions; } foreach ($this->params->get('blocks', []) as $block) { $blockOptions = []; if ($block->type == self::BLOCK_HIDDEN) continue; if ($block->type == self::BLOCK_SYSTEM) continue; switch ($block->type) { case self::BLOCK_CUSTOM: case self::BLOCK_CUSTOM_DESCRIPTION: $blockOptions['title'] = $this->translateText($block->title); $blockOptions['description'] = $this->translateText($block->description); if ($block->type == self::BLOCK_CUSTOM) { $blockOptions['toggle'] = [ 'value' => $block->alias, 'enabled' => !!$block->default_enabled, 'readonly' => !!$block->readonly, ]; } break; case self::BLOCK_PRIVACY: if ($privacyLink) { $blockOptions['title'] = Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BLOCK_' . $block->type . '_TITLE'); $blockOptions['description'] = Text::sprintf('PLG_SYSTEM_N3TCOOKIECONSENT_BLOCK_' . $block->type . '_DESCRIPTION', $privacyLink); } else continue 2; break; case self::BLOCK_CONSENT: $blockOptions['title'] = Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BLOCK_' . $block->type . '_TITLE'); $blockOptions['description'] = '
'; break; default: $blockOptions['title'] = Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BLOCK_' . $block->type . '_TITLE'); $blockOptions['description'] = Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_BLOCK_' . $block->type . '_DESCRIPTION'); break; } switch ($block->type) { case self::BLOCK_CUSTOM: $blockOptions['toggle'] = [ 'value' => $block->alias, 'enabled' => !!$block->default_enabled, 'readonly' => !!$block->readonly, ]; break; case self::BLOCK_FUNCTIONAL: $blockOptions['toggle'] = [ 'value' => $block->type, 'enabled' => true, 'readonly' => true, ]; break; case self::BLOCK_PREFERENCES: case self::BLOCK_ANALYTICS: case self::BLOCK_MARKETING: case self::BLOCK_UNKNOWN: $blockOptions['toggle'] = [ 'value' => $block->type, 'enabled' => false, 'readonly' => false, ]; break; } if (isset($block->cookies) && !empty($block->cookies)) { $cookieTable = []; foreach ((array)$block->cookies as $cookie) { $cookie = (object)$cookie; $cookieOptions = [ 'name' => $cookie->name, 'description' => $this->translateText($cookie->description ?? '') ?: ' ', 'is_regex' => !!($cookie->regex ?? false), ]; if ($this->params->get('show_cookie_provider', 1)) $cookieOptions['provider'] = $this->translateText($cookie->provider ?? '') ?: ' '; if ($this->params->get('show_cookie_expiration', 1)) { if (isset($cookie->expiration_unit) && !empty($cookie->expiration_unit)) $cookieOptions['expiration'] = $this->translateText('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_' . strtoupper($cookie->expiration_unit), (int)($cookie->expiration ?? '')); else $cookieOptions['expiration'] = $this->translateText($cookie->expiration ?? '') ?: ' '; } $cookieTable[] = $cookieOptions; } if ($cookieTable) $blockOptions['cookie_table'] = $cookieTable; } $settingsModallanguage['blocks'][] = $blockOptions; } $language['settings_modal'] = $settingsModallanguage; $options['languages'] = [ 'default' => $language, ]; return $options; } /** * Returns array containing CSS variables with custom colors * @return array * * @since 4.0.0 */ private function styleOptions(): array { $colors = []; foreach ($this->params as $name => $value) { if (preg_match('~^color_~', $name) && $value) { $name = '--cc-' . preg_replace('~^color_~', '', $name); $name = str_replace('_', '-', $name); $colors[$name] = $value; } } return $colors; } /** * returns array containing IFrameManager script settings * @return array * * @throws Exception * @since 4.0.0 */ private function iframeManagerOptions(): array { $services = []; $service = [ 'embedUrl' => '{data-id}', 'cookie' => [ 'name' => $this->params->get('cookie_name', 'n3t_cc') . '_ifm_unknown', 'expiration' => (int)$this->params->get('cookie_expiration', 395), ], 'languages' => [ 'default' => [ 'notice' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_UNKNOWN'), 'loadBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_LOAD'), 'loadAllBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_ALWAYS'), ] ], ]; if ($thumb = $this->params->get('ifm_thumbnail')) { if (Version::MAJOR_VERSION >= 4) $thumb = HTMLHelper::cleanImageURL($thumb)->url; $service['thumbnailUrl'] = Uri::base() . $thumb; } $services['unknown'] = $service; $service = [ 'embedUrl' => 'https://www.youtube-nocookie.com/embed/{data-id}', 'thumbnailUrl' => 'https://i3.ytimg.com/vi/{data-id}/hqdefault.jpg', 'cookie' => [ 'name' => $this->params->get('cookie_name', 'n3t_cc') . '_ifm_youtube', ], 'languages' => [ 'default' => [ 'notice' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_YOUTUBE'), 'loadBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_LOAD'), 'loadAllBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_ALWAYS'), ] ], ]; $services['youtube'] = $service; $service = [ 'embedUrl' => 'https://player.vimeo.com/video/{data-id}', 'cookie' => [ 'name' => $this->params->get('cookie_name', 'n3t_cc') . '_ifm_vimeo', ], 'languages' => [ 'default' => [ 'notice' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_VIMEO'), 'loadBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_LOAD'), 'loadAllBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_ALWAYS'), ] ], ]; $services['vimeo'] = $service; $service = [ 'embedUrl' => 'https://www.dailymotion.com/embed/video/{data-id}', 'cookie' => [ 'name' => $this->params->get('cookie_name', 'n3t_cc') . '_ifm_dailymotion', ], 'languages' => [ 'default' => [ 'notice' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_DAILYMOTION'), 'loadBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_LOAD'), 'loadAllBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_ALWAYS'), ] ], ]; $services['dailymotion'] = $service; $providers = (array)$this->params->get('ifm_services'); foreach ($providers as $provider) { $provider = (array)$provider; $service = [ 'embedUrl' => '{data-id}', 'cookie' => [ 'name' => $this->params->get('cookie_name', 'n3t_cc') . '_ifm_' . md5($provider['url']), ], 'languages' => [ 'default' => [ 'notice' => $provider['terms'] ? Text::sprintf('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_GENERAL_TOS', $provider['terms'], $provider['name']) : Text::sprintf('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_GENERAL', $provider['name']), 'loadBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_LOAD'), 'loadAllBtn' => Text::_('PLG_SYSTEM_N3TCOOKIECONSENT_IFM_BTN_ALWAYS'), ] ], ]; if ($thumb = $provider['thumbnail']) { if (Version::MAJOR_VERSION >= 4) $thumb = HTMLHelper::cleanImageURL($thumb)->url; $service['thumbnailUrl'] = Uri::base() . $thumb; } elseif ($thumb = $this->params->get('ifm_thumbnail')) { if (Version::MAJOR_VERSION >= 4) $thumb = HTMLHelper::cleanImageURL($thumb)->url; $service['thumbnailUrl'] = Uri::base() . $thumb; } $services[md5($provider['url'])] = $service; } $options = [ 'currLang' => 'default', 'services' => $services, ]; return $options; } /** * Resturns string with HTML code to insert after * @return string|null * * @throws Exception * @since 4.0.0 */ private function htmlOutput(): ?string { $html = "\n"; $path = HTMLHelper::script('plg_n3tcookieconsent/n3tconsentmanager.min.js', ['pathOnly' => true, 'relative' => true]); $path .= '?' . Factory::getDocument()->getMediaVersion(); $html.= '' . "\n"; if ($this->isTriggerVisible()) { $trigger = $this->renderLayout('trigger', [ 'params' => $this->params, ]); } else $trigger = false; $params = [ 'options' => $this->scriptOptions(), 'cookies' => $this->loadCookies(), 'trigger' => $trigger, 'params' => $this->params, 'isScanMode' => $this->isScanMode, ]; if ($this->params->get('use_iframe_manager', false)) $params['iframeManagerOptions'] = $this->iframeManagerOptions(); $script = $this->renderLayout('script', $params); $html.= '' . "\n"; return $html; } /** * Updates plugin settings in database * @return bool * * @since 4.0.0 */ private function updateParams(): bool { $plugin = PluginHelper::getPlugin($this->_type, $this->_name); if (strlen((string)$this->params) > pow(2,16) - 1) return false; $db = Factory::getDbo(); $query = $db->getQuery(true); $query->update('#__extensions') ->set('params=' . $db->quote((string)$this->params)) ->where('extension_id = ' . (int)$plugin->id); try { $db->lockTable('#__extensions'); } catch (Exception $e) { return false; } try { $result = $db->setQuery($query)->execute(); } catch (Exception $e) { $db->unlockTables(); return false; } $db->unlockTables(); $this->cookieList = null; return $result; } /** * Parse expiration string into expiration and expiration unit * * @param string $expirationStr * @param string $expiration * @param string $expirationUnit * * @throws Exception * @since 4.0.0 */ private function parseExpiration(string $expirationStr, ?string &$expiration, ?string &$expirationUnit) { $expiration = $expirationStr; $expirationUnit = ''; if ($this->isMultiLanguage) { switch (true) { case strpos(strtolower($expiration), 'session') !== false: $expiration = $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_SESSION'); break; case strpos(strtolower($expiration), 'various') !== false: $expiration = $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_VARIOUS'); break; case strpos(strtolower($expiration), 'year') !== false: $expiration = (int)$expiration; $expirationUnit = 'years'; break; case strpos(strtolower($expiration), 'month') !== false: $expiration = (int)$expiration; $expirationUnit = 'months'; break; case strpos(strtolower($expiration), 'day') !== false: $expiration = (int)$expiration; $expirationUnit = 'days'; break; case strpos(strtolower($expiration), 'hour') !== false: $expiration = (int)$expiration; $expirationUnit = 'hours'; break; case strpos(strtolower($expiration), 'minute') !== false: $expiration = (int)$expiration; $expirationUnit = 'minutes'; break; case strpos(strtolower($expiration), 'second') !== false: $expiration = (int)$expiration; $expirationUnit = 'seconds'; break; } } else { switch (true) { case strpos(strtolower($expiration), 'session') !== false: $expiration = $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_SESSION'); break; case strpos(strtolower($expiration), 'various') !== false: $expiration = $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_VARIOUS'); break; case strpos(strtolower($expiration), 'year') !== false: $expiration = Text::plural('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_YEARS', (int) $expiration); break; case strpos(strtolower($expiration), 'month') !== false: $expiration = Text::plural('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_MONTHS', (int) $expiration); break; case strpos(strtolower($expiration), 'day') !== false: $expiration = Text::plural('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_DAYS', (int) $expiration); break; case strpos(strtolower($expiration), 'hour') !== false: $expiration = Text::plural('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_HOURS', (int) $expiration); break; case strpos(strtolower($expiration), 'minute') !== false: $expiration = Text::plural('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_MINUTES', (int) $expiration); break; case strpos(strtolower($expiration), 'second') !== false: $expiration = Text::plural('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_SECONDS', (int) $expiration); break; } } } /** * Search cookie databases for cookie and return its description * * @param string $cookieName * * @return ?array * * @throws Exception * @since 4.0.0 */ private function searchCookieDatabase(string $cookieName): ?array { $cookieData = null; // Joomla cookie database $lang = Factory::getApplication()->getLanguage(); if (($handle = fopen(__DIR__ . '/data/joomla.csv', "r")) !== false) { while (($data = fgetcsv($handle)) !== false) { if (count($data) < 7) continue; $name = $data[1]; $name = str_replace('(n3t_cc)', $this->params->get('cookie_name', 'n3t_cc'), $name); if ((int)$data[3]) $name = ApplicationHelper::getHash($name); if ((int)$data[4]) $name = md5($name); if (!(int)$data[2] && $cookieName !== $name) continue; if ((int)$data[2] && !preg_match('~' . $name . '~', $cookieName)) continue; $cookieData = [ 'name' => $name, 'description' => $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_DATABASE_' . $data[5]), 'regex' => (int)$data[2], 'provider' => $this->multilang('SITENAME'), 'expiration' => $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_UNKNOWN'), 'category' => $data[6], ]; switch ($data[7]) { case 'plugin': $info = explode('|', $data[8]); if (count($info) < 5) break; $plugin = PluginHelper::getPlugin($info[0], $info[1]); if (!$plugin) break; $params = new Registry($plugin->params); if ($this->isMultiLanguage) { $cookieData['expiration'] = (int)$params->get($info[2], (int)$info[3]); $cookieData['expiration_unit'] = $info[4]; } else $cookieData['expiration'] = Text::plural('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_' . strtoupper($info[4]), (int)$params->get($info[2], (int)$info[3])); break; case 'session': case 'various': case 'unknown': $cookieData['expiration'] = $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_' . strtoupper($data[7])); break; default: if ($this->isMultiLanguage) { $cookieData['expiration'] = $data[7]; $cookieData['expiration_unit'] = $data[8]; } else $cookieData['expiration'] = Text::plural('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_' . strtoupper($data[7]), (int)$data[8]); } break; } fclose($handle); } // Open cookie database if (!$cookieData && (($handle = fopen(__DIR__ . '/data/open-cookie-database.csv', "r")) !== false)) { while (($data = fgetcsv($handle)) !== false) { if (count($data) < 10) continue; if (!(int)$data[9] && $cookieName !== $data[3]) continue; if ((int)$data[9] && strpos($cookieName, $data[3]) !== 0) continue; $description = $data[5]; if ($lang->hasKey('PLG_SYSTEM_N3TCOOKIECONSENT_DATABASE_' . $data[0])) $description = $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_DATABASE_' . $data[0]); $this->parseExpiration($data[6], $expiration, $expirationUnit); $cookieData = [ 'name' => (int)$data[9] ? '^' . $data[3] : $data[3], 'description' => $description, 'regex' => (int)$data[9], 'provider' => $data[1], 'expiration' => $expiration, 'expiration_unit' => $expirationUnit, ]; switch (strtolower($data[2])) { case 'functional': $cookieData['category'] = self::BLOCK_FUNCTIONAL; break; case 'analytics': $cookieData['category'] = self::BLOCK_ANALYTICS; break; case 'marketing': $cookieData['category'] = self::BLOCK_MARKETING; break; default: $cookieData['category'] = self::BLOCK_HIDDEN; break; } break; } fclose($handle); } // Unknown Cookie if (!$cookieData) { $cookieData = [ 'name' => $cookieName, 'description' => $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_DESCRIPTION_UNKNOWN'), 'regex' => 0, 'provider' => $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_PROVIDER_UNKNOWN'), 'expiration' => $this->multilang('PLG_SYSTEM_N3TCOOKIECONSENT_SCAN_COOKIE_EXPIRATION_UNKNOWN'), 'category' => self::BLOCK_HIDDEN, ]; } return $cookieData; } /** * Adds new unknown Cookie to "Unknown" category * * @param string $cookieName * * @return bool * * @throws Exception * @since 4.0.0 */ private function registerUnknownCookie(string $cookieName): bool { $cookieName = trim($cookieName); if (!$cookieName) return false; if ($this->isCookieRegistered($cookieName)) return false; if ($this->isScanMode) { $scannedList = Factory::getApplication()->getSession()->get('n3tcc_scan_list', []); if (!in_array($cookieName, $scannedList)) { $scannedList[] = $cookieName; Factory::getApplication()->getSession()->set('n3tcc_scan_list', $scannedList); } } $this->debuggerCollectedCookies[] = $cookieName; $cookieData = $this->searchCookieDatabase($cookieName); if ($cookieData['category'] === self::SCAN_IGNORE) return false; $blocks = (array)$this->params->get('blocks', []); $blockIndex = null; foreach ($blocks as $index => $block) { if ($block->type == $cookieData['category'] || isset($block->alias) && $block->alias == $cookieData['category']) { $blockIndex = $index; break; } } if ($blockIndex !== null) $block = $blocks[$blockIndex]; else { $block = new stdClass(); $block->type = $cookieData['category']; $block->alias = ''; $block->title = ''; $block->description = ''; $block->default_enabled = 0; $block->readonly = 0; $block->cookies = []; $blocks[] = $block; } unset($cookieData['category']); if (isset($block->cookies) && !empty($block->cookies)) $block->cookies = (array)$block->cookies; else $block->cookies = []; $block->cookies[] = (object)$cookieData; $this->params->set('blocks', $blocks); $this->cookieList = null; return true; } /** * Returns log file based on params * * @return string * * @since 4.0.0 */ private function logFile(): string { return $this->params->get('log_consents_rotate') ? self::LOG_FILE : 'keep/' . self::LOG_FILE; } /** * Joomla onBeforeRender Event * * @since 4.0.0 */ public function onBeforeRender() { $app = Factory::getApplication(); if ($this->isScanModeFinished) { $script = "window.parent.postMessage('n3t_cookie_consent_finish_scan', '*');"; if (Version::MAJOR_VERSION < 4) Factory::getDocument()->addScriptDeclaration($script); else { $wa = Factory::getApplication()->getDocument()->getWebAssetManager(); $wa->addInlineScript($script); } } if (!$app->isClient('administrator')) return; $hasDefinition = false; $blocks = (array)$this->params->get('blocks', []); foreach ($blocks as $block) { if (isset($block->cookies) && !empty($block->cookies)) { $hasDefinition = true; break; } } if (!$hasDefinition) { $this->loadLanguage(); $plugin = PluginHelper::getPlugin($this->_type, $this->_name); $app->enqueueMessage(Text::sprintf('PLG_SYSTEM_N3TCOOKIECONSENT_WARNING_SETUP_COOKIES_FIRST', Route::_('index.php?option=com_plugins&view=plugin&task=plugin.edit&extension_id=' . $plugin->id)), 'warning'); } } /** * @param string $attrStr * * @return array * * @since 4.0.2 */ private function parseHtmlAttributes(string $htmlCode, array &$debuggerData = []): array { $dom = new \DomDocument(); $dom->loadHTML($htmlCode); $elem = $dom->getElementsByTagName('iframe')->item(0); $attrs = []; foreach ($elem->attributes as $name => $value) $attrs[$name] = $value->textContent; return $attrs; } /** * Joomla onAfterRender Event * * @since 4.0.0 */ public function onAfterRender() { $app = Factory::getApplication(); if (!$this->isEnabled()) return; $buffer = $app->getBody(); if (strpos($buffer, 'loadLanguage(); $html = $this->htmlOutput(); if ($html) $buffer = preg_replace('~(]*)?>)~', '$1' . $html, $buffer); if (strpos($buffer, '{n3tcookieconsent ') !== false) $buffer = preg_replace('~{n3tcookieconsent\ssettings}([^{]*){/n3tcookieconsent}~', '$1', $buffer); if ($this->params->get('youtube_nocookie', true)) { if (strpos($buffer, '