| Server IP : 213.186.33.4 / Your IP : 216.73.216.193 Web Server : Apache System : Linux webm006.cluster103.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64 User : awebpaca ( 35430) PHP Version : 8.5.0 Disable Function : _dyuweyrj4,_dyuweyrj4r,dl MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/a/w/e/awebpaca/piwik/plugins/TagManager/Context/ |
Upload File : |
<?php
/**
* Matomo - free/libre analytics platform
*
* @link https://matomo.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/
namespace Piwik\Plugins\TagManager\Context;
use Piwik\Common;
use Piwik\Container\StaticContainer;
use Piwik\Plugins\TagManager\Context\Storage\StorageInterface;
use Piwik\Plugins\TagManager\Exception\EntityRecursionException;
use Piwik\Plugins\TagManager\Model\Container;
use Piwik\Plugins\TagManager\Model\Environment;
use Piwik\Plugins\TagManager\Model\Salt;
use Piwik\Plugins\TagManager\Model\Tag;
use Piwik\Plugins\TagManager\Model\Trigger;
use Piwik\Plugins\TagManager\Model\Variable;
use Piwik\Plugins\TagManager\Template\Variable\VariablesProvider;
use Piwik\Settings\FieldConfig;
abstract class BaseContext
{
/**
* @var VariablesProvider
*/
protected $variablesProvider;
/**
* @var Variable
*/
protected $variableModel;
/**
* @var Trigger
*/
protected $triggerModel;
/**
* @var Tag
*/
protected $tagModel;
/**
* @var Container
*/
protected $containerModel;
/**
* @var StorageInterface
*/
protected $storage;
/**
* @var Salt
*/
protected $salt;
private $variables = array();
private $nestedVariableCals = [];
public function __construct(VariablesProvider $variablesProvider, Variable $variableModel, Trigger $triggerModel, Tag $tagModel, Container $containerModel, StorageInterface $storage, Salt $salt)
{
$this->variablesProvider = $variablesProvider;
$this->variableModel = $variableModel;
$this->triggerModel = $triggerModel;
$this->tagModel = $tagModel;
$this->containerModel = $containerModel;
$this->storage = $storage;
$this->salt = $salt;
}
abstract public function getId();
abstract public function getName();
abstract public function generate($container);
abstract public function getInstallInstructions($container, $environment);
protected function generatePublicContainer($container, $release)
{
$this->nestedVariableCals = [];
$idSite = $container['idsite'];
$idContainer = $container['idcontainer'];
$idContainerVersion = $release['idcontainerversion'];
$container['idcontainerversion'] = $idContainerVersion;
$environment = $release['environment'];
$this->variables = [];
$version = $this->containerModel->getContainerVersion($idSite, $idContainer, $idContainerVersion);
$containerJs = [
'id' => $idContainer,
'idsite' => $idSite,
'versionName' => $version['name'],
'revision' => $version['revision'],
'environment' => $environment,
'tags' => [],
'triggers' => [],
'variables' => [],
];
foreach ($this->tagModel->getContainerTags($idSite, $idContainerVersion) as $tag) {
$containerJs['tags'][] = [
'id' => $tag['idtag'],
'type' => $tag['type'],
'name' => $tag['name'],
'parameters' => $this->parametersToVariableJs($container, $tag),
'blockTriggerIds' => $tag['block_trigger_ids'],
'fireTriggerIds' => $tag['fire_trigger_ids'],
'fireLimit' => $tag['fire_limit'],
'fireDelay' => $tag['fire_delay'],
'startDate' => $tag['start_date'],
'endDate' => $tag['end_date'],
];
}
foreach ($this->triggerModel->getContainerTriggers($idSite, $idContainerVersion) as $trigger) {
// we are ignoring any trigger that is not actually used in any tag for performance reasons
// (for now, we might change this later so triggers can more easily build on top of each other)
$conditions = [];
if (!empty($trigger['conditions'])) {
foreach ($trigger['conditions'] as $condition) {
if (!empty($condition['actual'])) {
$actual = $this->variableToArray($container, $condition['actual']);
if ($actual) {
$conditions[] = [
'actual' => $actual,
'comparison' => $condition['comparison'],
'expected' => $condition['expected']
];
}
}
}
}
$trigger = [
'id' => $trigger['idtrigger'],
'type' => $trigger['type'],
'name' => $trigger['name'],
'parameters' => $this->parametersToVariableJs($container, $trigger),
'conditions' => $conditions,
];
$containerJs['triggers'][] = $trigger;
}
foreach ($this->variableModel->getContainerVariables($idSite, $idContainerVersion) as $variable) {
// we are ignoring any trigger that is not actually used in any tag for performance reasons
// (for now, we might change this later so triggers can more easily build on top of each other)
$this->variableToArray($container, $variable);
}
$containerJs['variables'] = array_values($this->variables);
$this->variables = array();
return $containerJs;
}
private function parametersToVariableJs($container, $entity)
{
if (!empty($entity['name'])) {
$this->nestedVariableCals[] = $entity['name'];
}
if (count($this->nestedVariableCals) > 500) {
// eg MatomoConfiguration variable referencing itself in a variable like matomoUrl=https://matomo.org{{MatomoConfiguration}}
$entries = array_slice($this->nestedVariableCals, -3); // show last 3 entities in error message
$entries = array_unique($entries);
throw new EntityRecursionException('It seems an entity references itself or a recursion is caused in some other way. It may be related due to these entites: "'.implode(',', $entries). '". Please check if the entity references itself maybe or if a recursion might happen in another way.');
}
$parameters = $entity['parameters'];
$keyTemplateTypeSeparator = '____';
$parameterTemplateTypes = array();
if (!empty($entity['typeMetadata']['parameters'])) {
foreach ($entity['typeMetadata']['parameters'] as $parameter) {
// we replace variables only when the field type is a template
if (Variable::hasFieldConfigVariableParameter($parameter)) {
$parameterTemplateTypes[] = $parameter['name'];
}
if (!empty($parameter['uiControl']) && $parameter['uiControl'] === FieldConfig::UI_CONTROL_MULTI_TUPLE
) {
if (!empty($parameter['uiControlAttributes']['field1']['key'])
&& Variable::hasFieldConfigVariableParameter($parameter['uiControlAttributes']['field1'])) {
$parameterTemplateTypes[] = $parameter['name'] . $keyTemplateTypeSeparator . $parameter['uiControlAttributes']['field1']['key'];
}
if (!empty($parameter['uiControlAttributes']['field2']['key'])
&& Variable::hasFieldConfigVariableParameter($parameter['uiControlAttributes']['field2'])) {
$parameterTemplateTypes[] = $parameter['name'] . $keyTemplateTypeSeparator . $parameter['uiControlAttributes']['field2']['key'];
}
}
}
}
$vars = [];
foreach ($parameters as $name => $value) {
if (is_array($value)) {
if (in_array($name, $parameterTemplateTypes, true)) {
foreach ($value as $key => $subValue) {
if (is_array($subValue)) {
foreach ($subValue as $subKey => $subSubValue) {
if (in_array($name . $keyTemplateTypeSeparator . $subKey, $parameterTemplateTypes, true)) {
$value[$key][$subKey] = $this->parameterToVariableJs($subSubValue, $container);
}
}
} else {
$value[$key] = $this->parameterToVariableJs($subValue, $container);
}
}
}
$vars[$name] = $value;
} else {
if (in_array($name, $parameterTemplateTypes, true)) {
$vars[$name] = $this->parameterToVariableJs($value, $container);
} else {
$vars[$name] = $value;
}
}
}
if (!empty($entity['name'])) {
array_pop($this->nestedVariableCals);
}
return $vars;
}
private function mb_strpos($haystack, $needle, $offset) {
if (function_exists('mb_strpos')) {
return mb_strpos($haystack, $needle, $offset, 'UTF-8');
}
return strpos($haystack, $needle, $offset);
}
private function mb_strrpos($haystack, $needle, $offset) {
if (function_exists('mb_strpos')) {
return mb_strrpos($haystack, $needle, $offset, 'UTF-8');
}
return strrpos($haystack, $needle, $offset);
}
protected function parameterToVariableJs($value, $container)
{
if (is_scalar($value) && preg_match_all('/{{.+?}}/', $value, $matches)) {
$multiVars = [];
$pos = 0;
do {
$start = $this->mb_strpos($value, '{{', $pos);
$end = false;
if ($start !== false) {
// only if string contains a {{ we need to look to see if we find a matching end string
$end = $this->mb_strpos($value, '}}', $start);
}
if ($end !== false) {
// now this might seem random, but it is basically to detect if there are the brackets two times there
// like "foo{{notExisting{{PageUrl}}" then we still detect "{{PageUrl}}"
$start = $this->mb_strrpos(Common::mb_substr($value, 0, $end), '{{', $pos);
}
if ($start === false || $end === false) {
$val = $this->substr($value, $pos);
if ($val !== '' && $val !== false && $val !== null) {
$multiVars[] = $val;
}
break;
}
if ($start !== 0) {
// only if string does not start with "{{..."
$val = str_replace(array('\\{', '\\}'), array('{', '}'), $this->substr($value, $pos, $start - $pos)); // regular text
if ($val !== '' && $val !== false && $val !== null) {
$multiVars[] = $val;
}
}
$ignoreLengthOpeningBrackets = 2;
$variableName = Common::mb_substr($value, $start + $ignoreLengthOpeningBrackets, $end - ($start+$ignoreLengthOpeningBrackets));
$trimmedVariableName = trim($variableName);
if ($trimmedVariableName
&& Common::mb_substr($trimmedVariableName, 0, 1) !== '{') { // case when using {{{foobar}}
$var = $this->variableToArray($container, $trimmedVariableName);
if ($var) {
$multiVars[] = $var;
} else {
// the variable does not exist, therefore we simply add the text again
$multiVars[] = '{{' . $variableName . '}}';
}
} else {
// the variable does not exist, therefore we simply add the text again
$multiVars[] = '{{' . $variableName . '}}';
}
$pos = $end + $ignoreLengthOpeningBrackets;
} while ($end !== false);
$allStrings = true;
foreach ($multiVars as $var) {
if (!is_string($var)) {
$allStrings = false;
}
}
if ($allStrings) {
// no variables detected... for simplicity we return one single string
return implode('', $multiVars);
}
if (count($multiVars) === 1) {
return array_shift($multiVars);
} else {
return array('joinedVariable' => $multiVars);
}
}
// just a regular text value but does not contain a variable
return $value;
}
private function substr($str, $start, $length = null)
{
if (function_exists('mb_substr')) {
return mb_substr($str, $start, $length, 'UTF-8');
}
return substr($str, $start, $length);
}
protected function variableToArray($container, $variableNameOrVariable)
{
if (is_array($variableNameOrVariable)) {
$variable = $variableNameOrVariable;
} else {
$variable = $this->variableModel->findVariableByName($container['idsite'], $container['idcontainerversion'], $variableNameOrVariable);
}
if ($variable) {
$lookUpTable = [];
if (!empty($variable['lookup_table']) && is_array($variable['lookup_table'])) {
foreach ($variable['lookup_table'] as $lookup) {
$lookUpTable[] = ['matchValue' => $lookup['match_value'], 'outValue' => $lookup['out_value'], 'comparison' => $lookup['comparison']];
}
}
$var = [
'name' => $variable['name'],
'type' => $variable['type'],
'lookUpTable' => $lookUpTable,
'defaultValue' => $variable['default_value'],
'parameters' => $this->parametersToVariableJs($container, $variable)
];
// by setting var name key we make sure to not include same var twice
$this->variables[$var['name']] = $var;
return $var;
} else {
// try to find pre-configured variable if no user variable found
$variable = $this->variablesProvider->getPreConfiguredVariable($variableNameOrVariable);
if ($variable) {
$defaultParams = [];
foreach ($variable->getParameters() as $parameter) {
$defaultParams[$parameter->getName()] = $parameter->getValue();
}
$var = [
'name' => ucfirst($variable->getId()),
'type' => $variable->getId(),
'lookUpTable' => [],
'defaultValue' => null,
'parameters' => $this->parametersToVariableJs($container, array('parameters' => $defaultParams, 'typeMetadata' => array()))
];
$this->variables[$var['name']] = $var;
return $var;
}
}
}
public function getOrder()
{
return 99;
}
public function toArray()
{
return array(
'id' => $this->getId(),
'name' => $this->getName(),
);
}
public function getJsTargetPath($idSite, $idContainer, $environment, $containerCreatedDate)
{
$idSite = (int) $idSite;
$path = StaticContainer::get('TagManagerContainerStorageDir') . '/' . StaticContainer::get('TagManagerContainerFilesPrefix') . $idContainer;
if ($environment === Environment::ENVIRONMENT_PREVIEW) {
// we do not add a hash here with the salt as the preview may be public, and if this was public, they could
// calculate the salt from the hash which would then allow to calculate other hashes
$path .= '_' . $environment;
} elseif ($environment !== Environment::ENVIRONMENT_LIVE) {
// we need to add a random ID behind it, otherwise people would be able to guess the path to dev or staging
// environment and see in advance what might be rolled out soon, what is being tested, etc. We make sure to
// have two/three random factors in here not only the salt to reduce chances of being able to calculate the salt
$path .= '_' . $environment . '_' . substr(sha1($idContainer . $idSite . $containerCreatedDate . $environment . $this->salt->getSalt()), 0, 24);
}
return $path;
}
public static function removeAllFilesOfAllContainers()
{
$files = self::findFiles(PIWIK_DOCUMENT_ROOT . StaticContainer::get('TagManagerContainerStorageDir'), StaticContainer::get('TagManagerContainerFilesPrefix') . '*.js');
if (!empty($files)) {
foreach ($files as $file) {
self::deleteFile($file);
}
}
return count($files);
}
public static function removeAllContainerFiles($idContainer)
{
if (empty($idContainer) || strlen($idContainer) <= 5) {
return; // prevent accidental deletion of multiple container files
}
$files = self::findFiles(PIWIK_DOCUMENT_ROOT . StaticContainer::get('TagManagerContainerStorageDir'), sprintf('%s%s*.js', StaticContainer::get('TagManagerContainerFilesPrefix'), $idContainer));
if (!empty($files)) {
foreach ($files as $file) {
self::deleteFile($file);
}
}
return count($files);
}
private static function deleteFile($file)
{
$storage = StaticContainer::get('Piwik\Plugins\TagManager\Context\Storage\StorageInterface');
$storage->delete($file);
}
private static function findFiles($sdir, $spattern)
{
$storage = StaticContainer::get('Piwik\Plugins\TagManager\Context\Storage\StorageInterface');
return $storage->find($sdir, $spattern);
}
public static function removeNoLongerExistingEnvironments($availableEnvironments)
{
if (!is_array($availableEnvironments)) {
return array();
}
$availableEnvironments[] = Environment::ENVIRONMENT_LIVE; // we make sure they are set as we never want to remove them
$availableEnvironments[] = Environment::ENVIRONMENT_PREVIEW;
$basePath = PIWIK_DOCUMENT_ROOT . StaticContainer::get('TagManagerContainerStorageDir');
$files = self::findFiles($basePath, StaticContainer::get('TagManagerContainerFilesPrefix') . '*.js');
$environmentsDeleted = array();
if (!empty($files)) {
foreach ($files as $file) {
$filename = str_replace($basePath . '/' . StaticContainer::get('TagManagerContainerFilesPrefix'), '', $file);
$filename = str_replace('.js', '', $filename);
$filename = explode('_', $filename);
if (count($filename) === 3) {
// 0 = container
// 1 = environment
// 2 = hash
// we ignore preview environment and live environment already by design but also define it specifically
$env = $filename[1];
try {
Environment::checkEnvironmentNameFormat($env);
} catch (\Exception $e) {
// for some reason not a valid environment... we make sure to not delete anything weird
continue;
}
if (!in_array($env, $availableEnvironments)) {
$environmentsDeleted[] = $env;
self::deleteFile($file);
}
}
}
}
return $environmentsDeleted;
}
}