1<?php
2
3/**
4 * Defines common attribute collections that modules reference
5 */
6
7class HTMLPurifier_AttrCollections
8{
9
10    /**
11     * Associative array of attribute collections, indexed by name.
12     * @type array
13     */
14    public $info = array();
15
16    /**
17     * Performs all expansions on internal data for use by other inclusions
18     * It also collects all attribute collection extensions from
19     * modules
20     * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
21     * @param HTMLPurifier_HTMLModule[] $modules Hash array of HTMLPurifier_HTMLModule members
22     */
23    public function __construct($attr_types, $modules)
24    {
25        $this->doConstruct($attr_types, $modules);
26    }
27
28    public function doConstruct($attr_types, $modules)
29    {
30        // load extensions from the modules
31        foreach ($modules as $module) {
32            foreach ($module->attr_collections as $coll_i => $coll) {
33                if (!isset($this->info[$coll_i])) {
34                    $this->info[$coll_i] = array();
35                }
36                foreach ($coll as $attr_i => $attr) {
37                    if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) {
38                        // merge in includes
39                        $this->info[$coll_i][$attr_i] = array_merge(
40                            $this->info[$coll_i][$attr_i],
41                            $attr
42                        );
43                        continue;
44                    }
45                    $this->info[$coll_i][$attr_i] = $attr;
46                }
47            }
48        }
49        // perform internal expansions and inclusions
50        foreach ($this->info as $name => $attr) {
51            // merge attribute collections that include others
52            $this->performInclusions($this->info[$name]);
53            // replace string identifiers with actual attribute objects
54            $this->expandIdentifiers($this->info[$name], $attr_types);
55        }
56    }
57
58    /**
59     * Takes a reference to an attribute associative array and performs
60     * all inclusions specified by the zero index.
61     * @param array &$attr Reference to attribute array
62     */
63    public function performInclusions(&$attr)
64    {
65        if (!isset($attr[0])) {
66            return;
67        }
68        $merge = $attr[0];
69        $seen  = array(); // recursion guard
70        // loop through all the inclusions
71        for ($i = 0; isset($merge[$i]); $i++) {
72            if (isset($seen[$merge[$i]])) {
73                continue;
74            }
75            $seen[$merge[$i]] = true;
76            // foreach attribute of the inclusion, copy it over
77            if (!isset($this->info[$merge[$i]])) {
78                continue;
79            }
80            foreach ($this->info[$merge[$i]] as $key => $value) {
81                if (isset($attr[$key])) {
82                    continue;
83                } // also catches more inclusions
84                $attr[$key] = $value;
85            }
86            if (isset($this->info[$merge[$i]][0])) {
87                // recursion
88                $merge = array_merge($merge, $this->info[$merge[$i]][0]);
89            }
90        }
91        unset($attr[0]);
92    }
93
94    /**
95     * Expands all string identifiers in an attribute array by replacing
96     * them with the appropriate values inside HTMLPurifier_AttrTypes
97     * @param array &$attr Reference to attribute array
98     * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance
99     */
100    public function expandIdentifiers(&$attr, $attr_types)
101    {
102        // because foreach will process new elements we add, make sure we
103        // skip duplicates
104        $processed = array();
105
106        foreach ($attr as $def_i => $def) {
107            // skip inclusions
108            if ($def_i === 0) {
109                continue;
110            }
111
112            if (isset($processed[$def_i])) {
113                continue;
114            }
115
116            // determine whether or not attribute is required
117            if ($required = (strpos($def_i, '*') !== false)) {
118                // rename the definition
119                unset($attr[$def_i]);
120                $def_i = trim($def_i, '*');
121                $attr[$def_i] = $def;
122            }
123
124            $processed[$def_i] = true;
125
126            // if we've already got a literal object, move on
127            if (is_object($def)) {
128                // preserve previous required
129                $attr[$def_i]->required = ($required || $attr[$def_i]->required);
130                continue;
131            }
132
133            if ($def === false) {
134                unset($attr[$def_i]);
135                continue;
136            }
137
138            if ($t = $attr_types->get($def)) {
139                $attr[$def_i] = $t;
140                $attr[$def_i]->required = $required;
141            } else {
142                unset($attr[$def_i]);
143            }
144        }
145    }
146}
147
148// vim: et sw=4 sts=4
149