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.
   1  <?php
   2  
   3  /*
   4   * This file is part of Mustache.php.
   5   *
   6   * (c) 2010-2017 Justin Hileman
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  /**
  13   * Mustache Template rendering Context.
  14   */
  15  class Mustache_Context
  16  {
  17      private $stack      = array();
  18      private $blockStack = array();
  19  
  20      /**
  21       * Mustache rendering Context constructor.
  22       *
  23       * @param mixed $context Default rendering context (default: null)
  24       */
  25      public function __construct($context = null)
  26      {
  27          if ($context !== null) {
  28              $this->stack = array($context);
  29          }
  30      }
  31  
  32      /**
  33       * Push a new Context frame onto the stack.
  34       *
  35       * @param mixed $value Object or array to use for context
  36       */
  37      public function push($value)
  38      {
  39          array_push($this->stack, $value);
  40      }
  41  
  42      /**
  43       * Push a new Context frame onto the block context stack.
  44       *
  45       * @param mixed $value Object or array to use for block context
  46       */
  47      public function pushBlockContext($value)
  48      {
  49          array_push($this->blockStack, $value);
  50      }
  51  
  52      /**
  53       * Pop the last Context frame from the stack.
  54       *
  55       * @return mixed Last Context frame (object or array)
  56       */
  57      public function pop()
  58      {
  59          return array_pop($this->stack);
  60      }
  61  
  62      /**
  63       * Pop the last block Context frame from the stack.
  64       *
  65       * @return mixed Last block Context frame (object or array)
  66       */
  67      public function popBlockContext()
  68      {
  69          return array_pop($this->blockStack);
  70      }
  71  
  72      /**
  73       * Get the last Context frame.
  74       *
  75       * @return mixed Last Context frame (object or array)
  76       */
  77      public function last()
  78      {
  79          return end($this->stack);
  80      }
  81  
  82      /**
  83       * Find a variable in the Context stack.
  84       *
  85       * Starting with the last Context frame (the context of the innermost section), and working back to the top-level
  86       * rendering context, look for a variable with the given name:
  87       *
  88       *  * If the Context frame is an associative array which contains the key $id, returns the value of that element.
  89       *  * If the Context frame is an object, this will check first for a public method, then a public property named
  90       *    $id. Failing both of these, it will try `__isset` and `__get` magic methods.
  91       *  * If a value named $id is not found in any Context frame, returns an empty string.
  92       *
  93       * @param string $id Variable name
  94       *
  95       * @return mixed Variable value, or '' if not found
  96       */
  97      public function find($id)
  98      {
  99          return $this->findVariableInStack($id, $this->stack);
 100      }
 101  
 102      /**
 103       * Find a 'dot notation' variable in the Context stack.
 104       *
 105       * Note that dot notation traversal bubbles through scope differently than the regular find method. After finding
 106       * the initial chunk of the dotted name, each subsequent chunk is searched for only within the value of the previous
 107       * result. For example, given the following context stack:
 108       *
 109       *     $data = array(
 110       *         'name' => 'Fred',
 111       *         'child' => array(
 112       *             'name' => 'Bob'
 113       *         ),
 114       *     );
 115       *
 116       * ... and the Mustache following template:
 117       *
 118       *     {{ child.name }}
 119       *
 120       * ... the `name` value is only searched for within the `child` value of the global Context, not within parent
 121       * Context frames.
 122       *
 123       * @param string $id Dotted variable selector
 124       *
 125       * @return mixed Variable value, or '' if not found
 126       */
 127      public function findDot($id)
 128      {
 129          $chunks = explode('.', $id);
 130          $first  = array_shift($chunks);
 131          $value  = $this->findVariableInStack($first, $this->stack);
 132  
 133          foreach ($chunks as $chunk) {
 134              if ($value === '') {
 135                  return $value;
 136              }
 137  
 138              $value = $this->findVariableInStack($chunk, array($value));
 139          }
 140  
 141          return $value;
 142      }
 143  
 144      /**
 145       * Find an 'anchored dot notation' variable in the Context stack.
 146       *
 147       * This is the same as findDot(), except it looks in the top of the context
 148       * stack for the first value, rather than searching the whole context stack
 149       * and starting from there.
 150       *
 151       * @see Mustache_Context::findDot
 152       *
 153       * @throws Mustache_Exception_InvalidArgumentException if given an invalid anchored dot $id
 154       *
 155       * @param string $id Dotted variable selector
 156       *
 157       * @return mixed Variable value, or '' if not found
 158       */
 159      public function findAnchoredDot($id)
 160      {
 161          $chunks = explode('.', $id);
 162          $first  = array_shift($chunks);
 163          if ($first !== '') {
 164              throw new Mustache_Exception_InvalidArgumentException(sprintf('Unexpected id for findAnchoredDot: %s', $id));
 165          }
 166  
 167          $value  = $this->last();
 168  
 169          foreach ($chunks as $chunk) {
 170              if ($value === '') {
 171                  return $value;
 172              }
 173  
 174              $value = $this->findVariableInStack($chunk, array($value));
 175          }
 176  
 177          return $value;
 178      }
 179  
 180      /**
 181       * Find an argument in the block context stack.
 182       *
 183       * @param string $id
 184       *
 185       * @return mixed Variable value, or '' if not found
 186       */
 187      public function findInBlock($id)
 188      {
 189          foreach ($this->blockStack as $context) {
 190              if (array_key_exists($id, $context)) {
 191                  return $context[$id];
 192              }
 193          }
 194  
 195          return '';
 196      }
 197  
 198      /**
 199       * Helper function to find a variable in the Context stack.
 200       *
 201       * @see Mustache_Context::find
 202       *
 203       * @param string $id    Variable name
 204       * @param array  $stack Context stack
 205       *
 206       * @return mixed Variable value, or '' if not found
 207       */
 208      private function findVariableInStack($id, array $stack)
 209      {
 210          for ($i = count($stack) - 1; $i >= 0; $i--) {
 211              $frame = &$stack[$i];
 212  
 213              switch (gettype($frame)) {
 214                  case 'object':
 215                      if (!($frame instanceof Closure)) {
 216                          // Note that is_callable() *will not work here*
 217                          // See https://github.com/bobthecow/mustache.php/wiki/Magic-Methods
 218                          if (method_exists($frame, $id)) {
 219                              return $frame->$id();
 220                          }
 221  
 222                          if (isset($frame->$id)) {
 223                              return $frame->$id;
 224                          }
 225  
 226                          if ($frame instanceof ArrayAccess && isset($frame[$id])) {
 227                              return $frame[$id];
 228                          }
 229                      }
 230                      break;
 231  
 232                  case 'array':
 233                      if (array_key_exists($id, $frame)) {
 234                          return $frame[$id];
 235                      }
 236                      break;
 237              }
 238          }
 239  
 240          return '';
 241      }
 242  }