1<?php 2/** 3 * Matomo - free/libre analytics platform 4 * 5 * @link https://matomo.org 6 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later 7 */ 8namespace Piwik\Plugins\TagManager\Template; 9 10use JShrink\Minifier; 11use Piwik\Common; 12use Piwik\Container\StaticContainer; 13use Piwik\Development; 14use Piwik\Piwik; 15use Piwik\Plugins\CorePluginsAdmin\SettingsMetadata; 16use Piwik\Plugins\TagManager\Context\WebContext; 17use Piwik\Plugins\TagManager\Settings\Storage\Backend\TransientBackend; 18use Piwik\Settings\Setting; 19use Piwik\Settings\Storage\Storage; 20 21/** 22 * @api 23 */ 24abstract class BaseTemplate 25{ 26 private $pluginName = null; 27 28 protected $templateType = ''; 29 30 const FIELD_TEMPLATE_VARIABLE = 'plugins/TagManager/angularjs/form-field/field-variable-template.html'; 31 const FIELD_TEMPLATE_TEXTAREA_VARIABLE = 'plugins/TagManager/angularjs/form-field/field-textarea-variable-template.html'; 32 const FIELD_TEMPLATE_VARIABLE_TYPE = 'plugins/TagManager/angularjs/form-field/field-variabletype-template.html'; 33 public static $RESERVED_SETTING_NAMES = [ 34 'container', 'tag', 'variable', 'trigger', 'length', 'window', 'document', 'get', 'fire', 'setUp', 'set', 'reset', 'type', 'part', 35 'default_value', 'lookup_table', 'conditions', 'condition', 'fire_limit', 'fire_delay', 'priority', 'parameters', 36 'start_date', 'end_date', 'type', 'name', 'status' 37 ]; 38 39 private $settingsStorage; 40 41 /** 42 * Get the ID of this template. 43 * The ID is by default automatically generated from the class name, but can be customized by returning a string. 44 * 45 * @return string 46 */ 47 public function getId() 48 { 49 return $this->makeIdFromClassname($this->templateType); 50 } 51 52 /** 53 * Get the list of parameters that can be configured for this template. 54 * @return Setting[] 55 */ 56 abstract public function getParameters(); 57 58 /** 59 * Get the category this template belongs to. 60 * @return string 61 */ 62 abstract public function getCategory(); 63 64 /** 65 * Defines in which contexts this tag should be available, for example "web". 66 * @return string[] 67 */ 68 abstract public function getSupportedContexts(); 69 70 private function getTranslationKey($part) 71 { 72 if (empty($this->templateType)) { 73 return ''; 74 } 75 76 if (!isset($this->pluginName)) { 77 $classname = get_class($this); 78 $parts = explode('\\', $classname); 79 80 if (count($parts) >= 4 && $parts[1] === 'Plugins') { 81 $this->pluginName = $parts[2]; 82 } 83 } 84 if (isset($this->pluginName)) { 85 return $this->pluginName . '_' . $this->getId() . $this->templateType . $part; 86 } 87 88 return ''; 89 } 90 91 /** 92 * Get the translated name of this template. 93 * @return string 94 */ 95 public function getName() 96 { 97 $key = $this->getTranslationKey('Name'); 98 if ($key) { 99 $translated = Piwik::translate($key); 100 if ($translated === $key) { 101 return $this->getId(); 102 } 103 return $translated; 104 } 105 return $this->getId(); 106 } 107 108 /** 109 * Get the translated description of this template. 110 * @return string 111 */ 112 public function getDescription() 113 { 114 $key = $this->getTranslationKey('Description'); 115 if ($key) { 116 $translated = Piwik::translate($key); 117 if ($translated === $key) { 118 return ''; 119 } 120 return $translated; 121 } 122 } 123 124 /** 125 * Get the translated help text for this template. 126 * @return string 127 */ 128 public function getHelp() 129 { 130 $key = $this->getTranslationKey('Help'); 131 if ($key) { 132 $translated = Piwik::translate($key); 133 if ($translated === $key) { 134 return ''; 135 } 136 return $translated; 137 } 138 } 139 140 /** 141 * Get the order for this template. The lower the order is, the higher in the list the template will be shown. 142 * @return int 143 */ 144 public function getOrder() 145 { 146 return 9999; 147 } 148 149 /** 150 * Get the image icon url. We could also use data:uris to return the amount of requests to load a page like this: 151 * return 'data:image/svg+xml;base64,' . base64_encode('<svg...</svg>'); 152 * However, we prefer the files since we can better define them in the legal notice. 153 * 154 * @return string 155 */ 156 public function getIcon() 157 { 158 return 'plugins/TagManager/images/defaultIcon.svg'; 159 } 160 161 /** 162 * Creates a new setting / parameter. 163 * 164 * Settings will be displayed in the UI depending on the order of `makeSetting` calls. This means you can define 165 * the order of the displayed settings by calling makeSetting first for more important settings. 166 * 167 * @param string $name The name of the setting that shall be created 168 * @param mixed $defaultValue The default value for this setting. Note the value will not be converted to the 169 * specified type. 170 * @param string $type The PHP internal type the value of this setting should have. 171 * Use one of FieldConfig::TYPE_* constancts 172 * @param \Closure $fieldConfigCallback A callback method to configure the field that shall be displayed in the 173 * UI to define the value for this setting 174 * @return Setting Returns an instance of the created measurable setting. 175 */ 176 protected function makeSetting($name, $defaultValue, $type, $fieldConfigCallback) 177 { 178 if (in_array(strtolower($name), self::$RESERVED_SETTING_NAMES, true)) { 179 throw new \Exception(sprintf('The setting name "%s" is reserved and cannot be used', $name)); 180 } 181 182 // we need to make sure to create new instance of storage all the time to prevent "leaking" using values across 183 // multiple tags, or triggers, or variables 184 $this->settingsStorage = new Storage(new TransientBackend($this->getId())); 185 186 $setting = new Setting($name, $defaultValue, $type, 'TagManager'); 187 $setting->setStorage($this->settingsStorage); 188 $setting->setConfigureCallback($fieldConfigCallback); 189 $setting->setIsWritableByCurrentUser(true); // we validate access on API level. 190 191 return $setting; 192 } 193 194 /** 195 * @ignore 196 */ 197 public function loadTemplate($context, $entity) 198 { 199 switch ($context) { 200 case WebContext::ID: 201 $className = get_class($this); 202 $autoloader_reflector = new \ReflectionClass($className); 203 $fileName = $autoloader_reflector->getFileName(); 204 205 $lenPhpExtension = 3; 206 $base = substr($fileName, 0 , -1 * $lenPhpExtension); 207 $file = $base . 'web.js'; 208 $minFile = $base . 'web.min.js'; 209 210 if (!StaticContainer::get('TagManagerJSMinificationEnabled')) { 211 return $this->loadTemplateFile($file); // avoid minification in test mode 212 } elseif (Development::isEnabled() && $this->hasTemplateFile($file)) { 213 // during dev mode we prefer the non-minified version for debugging purposes, but we still use 214 // the internal minifier to make sure we debug the same as a user would receive 215 $template = $this->loadTemplateFile($file); 216 $minified = Minifier::minify($template); 217 return $minified; 218 } elseif ($this->hasTemplateFile($minFile)) { 219 // recommended when there is a lot of content in the template. For example if the tag contains the 220 // content of a Matomo JS tracker then it will be useful or also in general. 221 return $this->loadTemplateFile($minFile); 222 } elseif ($this->hasTemplateFile($file)) { 223 // it does not minify so well as it doesn't rename variables, however, it does make it a bit smaller 224 // gzip should help with filesize re variables like `tagmanager` etc. 225 // the big advantage is really that JS Min files cannot be out of date or forgotton to be updated 226 $template = $this->loadTemplateFile($file); 227 $minified = Minifier::minify($template); 228 return $minified; 229 } 230 } 231 } 232 233 /** 234 * @ignore 235 */ 236 protected function makeIdFromClassname($rightTrimWord) 237 { 238 $className = get_class($this); 239 $parts = explode('\\', $className); 240 $id = end($parts); 241 242 if ($rightTrimWord && Common::stringEndsWith($id, $rightTrimWord)) { 243 $id = substr($id, 0, -strlen($rightTrimWord)); 244 } 245 246 return $id; 247 } 248 249 /** 250 * @ignore tests only 251 * @param $file 252 * @return bool 253 */ 254 protected function hasTemplateFile($file) 255 { 256 return is_readable($file); 257 } 258 259 /** 260 * @ignore tests only 261 * @param $file 262 * @return string|null 263 */ 264 protected function loadTemplateFile($file) 265 { 266 if ($this->hasTemplateFile($file)) { 267 return trim(file_get_contents($file)); 268 } 269 } 270 271 /** 272 * Lets you hide the advanced settings tab in the UI. 273 * @return bool 274 */ 275 public function hasAdvancedSettings() 276 { 277 return true; 278 } 279 280 /** 281 * If your template allows a user to add js/html code to the site for example, you should be overwriting this 282 * method and return `true`. 283 * @return bool 284 */ 285 public function isCustomTemplate() 286 { 287 return false; 288 } 289 290 /** 291 * @ignore 292 * @return array 293 */ 294 public function toArray() 295 { 296 $settingsMetadata = new SettingsMetadata(); 297 $params = array(); 298 $tagParameters = $this->getParameters(); 299 300 if (!empty($tagParameters)) { 301 foreach ($tagParameters as $parameter) { 302 $param = $settingsMetadata->formatSetting($parameter); 303 if (!empty($param)) { 304 // we need to manually set the value as otherwise a value from an actual tag, trigger, variable,... 305 // might be set because the instance of the template is shared and therefore the storage... 306 $param['value'] = $parameter->getDefaultValue(); 307 $params[] = $param; 308 } 309 } 310 } 311 312 return array( 313 'id' => $this->getId(), 314 'name' => $this->getName(), 315 'description' => $this->getDescription(), 316 'category' => Piwik::translate($this->getCategory()), 317 'icon' => $this->getIcon(), 318 'help' => $this->getHelp(), 319 'order' => $this->getOrder(), 320 'contexts' => $this->getSupportedContexts(), 321 'hasAdvancedSettings' => $this->hasAdvancedSettings(), 322 'isCustomTemplate' => $this->isCustomTemplate(), 323 'parameters' => $params, 324 ); 325 } 326 327 328} 329