1<?php
2
3/**
4 * Foreach Runtime Methods count(), init(), restore()
5 *
6 * @package    Smarty
7 * @subpackage PluginsInternal
8 * @author     Uwe Tews
9 */
10class Smarty_Internal_Runtime_Foreach
11{
12    /**
13     * Stack of saved variables
14     *
15     * @var array
16     */
17    private $stack = array();
18
19    /**
20     * Init foreach loop
21     *  - save item and key variables, named foreach property data if defined
22     *  - init item and key variables, named foreach property data if required
23     *  - count total if required
24     *
25     * @param \Smarty_Internal_Template $tpl
26     * @param mixed                     $from       values to loop over
27     * @param string                    $item       variable name
28     * @param bool                      $needTotal  flag if we need to count values
29     * @param null|string               $key        variable name
30     * @param null|string               $name       of named foreach
31     * @param array                     $properties of named foreach
32     *
33     * @return mixed $from
34     */
35    public function init(
36        Smarty_Internal_Template $tpl,
37        $from,
38        $item,
39        $needTotal = false,
40        $key = null,
41        $name = null,
42        $properties = array()
43    ) {
44        $needTotal = $needTotal || isset($properties[ 'total' ]);
45        $saveVars = array();
46        $total = null;
47        if (!is_array($from)) {
48            if (is_object($from)) {
49                if ($needTotal) {
50                    $total = $this->count($from);
51                }
52            } else {
53                settype($from, 'array');
54            }
55        }
56        if (!isset($total)) {
57            $total = empty($from) ? 0 : ($needTotal ? count($from) : 1);
58        }
59        if (isset($tpl->tpl_vars[ $item ])) {
60            $saveVars[ 'item' ] = array(
61                $item,
62                $tpl->tpl_vars[ $item ]
63            );
64        }
65        $tpl->tpl_vars[ $item ] = new Smarty_Variable(null, $tpl->isRenderingCache);
66        if ($total === 0) {
67            $from = null;
68        } else {
69            if ($key) {
70                if (isset($tpl->tpl_vars[ $key ])) {
71                    $saveVars[ 'key' ] = array(
72                        $key,
73                        $tpl->tpl_vars[ $key ]
74                    );
75                }
76                $tpl->tpl_vars[ $key ] = new Smarty_Variable(null, $tpl->isRenderingCache);
77            }
78        }
79        if ($needTotal) {
80            $tpl->tpl_vars[ $item ]->total = $total;
81        }
82        if ($name) {
83            $namedVar = "__smarty_foreach_{$name}";
84            if (isset($tpl->tpl_vars[ $namedVar ])) {
85                $saveVars[ 'named' ] = array(
86                    $namedVar,
87                    $tpl->tpl_vars[ $namedVar ]
88                );
89            }
90            $namedProp = array();
91            if (isset($properties[ 'total' ])) {
92                $namedProp[ 'total' ] = $total;
93            }
94            if (isset($properties[ 'iteration' ])) {
95                $namedProp[ 'iteration' ] = 0;
96            }
97            if (isset($properties[ 'index' ])) {
98                $namedProp[ 'index' ] = -1;
99            }
100            if (isset($properties[ 'show' ])) {
101                $namedProp[ 'show' ] = ($total > 0);
102            }
103            $tpl->tpl_vars[ $namedVar ] = new Smarty_Variable($namedProp);
104        }
105        $this->stack[] = $saveVars;
106        return $from;
107    }
108
109    /**
110     * [util function] counts an array, arrayAccess/traversable or PDOStatement object
111     *
112     * @param mixed $value
113     *
114     * @return int   the count for arrays and objects that implement countable, 1 for other objects that don't, and 0
115     *               for empty elements
116     */
117    public function count($value)
118    {
119        if ($value instanceof IteratorAggregate) {
120            // Note: getIterator() returns a Traversable, not an Iterator
121            // thus rewind() and valid() methods may not be present
122            return iterator_count($value->getIterator());
123        } elseif ($value instanceof Iterator) {
124            return $value instanceof Generator ? 1 : iterator_count($value);
125        } elseif ($value instanceof Countable) {
126            return count($value);
127        } elseif ($value instanceof PDOStatement) {
128            return $value->rowCount();
129        } elseif ($value instanceof Traversable) {
130            return iterator_count($value);
131        }
132        return count((array)$value);
133    }
134
135    /**
136     * Restore saved variables
137     *
138     * will be called by {break n} or {continue n} for the required number of levels
139     *
140     * @param \Smarty_Internal_Template $tpl
141     * @param int                       $levels number of levels
142     */
143    public function restore(Smarty_Internal_Template $tpl, $levels = 1)
144    {
145        while ($levels) {
146            $saveVars = array_pop($this->stack);
147            if (!empty($saveVars)) {
148                if (isset($saveVars[ 'item' ])) {
149                    $item = &$saveVars[ 'item' ];
150                    $tpl->tpl_vars[ $item[ 0 ] ]->value = $item[ 1 ]->value;
151                }
152                if (isset($saveVars[ 'key' ])) {
153                    $tpl->tpl_vars[ $saveVars[ 'key' ][ 0 ] ] = $saveVars[ 'key' ][ 1 ];
154                }
155                if (isset($saveVars[ 'named' ])) {
156                    $tpl->tpl_vars[ $saveVars[ 'named' ][ 0 ] ] = $saveVars[ 'named' ][ 1 ];
157                }
158            }
159            $levels--;
160        }
161    }
162}
163