1: <?php
2: /*
3: You may not change or alter any portion of this comment or credits
4: of supporting developers from this source code or any supporting source code
5: which is considered copyrighted (c) material of the original comment or credit authors.
6:
7: This program is distributed in the hope that it will be useful,
8: but WITHOUT ANY WARRANTY; without even the implied warranty of
9: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10: */
11:
12:
13: namespace Xoops\Html;
14:
15: use Xoops\Core\AttributeInterface;
16:
17: /**
18: * Attributes - Base class for HTML attributes
19: *
20: * @category Xoops\Html\Attributes
21: * @package Xoops\Html
22: * @author Richard Griffith <richard@geekwright.com>
23: * @copyright 2014 XOOPS Project (http://xoops.org)
24: * @license GNU GPL 2 or later (http://www.gnu.org/licenses/gpl-2.0.html)
25: * @link http://xoops.org
26: */
27: class Attributes extends \ArrayObject implements AttributeInterface
28: {
29: /**
30: * __construct
31: *
32: * @param array $attributes array of attribute name => value pairs
33: */
34: public function __construct($attributes = array())
35: {
36: parent::__construct([]);
37: if (!empty($attributes)) {
38: $this->setAll($attributes);
39: }
40: }
41:
42: /**
43: * add an element attribute value to a multi-value attribute (like class)
44: *
45: * @param string $name name of the attribute
46: * @param string|string[] $value value for the attribute
47: *
48: * @return void
49: */
50: public function add($name, $value)
51: {
52: if (is_scalar($value)) {
53: $value = explode(' ', (string) $value);
54: }
55: $values = $this->get($name, []);
56: if (is_scalar($values)) {
57: $values = (array) $values;
58: }
59: foreach ($value as $v) {
60: if (!in_array($v, $values)) {
61: $values[] = $v;
62: }
63: }
64: $this->offsetSet($name, $values);
65: }
66:
67: /**
68: * @var string[] list of attributes to NOT render
69: */
70: protected $suppressRenderAttributes = [];
71:
72: /**
73: * Add attributes to the render suppression list
74: *
75: * @param string|string[] $names attributes to suppress
76: *
77: * @return void
78: */
79: protected function suppressRender($names)
80: {
81: $names = (array) $names;
82: $this->suppressRenderAttributes = array_unique(
83: array_merge($this->suppressRenderAttributes, $names)
84: );
85: }
86:
87: /**
88: * controls rendering of specific attributes
89: *
90: * Example, some form elements have "attributes" that are not standard html attributes to be
91: * included in the rendered tag, like caption, or the value for a textarea element.
92: *
93: * Also, any attribute starting with a ":" is considered to be a control item, and is not
94: * rendered.
95: *
96: * @param string $name attribute name to check
97: *
98: * @return boolean true if this attribute should be rendered, false otherwise
99: */
100: protected function doRender($name)
101: {
102: if ((':' === substr($name, 0, 1))
103: || (in_array($name, $this->suppressRenderAttributes))) {
104: return false;
105: }
106: return true;
107: }
108:
109: /**
110: * render attributes as a string to include in HTML output
111: *
112: * @return string
113: */
114: public function renderAttributeString()
115: {
116: $rendered = '';
117: foreach ($this as $name => $value) {
118: if (!$this->doRender($name)) {
119: continue;
120: }
121: if ($name === 'name'
122: && $this->has('multiple')
123: && substr($value, -2) !== '[]'
124: ) {
125: $value .= '[]';
126: }
127: if (is_array($value)) {
128: // arrays can be used for class attributes, space separated
129: $set = '="' . htmlspecialchars(implode(' ', $value), ENT_QUOTES) .'"';
130: } elseif ($value===null) {
131: // null indicates attribute minimization (name only,) like autofocus or readonly
132: $set = '';
133: } else {
134: $set = '="' . htmlspecialchars($value, ENT_QUOTES) .'"';
135: }
136: $rendered .= htmlspecialchars($name, ENT_QUOTES) . $set . ' ';
137: }
138: return $rendered;
139: }
140:
141: /**
142: * Retrieve an attribute value.
143: *
144: * @param string $name Name of an attribute
145: * @param mixed $default A default value returned if the requested attribute is not set.
146: *
147: * @return mixed The value of the attribute, or $default if not set.
148: */
149: public function get($name, $default = false)
150: {
151: if ($this->offsetExists($name)) {
152: return $this->offsetGet($name);
153: }
154: return $default;
155: }
156:
157: /**
158: * Set an attribute value.
159: *
160: * @param string $name Name of the attribute option
161: * @param mixed $value Value of the attribute option
162: *
163: * @return void
164: */
165: public function set($name, $value=null)
166: {
167: // convert boolean to strings, so getAttribute can return boolean
168: // false for attributes that are not defined
169: $value = ($value===false) ? '0' : $value;
170: $value = ($value===true) ? '1' : $value;
171:
172: $this->offsetSet($name, $value);
173: }
174:
175: /**
176: * Determine if an attribute exists.
177: *
178: * @param string $name An attribute name.
179: *
180: * @return boolean TRUE if the given attribute exists, otherwise FALSE.
181: */
182: public function has($name)
183: {
184: return $this->offsetExists($name);
185: }
186:
187: /**
188: * Remove an attribute.
189: *
190: * @param string $name An attribute name.
191: *
192: * @return mixed An attribute value, if the named attribute existed and
193: * has been removed, otherwise NULL.
194: */
195: public function remove($name)
196: {
197: $value = null;
198: if ($this->offsetExists($name)) {
199: $value = $this->offsetGet($name);
200: $this->offsetUnset($name);
201: }
202:
203: return $value;
204: }
205:
206: /**
207: * Remove all attributes.
208: *
209: * @return array old values
210: */
211: public function clear()
212: {
213: return $this->exchangeArray(array());
214: }
215:
216: // extras
217:
218: /**
219: * Get a copy of all attributes
220: *
221: * @return array An array of attributes
222: */
223: public function getAll()
224: {
225: return $this->getArrayCopy();
226: }
227:
228: /**
229: * Get a list of all attribute names
230: *
231: * @return array An array of attribute names/keys
232: */
233: public function getNames()
234: {
235: return array_keys((array) $this);
236: }
237:
238: /**
239: * Replace all attribute with new set
240: *
241: * @param mixed $values array (or object) of new attributes
242: *
243: * @return array old values
244: */
245: public function setAll($values)
246: {
247: $oldValues = $this->exchangeArray($values);
248: return $oldValues;
249: }
250:
251: /**
252: * Set multiple attributes by using an associative array
253: *
254: * @param array $values array of new attributes
255: *
256: * @return void
257: */
258: public function setMerge($values)
259: {
260: $oldValues = $this->getArrayCopy();
261: $this->exchangeArray(array_merge($oldValues, $values));
262: }
263:
264: /**
265: * Set an element attribute array
266: *
267: * This allows an attribute which is an array to be built one
268: * element at a time.
269: *
270: * @param string $stem An attribute array name.
271: * @param string $name An attribute array item name. If empty, the
272: * value will be appended to the end of the
273: * array rather than added with the key $name.
274: * @param mixed $value An attribute array item value.
275: *
276: * @return void
277: */
278: public function setArrayItem($stem, $name, $value)
279: {
280: $newValue = array();
281: if ($this->offsetExists($stem)) {
282: $newValue = $this->offsetGet($stem);
283: if (!is_array($newValue)) {
284: $newValue = array();
285: }
286: }
287: if ($name === null || $name === '') {
288: $newValue[] = $value;
289: } else {
290: $newValue[$name] = $value;
291: }
292: $this->offsetSet($stem, $newValue);
293: }
294:
295: /**
296: * Retrieve a set of attributes based on a partial name
297: *
298: * @param string|null $nameLike restrict output to only attributes with a name starting with
299: * this string.
300: *
301: * @return array an array of all attributes with names matching $nameLike
302: */
303: public function getAllLike($nameLike = null)
304: {
305: if ($nameLike === null) {
306: return $this->getArrayCopy();
307: }
308:
309: $likeSet = array();
310: foreach ($this as $k => $v) {
311: if (substr($k, 0, strlen($nameLike))==$nameLike) {
312: $likeSet[$k]=$v;
313: }
314: }
315: return $likeSet;
316: }
317: }
318: