mustache_templates-8.x-1.0-beta4/src/Plugin/mustache/Magic/Condition.php
src/Plugin/mustache/Magic/Condition.php
<?php
namespace Drupal\mustache\Plugin\mustache\Magic;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\mustache\Plugin\MustacheMagic;
/**
* Perform simple condition checks within Mustache templates.
*
* Usage:
*
* You can use the "magical" {{#if.<data>.<condition>}} variable to perform
* conditions checks on given data. Examples:
*
* @code
* {{#if.node.title.empty}}Title is empty!{{/if.node.title.empty}}
* {{^if.node.id.numeric}}ID is not numeric...{{/if.node.id.numeric}}
* {{#if.user.uid.equals.1}}You are the admin.{{/if.user.uid.equals.1}}
* {{#if.user.uid.greaterthan.1}}You are authenticated.{{/if.user.uid.greaterthan.1}}
* {{#if.user.uid.lessthan.1}}You are anonymous.{{/if.user.uid.lessthan.1}}
* {{#if.node.field_mytodolist.atleast.1}}You have work to do.{{/if.node.field_mytodolist.atleast.1}}
* {{#if.node.field_mylazylist.atmost.0}}No time to be lazy.{{/if.node.field_mylazylist.atmost.1}}
* @endcode
*
* @MustacheMagic(
* id = "if",
* label = @Translation("Condition"),
* description = @Translation("Use the <b>{{#if.<data>.<condition>}}</b> variable to perform condition checks on given data. Examples: <ul><li>{{#if.node.title.empty}}Title is empty!{{/if.node.title.empty}}</li><li>{{^if.node.id.numeric}}ID is not numeric...{{/if.node.id.numeric}}</li><li>{{#if.user.uid.equals.1}}You are the admin.{{/if.user.uid.equals.1}}</li><li>{{#if.user.uid.greaterthan.1}}You are authenticated.{{/if.user.uid.greaterthan.1}}</li><li>{{#if.user.uid.lessthan.1}}You are anonymous.{{/if.user.uid.lessthan.1}}</li><li>{{#if.node.field_mytodolist.atleast.1}}You have work to do.{{/if.node.field_mytodolist.atleast.1}}</li><li>{{#if.node.field_mylazylist.atmost.0}}No time to be lazy.{{/if.node.field_mylazylist.atmost.1}}</li></ul>")
* )
*/
class Condition extends MustacheMagic {
/**
* The condition type.
*
* @var string|null
*/
protected $condition;
/**
* The comparison value to evaluate against.
*
* @var mixed
*/
protected $comparisonValue = NULL;
/**
* The keys which point to the data value that should be evaluated.
*
* @var array
*/
protected $keys = [];
/**
* Whether this object is a clone.
*
* @var bool
*/
protected $cloned = FALSE;
/**
* Available condition types.
*
* @var array
*/
protected static $available = [
'defined' => 0,
'empty' => 0,
'numeric' => 0,
'equals' => 1,
'greaterthan' => 1,
'biggerthan' => 1,
'lessthan' => 1,
'smallerthan' => 1,
'atleast' => 1,
'atmost' => 1,
];
/**
* The client library for magic conditions.
*
* @var string
*/
public static $library = 'mustache/magic.condition';
/**
* Implementation of the magic __isset() method.
*/
public function __isset($name): bool {
return !empty($name) || (!is_null($name) && is_scalar($name) && (trim((string) $name) !== ''));
}
/**
* Implementation of the magic __get() method.
*/
public function __get($name) {
if (!$this->cloned) {
// Clone the object, so that multiple usages of {{#if..}} conditions
// in one template will not interfere with each other.
$cloned = clone $this;
$cloned->cloned = TRUE;
return $cloned->__get($name);
}
if (isset($this->condition)) {
if (static::$available[$this->condition]) {
if (strtolower(trim($name)) == 'previous') {
$success = FALSE;
$scope = $this->switchDataScope($success, array_merge(['previous'], $this->keys));
if ($success) {
if (is_scalar($scope)) {
$this->comparisonValue = $scope;
}
elseif (is_object($scope) && method_exists($scope, '__toString')) {
$this->comparisonValue = (string) $scope;
}
elseif (is_array($scope) || is_object($scope) || is_null($scope)) {
$this->comparisonValue = json_encode($scope);
}
}
}
else {
$this->comparisonValue = $name;
}
}
return $this->evaluate();
}
elseif (isset(static::$available[$name])) {
$this->condition = $name;
if (!static::$available[$this->condition]) {
return $this->evaluate();
}
return $this;
}
$this->keys[] = $name;
return $this;
}
/**
* Evaluates the condition.
*
* @return bool
* Returns TRUE if condition evaluates to be true, FALSE otherwise.
*/
protected function evaluate() {
// Attach the library at the beginning, so that BubbleableMetadata will
// include that one (and will not forget about it).
$this->attachLibrary();
$success = FALSE;
$scope = $this->switchDataScope($success, $this->keys, $this->withTokens());
if (!$success || !isset($scope)) {
return $this->condition === 'empty';
}
if (!isset($this->condition)) {
$this->condition = 'empty';
return !$this->evaluate();
}
if (!isset(static::$available[$this->condition]) || (static::$available[$this->condition] && !isset($this->comparisonValue))) {
return FALSE;
}
switch ($this->condition) {
case 'defined':
return TRUE;
case 'empty':
return is_countable($scope) ? !count($scope) : empty($scope);
case 'numeric':
return is_object($scope) && method_exists($scope, '__toString') ? is_numeric((string) $scope) : is_numeric($scope);
case 'equals':
$actual_value = is_scalar($scope) || (is_object($scope) && method_exists($scope, '__toString')) ? (string) $scope : $scope;
if (is_array($actual_value) || is_object($actual_value) || is_null($actual_value)) {
$actual_value = json_encode($actual_value);
}
return $actual_value === (string) $this->comparisonValue;
case 'biggerthan':
case 'greaterthan':
if (is_object($scope) && method_exists($scope, '__toString')) {
$string_value = (string) $scope;
}
$actual_value = isset($string_value) && is_numeric($string_value) ? $string_value : $scope;
if (is_countable($actual_value)) {
if (is_numeric($this->comparisonValue)) {
return count($actual_value) > $this->comparisonValue;
}
if (is_countable($this->comparisonValue)) {
return count($actual_value) > count($this->comparisonValue);
}
}
$actual_value = is_object($scope) && method_exists($scope, '__toString') ? (string) $scope : $scope;
if (is_scalar($actual_value)) {
if (is_numeric($actual_value) && is_numeric($this->comparisonValue)) {
return $actual_value > $this->comparisonValue;
}
return mb_strlen((string) $actual_value) > mb_strlen((string) $this->comparisonValue);
}
return FALSE;
case 'smallerthan':
case 'lessthan':
if (is_object($scope) && method_exists($scope, '__toString')) {
$string_value = (string) $scope;
}
$actual_value = isset($string_value) && is_numeric($string_value) ? $string_value : $scope;
if (is_countable($actual_value)) {
if (is_numeric($this->comparisonValue)) {
return count($actual_value) < $this->comparisonValue;
}
if (is_countable($this->comparisonValue)) {
return count($actual_value) < count($this->comparisonValue);
}
}
$actual_value = is_object($scope) && method_exists($scope, '__toString') ? (string) $scope : $scope;
if (is_scalar($actual_value)) {
if (is_numeric($actual_value) && is_numeric($this->comparisonValue)) {
return $actual_value < $this->comparisonValue;
}
return mb_strlen((string) $actual_value) < mb_strlen((string) $this->comparisonValue);
}
return FALSE;
case 'atleast':
if (is_object($scope) && method_exists($scope, '__toString')) {
$string_value = (string) $scope;
}
$actual_value = isset($string_value) && is_numeric($string_value) ? $string_value : $scope;
if (is_countable($actual_value)) {
if (is_numeric($this->comparisonValue)) {
return count($actual_value) >= $this->comparisonValue;
}
if (is_countable($this->comparisonValue)) {
return count($actual_value) >= count($this->comparisonValue);
}
}
$actual_value = is_object($scope) && method_exists($scope, '__toString') ? (string) $scope : $scope;
if (is_scalar($actual_value)) {
if (is_numeric($actual_value) && is_numeric($this->comparisonValue)) {
return $actual_value >= $this->comparisonValue;
}
return mb_strlen((string) $actual_value) >= mb_strlen((string) $this->comparisonValue);
}
return FALSE;
case 'atmost':
if (is_object($scope) && method_exists($scope, '__toString')) {
$string_value = (string) $scope;
}
$actual_value = isset($string_value) && is_numeric($string_value) ? $string_value : $scope;
if (is_countable($actual_value)) {
if (is_numeric($this->comparisonValue)) {
return count($actual_value) <= $this->comparisonValue;
}
if (is_countable($this->comparisonValue)) {
return count($actual_value) <= count($this->comparisonValue);
}
}
$actual_value = is_object($scope) && method_exists($scope, '__toString') ? (string) $scope : $scope;
if (is_scalar($actual_value)) {
if (is_numeric($actual_value) && is_numeric($this->comparisonValue)) {
return $actual_value <= $this->comparisonValue;
}
return mb_strlen((string) $actual_value) <= mb_strlen((string) $this->comparisonValue);
}
return FALSE;
}
return FALSE;
}
/**
* Switches the data scope by using given data keys.
*
* @param bool &$success
* A flag that indicates whether the switch was completed successfully.
* @param array $keys
* The keys for being used to traverse throughout the data.
* @param bool $include_tokens
* (optional) Whether Token matches should be looked up. Default is FALSE.
*
* @return mixed
* The switched scope within the data.
*/
protected function &switchDataScope(&$success, array $keys, $include_tokens = FALSE) {
$scope = &$this->data;
foreach ($keys as $i => $key) {
if ((($scope instanceof \ArrayAccess) || is_array($scope)) && isset($scope[$key])) {
$scope = &$scope[$key];
continue;
}
if (is_object($scope)) {
if (method_exists($scope, $key)) {
$reflection = new \ReflectionMethod($scope, $key);
if (!$reflection->getNumberOfRequiredParameters()) {
$scope = &$scope->$key();
continue;
}
}
if (isset($scope->$key)) {
$scope = &$scope->$key;
continue;
}
}
unset($scope);
if (!$i && $include_tokens) {
$token_data = isset($this->element['#with_tokens']['data']) ? $this->element['#with_tokens']['data'] : [];
$token_options = isset($this->element['#with_tokens']['options']) ? $this->element['#with_tokens']['options'] : [];
$bubbleable_metadata = BubbleableMetadata::createFromRenderArray($this->element);
if (!$token_data) {
/** @var \Drupal\mustache\MustacheTokenProcessor $token_processor */
$token_processor = \Drupal::service('mustache.token_processor');
$fake_tokens = [];
NestedArray::setValue($fake_tokens, $keys, '[' . implode(':', $keys) . ']');
$token_processor->processData($token_data, $fake_tokens, 'view', $bubbleable_metadata);
}
$target = static::getTokenIterate()->getIterableTarget($keys, $token_data, $token_options, $bubbleable_metadata);
$bubbleable_metadata->applyTo($this->element);
if ($target->exists() && isset($target[$key])) {
$scope = &$target[$key];
continue;
}
}
$success = FALSE;
return $scope;
}
$success = TRUE;
return $scope;
}
/**
* Get the token iterate service.
*
* @return \Drupal\mustache\MustacheTokenIterate
* The token iterate service.
*/
protected static function getTokenIterate() {
return \Drupal::service('mustache.token_iterate');
}
}
