Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 401 and 402] [Versions 401 and 403]

   1  <?php
   2  
   3  namespace PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
   4  
   5  use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
   6  use PhpOffice\PhpSpreadsheet\Exception;
   7  use PhpOffice\PhpSpreadsheet\Style\Conditional;
   8  use PhpOffice\PhpSpreadsheet\Style\ConditionalFormatting\Wizard;
   9  
  10  /**
  11   * @method TextValue contains(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
  12   * @method TextValue doesNotContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
  13   * @method TextValue doesntContain(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
  14   * @method TextValue beginsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
  15   * @method TextValue startsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
  16   * @method TextValue endsWith(string $value, string $operandValueType = Wizard::VALUE_TYPE_LITERAL)
  17   */
  18  class TextValue extends WizardAbstract implements WizardInterface
  19  {
  20      protected const MAGIC_OPERATIONS = [
  21          'contains' => Conditional::OPERATOR_CONTAINSTEXT,
  22          'doesntContain' => Conditional::OPERATOR_NOTCONTAINS,
  23          'doesNotContain' => Conditional::OPERATOR_NOTCONTAINS,
  24          'beginsWith' => Conditional::OPERATOR_BEGINSWITH,
  25          'startsWith' => Conditional::OPERATOR_BEGINSWITH,
  26          'endsWith' => Conditional::OPERATOR_ENDSWITH,
  27      ];
  28  
  29      protected const OPERATORS = [
  30          Conditional::OPERATOR_CONTAINSTEXT => Conditional::CONDITION_CONTAINSTEXT,
  31          Conditional::OPERATOR_NOTCONTAINS => Conditional::CONDITION_NOTCONTAINSTEXT,
  32          Conditional::OPERATOR_BEGINSWITH => Conditional::CONDITION_BEGINSWITH,
  33          Conditional::OPERATOR_ENDSWITH => Conditional::CONDITION_ENDSWITH,
  34      ];
  35  
  36      protected const EXPRESSIONS = [
  37          Conditional::OPERATOR_CONTAINSTEXT => 'NOT(ISERROR(SEARCH(%s,%s)))',
  38          Conditional::OPERATOR_NOTCONTAINS => 'ISERROR(SEARCH(%s,%s))',
  39          Conditional::OPERATOR_BEGINSWITH => 'LEFT(%s,LEN(%s))=%s',
  40          Conditional::OPERATOR_ENDSWITH => 'RIGHT(%s,LEN(%s))=%s',
  41      ];
  42  
  43      /** @var string */
  44      protected $operator;
  45  
  46      /** @var string */
  47      protected $operand;
  48  
  49      /**
  50       * @var string
  51       */
  52      protected $operandValueType;
  53  
  54      public function __construct(string $cellRange)
  55      {
  56          parent::__construct($cellRange);
  57      }
  58  
  59      protected function operator(string $operator): void
  60      {
  61          if (!isset(self::OPERATORS[$operator])) {
  62              throw new Exception('Invalid Operator for Text Value CF Rule Wizard');
  63          }
  64  
  65          $this->operator = $operator;
  66      }
  67  
  68      protected function operand(string $operand, string $operandValueType = Wizard::VALUE_TYPE_LITERAL): void
  69      {
  70          if (is_string($operand)) {
  71              $operand = $this->validateOperand($operand, $operandValueType);
  72          }
  73  
  74          $this->operand = $operand;
  75          $this->operandValueType = $operandValueType;
  76      }
  77  
  78      protected function wrapValue(string $value): string
  79      {
  80          return '"' . $value . '"';
  81      }
  82  
  83      protected function setExpression(): void
  84      {
  85          $operand = $this->operandValueType === Wizard::VALUE_TYPE_LITERAL
  86              ? $this->wrapValue(str_replace('"', '""', $this->operand))
  87              : $this->cellConditionCheck($this->operand);
  88  
  89          if (
  90              $this->operator === Conditional::OPERATOR_CONTAINSTEXT ||
  91              $this->operator === Conditional::OPERATOR_NOTCONTAINS
  92          ) {
  93              $this->expression = sprintf(self::EXPRESSIONS[$this->operator], $operand, $this->referenceCell);
  94          } else {
  95              $this->expression = sprintf(self::EXPRESSIONS[$this->operator], $this->referenceCell, $operand, $operand);
  96          }
  97      }
  98  
  99      public function getConditional(): Conditional
 100      {
 101          $this->setExpression();
 102  
 103          $conditional = new Conditional();
 104          $conditional->setConditionType(self::OPERATORS[$this->operator]);
 105          $conditional->setOperatorType($this->operator);
 106          $conditional->setText(
 107              $this->operandValueType !== Wizard::VALUE_TYPE_LITERAL
 108                  ? $this->cellConditionCheck($this->operand)
 109                  : $this->operand
 110          );
 111          $conditional->setConditions([$this->expression]);
 112          $conditional->setStyle($this->getStyle());
 113          $conditional->setStopIfTrue($this->getStopIfTrue());
 114  
 115          return $conditional;
 116      }
 117  
 118      public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
 119      {
 120          if (!in_array($conditional->getConditionType(), self::OPERATORS, true)) {
 121              throw new Exception('Conditional is not a Text Value CF Rule conditional');
 122          }
 123  
 124          $wizard = new self($cellRange);
 125          $wizard->operator = (string) array_search($conditional->getConditionType(), self::OPERATORS, true);
 126          $wizard->style = $conditional->getStyle();
 127          $wizard->stopIfTrue = $conditional->getStopIfTrue();
 128  
 129          // Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
 130          $wizard->operandValueType = Wizard::VALUE_TYPE_LITERAL;
 131          $condition = $conditional->getText();
 132          if (is_string($condition) && array_key_exists($condition, Calculation::$excelConstants)) {
 133              $condition = Calculation::$excelConstants[$condition];
 134          } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
 135              $wizard->operandValueType = Wizard::VALUE_TYPE_CELL;
 136              $condition = self::reverseAdjustCellRef($condition, $cellRange);
 137          } elseif (
 138              preg_match('/\(\)/', $condition) ||
 139              preg_match('/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/i', $condition)
 140          ) {
 141              $wizard->operandValueType = Wizard::VALUE_TYPE_FORMULA;
 142          }
 143          $wizard->operand = $condition;
 144  
 145          return $wizard;
 146      }
 147  
 148      /**
 149       * @param string $methodName
 150       * @param mixed[] $arguments
 151       */
 152      public function __call($methodName, $arguments): self
 153      {
 154          if (!isset(self::MAGIC_OPERATIONS[$methodName])) {
 155              throw new Exception('Invalid Operation for Text Value CF Rule Wizard');
 156          }
 157  
 158          $this->operator(self::MAGIC_OPERATIONS[$methodName]);
 159          $this->operand(...$arguments);
 160  
 161          return $this;
 162      }
 163  }