property1 = "a view property"; * $view->property2 = "another view property"; * return $view->render(); * } * * * @api */ class View implements ViewInterface { private $template = ''; /** * Instance * @var Environment */ private $twig; protected $templateVars = array(); private $contentType = 'text/html; charset=utf-8'; private $xFrameOptions = null; private $enableCacheBuster = true; private $useStrictReferrerPolicy = true; /** * Can be disabled to not send headers when rendering a view. This can be useful if heaps of views are being * rendered during one request to possibly prevent a segmentation fault see eg #15307 . It should not be disabled * for a main view, but could be disabled for views that are being rendered eg during a twig event as a "subview" which * is part of the "main view". * @var bool */ public $sendHeadersWhenRendering = true; /** * Constructor. * * @param string $templateFile The template file to load. Must be in the following format: * `"@MyPlugin/templateFileName"`. Note the absence of .twig * from the end of the name. */ public function __construct($templateFile) { $templateExt = '.twig'; if (substr($templateFile, -strlen($templateExt)) !== $templateExt) { $templateFile .= $templateExt; } $this->template = $templateFile; $this->initializeTwig(); $this->piwik_version = Version::VERSION; $this->userLogin = Piwik::getCurrentUserLogin(); $this->isSuperUser = Access::getInstance()->hasSuperUserAccess(); // following is used in ajaxMacros called macro (showMoreHelp as passed in other templates) - requestErrorDiv $isGeneralSettingsAdminEnabled = Controller::isGeneralSettingsAdminEnabled(); $isPluginsAdminEnabled = CorePluginsAdmin::isPluginsAdminEnabled(); // simplify template usage $this->showMoreFaqInfo = $this->isSuperUser && ($isGeneralSettingsAdminEnabled || $isPluginsAdminEnabled); try { $this->piwikUrl = SettingsPiwik::getPiwikUrl(); } catch (Exception $ex) { // pass (occurs when DB cannot be connected to, perhaps piwik URL cache should be stored in config file...) } $this->userRequiresPasswordConfirmation = Piwik::doesUserRequirePasswordConfirmation(Piwik::getCurrentUserLogin()); } /** * Disables the cache buster (adding of ?cb=...) to JavaScript and stylesheet files */ public function disableCacheBuster() { $this->enableCacheBuster = false; } /** * Returns the template filename. * * @return string */ public function getTemplateFile() { return $this->template; } /** * Returns the variables to bind to the template when rendering. * * @param array $override Template variable override values. Mainly useful * when including View templates in other templates. * @return array */ public function getTemplateVars($override = array()) { return $override + $this->templateVars; } /** * Directly assigns a variable to the view script. * Variable names may not be prefixed with '_'. * * @param string $key The variable name. * @param mixed $val The variable value. */ public function __set($key, $val) { $this->templateVars[$key] = $val; } /** * Retrieves an assigned variable. * Variable names may not be prefixed with '_'. * * @param string $key The variable name. * @return mixed The variable value. */ public function &__get($key) { return $this->templateVars[$key]; } /** * Returns true if a template variable has been set or not. * * @param string $name The name of the template variable. * @return bool */ public function __isset($name) { return isset($this->templateVars[$name]); } /** * Unsets a template variable. * * @param string $name The name of the template variable. */ public function __unset($name) { unset($this->templateVars[$name]); } private function initializeTwig() { $this->twig = StaticContainer::get(Twig::class)->getTwigEnvironment(); } /** * Renders the current view. Also sends the stored 'Content-Type' HTML header. * See {@link setContentType()}. * * @return string Generated template. */ public function render() { try { $this->currentModule = Piwik::getModule(); $this->currentAction = Piwik::getAction(); $this->url = Common::sanitizeInputValue(Url::getCurrentUrl()); $this->token_auth = Piwik::getCurrentUserTokenAuth(); $this->userHasSomeAdminAccess = Piwik::isUserHasSomeAdminAccess(); $this->userIsAnonymous = Piwik::isUserIsAnonymous(); $this->userIsSuperUser = Piwik::hasUserSuperUserAccess(); $this->latest_version_available = UpdateCheck::isNewestVersionAvailable(); $this->showUpdateNotificationToUser = !SettingsPiwik::isShowUpdateNotificationToSuperUsersOnlyEnabled() || Piwik::hasUserSuperUserAccess(); $this->disableLink = Common::getRequestVar('disableLink', 0, 'int'); $this->isWidget = Common::getRequestVar('widget', 0, 'int'); $this->isMultiServerEnvironment = SettingsPiwik::isMultiServerEnvironment(); $this->isInternetEnabled = SettingsPiwik::isInternetEnabled(); $this->shouldPropagateTokenAuth = $this->shouldPropagateTokenAuthInAjaxRequests(); $piwikAds = StaticContainer::get('Piwik\ProfessionalServices\Advertising'); $this->areAdsForProfessionalServicesEnabled = $piwikAds->areAdsForProfessionalServicesEnabled(); if (Development::isEnabled()) { $cacheBuster = rand(0, 10000); } else { $cacheBuster = UIAssetCacheBuster::getInstance()->piwikVersionBasedCacheBuster(); } $this->cacheBuster = $cacheBuster; $this->loginModule = Piwik::getLoginPluginName(); } catch (Exception $e) { Log::debug($e); // can fail, for example at installation (no plugin loaded yet) } if ($this->sendHeadersWhenRendering) { ProxyHttp::overrideCacheControlHeaders('no-store'); Common::sendHeader('Content-Type: ' . $this->contentType); // always sending this header, sometimes empty, to ensure that Dashboard embed loads // - when calling sendHeader() multiple times, the last one prevails if(!empty($this->xFrameOptions)) { Common::sendHeader('X-Frame-Options: ' . (string)$this->xFrameOptions); } // don't send Referer-Header for outgoing links if (!empty($this->useStrictReferrerPolicy)) { Common::sendHeader('Referrer-Policy: same-origin'); } else { // always send explicit default header Common::sendHeader('Referrer-Policy: no-referrer-when-downgrade'); } // this will be an empty string if CSP is disabled $cspHeader = StaticContainer::get(SecurityPolicy::class)->createHeaderString(); if ('' !== $cspHeader) { Common::sendHeader($cspHeader); } } return $this->renderTwigTemplate(); } /** * @internal * @ignore * @return Environment */ public function getTwig() { return $this->twig; } protected function renderTwigTemplate() { $output = $this->twig->render($this->getTemplateFile(), $this->getTemplateVars()); if ($this->enableCacheBuster) { $output = $this->applyFilter_cacheBuster($output); } $helper = new Theme; $output = $helper->rewriteAssetsPathToTheme($output); return $output; } protected function applyFilter_cacheBuster($output) { $cacheBuster = UIAssetCacheBuster::getInstance(); $cache = Cache::getTransientCache(); $cssCacheBusterId = $cache->fetch('cssCacheBusterId'); if (empty($cssCacheBusterId)) { $assetManager = AssetManager::getInstance(); $stylesheet = $assetManager->getMergedStylesheetAsset(); if ($stylesheet->exists()) { $content = $stylesheet->getContent(); } else { $content = $assetManager->getMergedStylesheet()->getContent(); } $cssCacheBusterId = $cacheBuster->md5BasedCacheBuster($content); $cache->save('cssCacheBusterId', $cssCacheBusterId); } $tagJs = 'cb=' . ($this->cacheBuster ?? $cacheBuster->piwikVersionBasedCacheBuster()); $tagCss = 'cb=' . $cssCacheBusterId; $pattern = array( '~