Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.21% covered (success)
91.21%
166 / 182
71.43% covered (warning)
71.43%
10 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
context
91.21% covered (success)
91.21%
166 / 182
71.43% covered (warning)
71.43%
10 / 14
94.38
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 clear
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 assign_var
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 append_var
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 retrieve_var
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 get_data_ref
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 set_num_rows
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 get_root_ref
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 assign_block_vars
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
9
 assign_block_vars_array
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 retrieve_block_vars
92.31% covered (success)
92.31%
24 / 26
0.00% covered (danger)
0.00%
0 / 1
13.08
 find_key_index
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
16
 alter_block_array
91.55% covered (success)
91.55%
65 / 71
0.00% covered (danger)
0.00%
0 / 1
28.47
 destroy_block_vars
36.36% covered (danger)
36.36%
4 / 11
0.00% covered (danger)
0.00%
0 / 1
5.32
1<?php
2/**
3*
4* This file is part of the phpBB Forum Software package.
5*
6* @copyright (c) phpBB Limited <https://www.phpbb.com>
7* @license GNU General Public License, version 2 (GPL-2.0)
8*
9* For full copyright and license information, please see
10* the docs/CREDITS.txt file.
11*
12*/
13
14namespace phpbb\template;
15
16/**
17* Stores variables assigned to template.
18*/
19class context
20{
21    /**
22    * variable that holds all the data we'll be substituting into
23    * the compiled templates. Takes form:
24    * --> $this->tpldata[block][iteration#][child][iteration#][child2][iteration#][variablename] == value
25    * if it's a root-level variable, it'll be like this:
26    * --> $this->tpldata[.][0][varname] == value
27    *
28    * @var array
29    */
30    private $tpldata = array('.' => array(0 => array()));
31
32    /**
33    * @var array Reference to template->tpldata['.'][0]
34    */
35    private $rootref;
36
37    /**
38    * @var bool
39    */
40    private $num_rows_is_set;
41
42    public function __construct()
43    {
44        $this->clear();
45    }
46
47    /**
48    * Clears template data set.
49    */
50    public function clear()
51    {
52        $this->tpldata = array('.' => array(0 => array()));
53        $this->rootref = &$this->tpldata['.'][0];
54        $this->num_rows_is_set = false;
55    }
56
57    /**
58    * Assign a single scalar value to a single key.
59    *
60    * Value can be a string, an integer or a boolean.
61    *
62    * @param string $varname Variable name
63    * @param mixed $varval Value to assign to variable
64    * @return true
65    */
66    public function assign_var(string $varname, mixed $varval)
67    {
68        $this->rootref[$varname] = $varval;
69
70        return true;
71    }
72
73    /**
74    * Append text to the string value stored in a key.
75    *
76    * Text is appended using the string concatenation operator (.).
77    *
78    * @param string $varname Variable name
79    * @param string $varval Value to append to variable
80    * @return true
81    */
82    public function append_var($varname, $varval)
83    {
84        $this->rootref[$varname] = (isset($this->rootref[$varname]) ? $this->rootref[$varname] : '') . $varval;
85
86        return true;
87    }
88
89    /**
90    * Retrieve a single scalar value from a single key.
91    *
92    * @param string $varname Variable name
93    * @return mixed Variable value, or null if not set
94    */
95    public function retrieve_var($varname)
96    {
97        return isset($this->rootref[$varname]) ? $this->rootref[$varname] : null;
98    }
99
100    /**
101    * Returns a reference to template data array.
102    *
103    * This function is public so that template renderer may invoke it.
104    * Users should alter template variables via functions in \phpbb\template\template.
105    *
106    * Note: modifying returned array will affect data stored in the context.
107    *
108    * @return array template data
109    */
110    public function &get_data_ref()
111    {
112        // returning a reference directly is not
113        // something php is capable of doing
114        $ref = &$this->tpldata;
115
116        if (!$this->num_rows_is_set)
117        {
118            /*
119            * We do not set S_NUM_ROWS while adding a row, to reduce the complexity
120            * If we would set it on adding, each subsequent adding would cause
121            * n modifications, resulting in a O(n!) complexity, rather then O(n)
122            */
123            foreach ($ref as $loop_name => &$loop_data)
124            {
125                if ($loop_name === '.')
126                {
127                    continue;
128                }
129
130                $this->set_num_rows($loop_data);
131            }
132            $this->num_rows_is_set = true;
133        }
134
135        return $ref;
136    }
137
138    /**
139    * Set S_NUM_ROWS for each row in this template block
140    *
141    * @param array $loop_data
142    */
143    protected function set_num_rows(&$loop_data)
144    {
145        $s_num_rows = count($loop_data);
146        foreach ($loop_data as &$mod_block)
147        {
148            foreach ($mod_block as $sub_block_name => &$sub_block)
149            {
150                // If the key name is lowercase and the data is an array,
151                // it could be a template loop. So we set the S_NUM_ROWS there
152                // as well.
153                if ($sub_block_name === strtolower($sub_block_name) && is_array($sub_block))
154                {
155                    $this->set_num_rows($sub_block);
156                }
157            }
158
159            // Check whether we are inside a block before setting the variable
160            if (isset($mod_block['S_BLOCK_NAME']))
161            {
162                $mod_block['S_NUM_ROWS'] = $s_num_rows;
163            }
164        }
165    }
166
167    /**
168    * Returns a reference to template root scope.
169    *
170    * This function is public so that template renderer may invoke it.
171    * Users should not need to invoke this function.
172    *
173    * Note: modifying returned array will affect data stored in the context.
174    *
175    * @return array template data
176    */
177    public function &get_root_ref()
178    {
179        // rootref is already a reference
180        return $this->rootref;
181    }
182
183    /**
184    * Assign key variable pairs from an array to a specified block
185    *
186    * @param string $blockname Name of block to assign $vararray to
187    * @param array $vararray A hash of variable name => value pairs
188    * @return true
189    */
190    public function assign_block_vars($blockname, array $vararray)
191    {
192        $this->num_rows_is_set = false;
193
194        // For nested block, $blockcount > 0, for top-level block, $blockcount == 0
195        $blocks = explode('.', $blockname);
196        $blockcount = count($blocks) - 1;
197
198        $block = &$this->tpldata;
199        for ($i = 0; $i < $blockcount; $i++)
200        {
201            $pos = strpos($blocks[$i], '[');
202            $name = ($pos !== false) ? substr($blocks[$i], 0, $pos) : $blocks[$i];
203            $block = &$block[$name];
204            $block_count = empty($block) ? 0 : count($block) - 1;
205            $index = (!$pos || strpos($blocks[$i], '[]') === $pos) ? $block_count : (min((int) substr($blocks[$i], $pos + 1, -1), $block_count));
206            $block = &$block[$index];
207        }
208
209        // $block = &$block[$blocks[$i]]; // Do not traverse the last block as it might be empty
210        $name = $blocks[$i];
211
212        // Assign S_ROW_COUNT and S_ROW_NUM
213        $s_row_count = isset($block[$name]) ? count($block[$name]) : 0;
214        $vararray['S_ROW_COUNT'] = $vararray['S_ROW_NUM'] = $s_row_count;
215
216        // Assign S_FIRST_ROW
217        if (!$s_row_count)
218        {
219            $vararray['S_FIRST_ROW'] = true;
220        }
221
222        // Assign S_BLOCK_NAME
223        $vararray['S_BLOCK_NAME'] = $name;
224
225        // Now the tricky part, we always assign S_LAST_ROW and remove the entry before
226        // This is much more clever than going through the complete template data on display (phew)
227        $vararray['S_LAST_ROW'] = true;
228        if ($s_row_count > 0)
229        {
230            unset($block[$name][($s_row_count - 1)]['S_LAST_ROW']);
231        }
232
233        // Now we add the block that we're actually assigning to.
234        // We're adding a new iteration to this block with the given
235        // variable assignments.
236        $block[$name][] = $vararray;
237
238        return true;
239    }
240
241    /**
242    * Assign key variable pairs from an array to a whole specified block loop
243    *
244    * @param string $blockname Name of block to assign $block_vars_array to
245    * @param array $block_vars_array An array of hashes of variable name => value pairs
246    * @return true
247    */
248    public function assign_block_vars_array($blockname, array $block_vars_array)
249    {
250        foreach ($block_vars_array as $vararray)
251        {
252            $this->assign_block_vars($blockname, $vararray);
253        }
254
255        return true;
256    }
257
258    /**
259    * Retrieve key variable pairs from the specified block
260    *
261    * @param string $blockname Name of block to retrieve $vararray from
262    * @param array $vararray An array of variable names, empty array retrieves all vars
263    * @return array of hashes with variable name as key and retrieved value or null as value
264    */
265    public function retrieve_block_vars($blockname, array $vararray)
266    {
267        // For nested block, $blockcount > 0, for top-level block, $blockcount == 0
268        $blocks = explode('.', $blockname);
269        $blockcount = count($blocks) - 1;
270
271        $block = $this->tpldata;
272        for ($i = 0; $i <= $blockcount; $i++)
273        {
274            if (($pos = strpos($blocks[$i], '[')) !== false)
275            {
276                $name = substr($blocks[$i], 0, $pos);
277
278                if (empty($block[$name]))
279                {
280                    return array();
281                }
282
283                if (strpos($blocks[$i], '[]') === $pos)
284                {
285                    $index = count($block[$name]) - 1;
286                }
287                else
288                {
289                    $index = min((int) substr($blocks[$i], $pos + 1, -1), count($block[$name]) - 1);
290                }
291            }
292            else
293            {
294                $name = $blocks[$i];
295                if (empty($block[$name]))
296                {
297                    return array();
298                }
299
300                $index = count($block[$name]) - 1;
301            }
302            $block = $block[$name];
303            $block = $block[$index];
304        }
305
306        $result = array();
307        if ($vararray === array())
308        {
309            // The calculated vars that depend on the block position are excluded from the complete block returned results
310            $excluded_vars = array('S_FIRST_ROW', 'S_LAST_ROW', 'S_BLOCK_NAME', 'S_NUM_ROWS', 'S_ROW_COUNT', 'S_ROW_NUM');
311
312            foreach ($block as $varname => $varvalue)
313            {
314                if ($varname === strtoupper($varname) && !is_array($varvalue) && !in_array($varname, $excluded_vars))
315                {
316                    $result[$varname] = $varvalue;
317                }
318            }
319        }
320        else
321        {
322            foreach ($vararray as $varname)
323            {
324                $result[$varname] = isset($block[$varname]) ? $block[$varname] : null;
325            }
326        }
327        return $result;
328    }
329
330    /**
331    * Find the index for a specified key in the innermost specified block
332    *
333    * @param    string    $blockname    the blockname, for example 'loop'
334    * @param    mixed    $key        Key to search for
335    *
336    * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position]
337    *
338    * int: Position [the position to search for]
339    *
340    * If key is false the position is set to 0
341    * If key is true the position is set to the last entry
342    *
343    * @return false|int false if not found, index position otherwise; be sure to test with ===
344    */
345    public function find_key_index($blockname, $key)
346    {
347        // For nested block, $blockcount > 0, for top-level block, $blockcount == 0
348        $blocks = explode('.', $blockname);
349        $blockcount = count($blocks) - 1;
350
351        $block = $this->tpldata;
352        for ($i = 0; $i < $blockcount; $i++)
353        {
354            $pos = strpos($blocks[$i], '[');
355            $name = ($pos !== false) ? substr($blocks[$i], 0, $pos) : $blocks[$i];
356
357            if (!isset($block[$name]))
358            {
359                return false;
360            }
361
362            $index = (!$pos || strpos($blocks[$i], '[]') === $pos) ? (count($block[$name]) - 1) : (min((int) substr($blocks[$i], $pos + 1, -1), count($block[$name]) - 1));
363
364            if (!isset($block[$name][$index]))
365            {
366                return false;
367            }
368            $block = $block[$name][$index];
369        }
370
371        if (!isset($block[$blocks[$i]]))
372        {
373            return false;
374        }
375        $block = $block[$blocks[$i]]; // Traverse the last block
376
377        // Change key to zero (change first position) if false and to last position if true
378        if (is_bool($key))
379        {
380            return (!$key) ? 0 : (count($block) ?? 0) - 1;
381        }
382
383        // Get correct position if array given
384        if (is_array($key))
385        {
386            // Search array to get correct position
387            $search_key = key($key);
388            $search_value = current($key);
389
390            foreach ($block as $i => $val_ary)
391            {
392                if ($val_ary[$search_key] === $search_value)
393                {
394                    return $i;
395                }
396            }
397        }
398
399        return (is_int($key) && ((0 <= $key) && ($key < count($block)))) ? $key : false;
400    }
401
402    /**
403    * Change already assigned key variable pair (one-dimensional - single loop entry)
404    *
405    * An example of how to use this function:
406    * {@example alter_block_array.php}
407    *
408    * @param    string    $blockname    the blockname, for example 'loop'
409    * @param    array    $vararray    the var array to insert/add or merge
410    * @param    mixed    $key        Key to search for
411    *
412    * array: KEY => VALUE [the key/value pair to search for within the loop to determine the correct position]
413    *
414    * int: Position [the position to change or insert at directly given]
415    *
416    * If key is false the position is set to 0
417    * If key is true the position is set to the last entry
418    *
419    * @param    string    $mode        Mode to execute (valid modes are 'insert', 'change' and 'delete')
420    *
421    *    If insert, the vararray is inserted at the given position (position counting from zero).
422    *    If change, the current block gets merged with the vararray (resulting in new key/value pairs be added and existing keys be replaced by the new \value).
423    *    If delete, the vararray is ignored, and the block at the given position (counting from zero) is removed.
424    *
425    * Since counting begins by zero, inserting at the last position will result in this array: array(vararray, last positioned array)
426    * and inserting at position 1 will result in this array: array(first positioned array, vararray, following vars)
427    *
428    * @return bool false on error, true on success
429    */
430    public function alter_block_array($blockname, array $vararray, $key = false, $mode = 'insert')
431    {
432        $this->num_rows_is_set = false;
433
434        // For nested block, $blockcount > 0, for top-level block, $blockcount == 0
435        $blocks = explode('.', $blockname);
436        $blockcount = count($blocks) - 1;
437
438        $block = &$this->tpldata;
439        for ($i = 0; $i < $blockcount; $i++)
440        {
441            if (($pos = strpos($blocks[$i], '[')) !== false)
442            {
443                $name = substr($blocks[$i], 0, $pos);
444
445                if (strpos($blocks[$i], '[]') === $pos)
446                {
447                    $index = count($block[$name]) - 1;
448                }
449                else
450                {
451                    $index = min((int) substr($blocks[$i], $pos + 1, -1), count($block[$name]) - 1);
452                }
453            }
454            else
455            {
456                $name = $blocks[$i];
457                $index = count($block[$name]) - 1;
458            }
459            $block = &$block[$name];
460            $block = &$block[$index];
461        }
462        $name = $blocks[$i];
463
464        // If last block does not exist and we are inserting, and not searching for key, we create it empty; otherwise, nothing to do
465        if (!isset($block[$name]))
466        {
467            if ($mode != 'insert' || is_array($key))
468            {
469                return false;
470            }
471            $block[$name] = array();
472        }
473
474        $block = &$block[$name]; // Now we can traverse the last block
475
476        // Change key to zero (change first position) if false and to last position if true
477        if ($key === false || $key === true)
478        {
479            $key = ($key === false) ? 0 : count($block);
480        }
481
482        // Get correct position if array given
483        if (is_array($key))
484        {
485            // Search array to get correct position
486            $search_key = key($key);
487            $search_value = current($key);
488
489            $key = null;
490            foreach ($block as $i => $val_ary)
491            {
492                if ($val_ary[$search_key] === $search_value)
493                {
494                    $key = $i;
495                    break;
496                }
497            }
498
499            // key/value pair not found
500            if ($key === null)
501            {
502                return false;
503            }
504        }
505
506        // Insert Block
507        if ($mode == 'insert')
508        {
509            // Make sure we are not exceeding the last iteration
510            if ($key >= count($block))
511            {
512                $key = count($block);
513                unset($block[($key - 1)]['S_LAST_ROW']);
514                $vararray['S_LAST_ROW'] = true;
515            }
516            if ($key <= 0)
517            {
518                $key = 0;
519                unset($block[0]['S_FIRST_ROW']);
520                $vararray['S_FIRST_ROW'] = true;
521            }
522
523            // Assign S_BLOCK_NAME
524            $vararray['S_BLOCK_NAME'] = $name;
525
526            // Re-position template blocks
527            for ($i = count($block); $i > $key; $i--)
528            {
529                $block[$i] = $block[$i-1];
530
531                $block[$i]['S_ROW_COUNT'] = $block[$i]['S_ROW_NUM'] = $i;
532            }
533
534            // Insert vararray at given position
535            $block[$key] = $vararray;
536            $block[$key]['S_ROW_COUNT'] = $block[$key]['S_ROW_NUM'] = $key;
537
538            return true;
539        }
540
541        // Which block to change?
542        if ($mode == 'change')
543        {
544            // If key is out of bounds, do not change anything
545            if ($key > count($block) || $key < 0)
546            {
547                return false;
548            }
549
550            if ($key == count($block))
551            {
552                $key--;
553            }
554
555            $block[$key] = array_merge($block[$key], $vararray);
556
557            return true;
558        }
559
560        // Delete Block
561        if ($mode == 'delete')
562        {
563            // If we are exceeding last iteration, do not delete anything
564            if ($key > count($block) || $key < 0)
565            {
566                return false;
567            }
568
569            // If we are positioned at the end, we remove the last element
570            if ($key == count($block))
571            {
572                $key--;
573            }
574
575            // We are deleting the last element in the block, so remove the block
576            if (count($block) === 1)
577            {
578                $block = null; // unset($block); does not work on references
579                return true;
580            }
581
582            // Re-position template blocks
583            for ($i = $key; $i < count($block)-1; $i++)
584            {
585                $block[$i] = $block[$i+1];
586                $block[$i]['S_ROW_COUNT'] = $block[$i]['S_ROW_NUM'] = $i;
587            }
588
589            // Remove the last element
590            unset($block[$i]);
591
592            // Set first and last elements again, in case they were removed
593            $block[0]['S_FIRST_ROW'] = true;
594            $block[count($block)-1]['S_LAST_ROW'] = true;
595
596            return true;
597        }
598
599        return false;
600    }
601
602    /**
603    * Reset/empty complete block
604    *
605    * @param string $blockname Name of block to destroy
606    * @return true
607    */
608    public function destroy_block_vars($blockname)
609    {
610        $this->num_rows_is_set = false;
611        if (strpos($blockname, '.') !== false)
612        {
613            // Nested block.
614            $blocks = explode('.', $blockname);
615            $blockcount = count($blocks) - 1;
616
617            $str = &$this->tpldata;
618            for ($i = 0; $i < $blockcount; $i++)
619            {
620                $str = &$str[$blocks[$i]];
621                $str = &$str[count($str) - 1];
622            }
623
624            unset($str[$blocks[$blockcount]]);
625        }
626        else
627        {
628            // Top-level block.
629            unset($this->tpldata[$blockname]);
630        }
631
632        return true;
633    }
634}