1 <?php
  2 
  3 /**
  4  * @package redaxo\core
  5  */
  6 class rex_fragment
  7 {
  8     /**
  9      * filename of the actual fragmentfile.
 10      *
 11      * @var string
 12      */
 13     private $filename;
 14 
 15     /**
 16      * key-value pair which represents all variables defined inside the fragment.
 17      *
 18      * @var array
 19      */
 20     private $vars;
 21 
 22     /**
 23      * another fragment which can optionaly be used to decorate the current fragment.
 24      *
 25      * @var rex_fragment
 26      */
 27     private $decorator;
 28 
 29     /**
 30      * Creates a fragment with the given variables.
 31      *
 32      * @param array $vars A array of key-value pairs to pass as local parameters
 33      */
 34     public function __construct(array $vars = [])
 35     {
 36         $this->vars = $vars;
 37     }
 38 
 39     /**
 40      * Returns the value of the given variable $name.
 41      *
 42      * @param string $name    Variable name
 43      * @param string $default Default value
 44      *
 45      * @return mixed
 46      */
 47     public function getVar($name, $default = null)
 48     {
 49         if (isset($this->vars[$name])) {
 50             return $this->vars[$name];
 51         }
 52 
 53         return $default;
 54     }
 55 
 56     /**
 57      * Set the variable $name to the given value.
 58      *
 59      * @param string $name   The name of the variable
 60      * @param mixed  $value  The value for the variable
 61      * @param bool   $escape Flag which indicates if the value should be escaped or not
 62      *
 63      * @throws InvalidArgumentException
 64      *
 65      * @return $this
 66      */
 67     public function setVar($name, $value, $escape = true)
 68     {
 69         if (null === $name) {
 70             throw new InvalidArgumentException(sprintf('Expecting $name to be not null!'));
 71         }
 72 
 73         if ($escape) {
 74             $this->vars[$name] = $this->escape($value);
 75         } else {
 76             $this->vars[$name] = $value;
 77         }
 78 
 79         return $this;
 80     }
 81 
 82     /**
 83      * Parses the variables of the fragment into the file $filename.
 84      *
 85      * @param string $filename the filename of the fragment to parse
 86      *
 87      * @throws InvalidArgumentException
 88      * @throws rex_exception
 89      *
 90      * @return string
 91      */
 92     public function parse($filename)
 93     {
 94         if (!is_string($filename)) {
 95             throw new InvalidArgumentException(sprintf('Expecting $filename to be a string, %s given!', gettype($filename)));
 96         }
 97 
 98         $this->filename = $filename;
 99 
100         foreach (self::$fragmentDirs as $fragDir) {
101             $fragment = $fragDir . $filename;
102             if (is_readable($fragment)) {
103                 ob_start();
104                 require $fragment;
105                 $content = ob_get_clean();
106 
107                 if ($this->decorator) {
108                     $this->decorator->setVar('rexDecoratedContent', $content, false);
109                     $content = $this->decorator->parse($this->decorator->filename);
110                 }
111 
112                 return $content;
113             }
114         }
115 
116         throw new rex_exception(sprintf('Fragmentfile "%s" not found!', $filename));
117     }
118 
119     /**
120      * Decorate the current fragment, with another fragment.
121      * The decorated fragment receives the parameters which are passed to this method.
122      *
123      * @param string $filename The filename of the fragment used for decoration
124      * @param array  $params   A array of key-value pairs to pass as parameters
125      *
126      * @return $this
127      */
128     public function decorate($filename, array $params)
129     {
130         $this->decorator = new self($params);
131         $this->decorator->filename = $filename;
132 
133         return $this;
134     }
135 
136     // -------------------------- in-fragment helpers
137 
138     /**
139      * Escapes the value $val for proper use in the gui.
140      *
141      * @param mixed  $value    The value to escape
142      * @param string $strategy One of "html", "html_attr", "css", "js", "url"
143      *
144      * @throws InvalidArgumentException
145      *
146      * @return mixed
147      */
148     protected function escape($value, $strategy = 'html')
149     {
150         return rex_escape($value, $strategy);
151     }
152 
153     /**
154      * Include a Subfragment from within a fragment.
155      *
156      * The Subfragment gets all variables of the current fragment, plus optional overrides from $params
157      *
158      * @param string $filename The filename of the fragment to use
159      * @param array  $params   A array of key-value pairs to pass as local parameters
160      */
161     protected function subfragment($filename, array $params = [])
162     {
163         $fragment = new self(array_merge($this->vars, $params));
164         echo $fragment->parse($filename);
165     }
166 
167     /**
168      * Translate the given key $key.
169      *
170      * @param string $key The key to translate
171      *
172      * @throws InvalidArgumentException
173      *
174      * @return string
175      */
176     protected function i18n($key)
177     {
178         if (!is_string($key)) {
179             throw new InvalidArgumentException(sprintf('Expecting $key to be a string, %s given!', gettype($key)));
180         }
181 
182         // use the magic call only when more than one parameter is passed along,
183         // to get best performance
184         $argNum = func_num_args();
185         if ($argNum > 1) {
186             // pass along all given parameters
187             $args = func_get_args();
188             return call_user_func_array(['rex_i18n', 'msg'], $args);
189         }
190 
191         return rex_i18n::msg($key);
192     }
193 
194     /**
195      * Magic getter to reference variables from within the fragment.
196      *
197      * @param string $name The name of the variable to get
198      *
199      * @return mixed
200      */
201     public function __get($name)
202     {
203         if (isset($this->vars[$name]) || array_key_exists($name, $this->vars)) {
204             return $this->vars[$name];
205         }
206 
207         trigger_error(sprintf('Undefined variable "%s" in rex_fragment "%s"', $name, $this->filename), E_USER_WARNING);
208 
209         return null;
210     }
211 
212     /**
213      * Magic method to check if a variable is set.
214      *
215      * @param string $name The name of the variable to check
216      *
217      * @return bool
218      */
219     public function __isset($name)
220     {
221         return isset($this->vars[$name]) || array_key_exists($name, $this->vars);
222     }
223 
224     // /-------------------------- in-fragment helpers
225 
226     /**
227      * array which contains all folders in which fragments will be searched for at runtime.
228      *
229      * @var array
230      */
231     private static $fragmentDirs = [];
232 
233     /**
234      * Add a path to the fragment search path.
235      *
236      * @param string $dir A path to a directory where fragments can be found
237      */
238     public static function addDirectory($dir)
239     {
240         // add the new directory in front of the already know dirs,
241         // so a later caller can override core settings/fragments
242         $dir = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
243         array_unshift(self::$fragmentDirs, $dir);
244     }
245 }
246