1 <?php
  2 
  3 /**
  4  * @package redaxo\core
  5  */
  6 class rex_select
  7 {
  8     private $attributes = [];
  9     private $currentOptgroup = 0;
 10     private $optgroups = [];
 11     private $options = [];
 12     private $option_selected;
 13     private $optCount = 0;
 14 
 15     public function __construct()
 16     {
 17         $this->init();
 18     }
 19 
 20     public function init()
 21     {
 22         $this->resetSelected();
 23         $this->setName('standard');
 24         $this->setSize('1');
 25         $this->setMultiple(false);
 26         $this->setDisabled(false);
 27     }
 28 
 29     public function setAttributes($attributes)
 30     {
 31         $this->attributes = array_merge($this->attributes, $attributes);
 32     }
 33 
 34     public function setAttribute($name, $value)
 35     {
 36         $this->attributes[$name] = $value;
 37     }
 38 
 39     public function delAttribute($name)
 40     {
 41         if ($this->hasAttribute($name)) {
 42             unset($this->attributes[$name]);
 43             return true;
 44         }
 45         return false;
 46     }
 47 
 48     public function hasAttribute($name)
 49     {
 50         return isset($this->attributes[$name]);
 51     }
 52 
 53     public function getAttribute($name, $default = '')
 54     {
 55         if ($this->hasAttribute($name)) {
 56             return $this->attributes[$name];
 57         }
 58         return $default;
 59     }
 60 
 61     public function setMultiple($multiple = true)
 62     {
 63         if ($multiple) {
 64             $this->setAttribute('multiple', 'multiple');
 65             if ($this->getAttribute('size') == '1') {
 66                 $this->setSize('5');
 67             }
 68         } else {
 69             $this->delAttribute('multiple');
 70         }
 71     }
 72 
 73     public function setDisabled($disabled = true)
 74     {
 75         if ($disabled) {
 76             $this->setAttribute('disabled', 'disabled');
 77         } else {
 78             $this->delAttribute('disabled');
 79         }
 80     }
 81 
 82     public function setName($name)
 83     {
 84         $this->setAttribute('name', $name);
 85     }
 86 
 87     public function setId($id)
 88     {
 89         $this->setAttribute('id', $id);
 90     }
 91 
 92     /**
 93      * select style
 94      * Es ist moeglich sowohl eine Styleklasse als auch einen Style zu uebergeben.
 95      *
 96      * Aufrufbeispiel:
 97      * $sel_media->setStyle('class="inp100"');
 98      * und/oder
 99      * $sel_media->setStyle("width:150px;");
100      */
101     public function setStyle($style)
102     {
103         if (strpos($style, 'class=') !== false) {
104             if (preg_match('/class=["\']?([^"\']*)["\']?/i', $style, $matches)) {
105                 $this->setAttribute('class', $matches[1]);
106             }
107         } else {
108             $this->setAttribute('style', $style);
109         }
110     }
111 
112     public function setSize($size)
113     {
114         $this->setAttribute('size', $size);
115     }
116 
117     public function setSelected($selected)
118     {
119         if (is_array($selected)) {
120             foreach ($selected as $sectvalue) {
121                 $this->setSelected($sectvalue);
122             }
123         } else {
124             $this->option_selected[] = (string) rex_escape($selected);
125         }
126     }
127 
128     public function resetSelected()
129     {
130         $this->option_selected = [];
131     }
132 
133     public function addOptgroup($label)
134     {
135         ++$this->currentOptgroup;
136         $this->optgroups[$this->currentOptgroup] = $label;
137     }
138 
139     /**
140      * Fügt eine Option hinzu.
141      */
142     public function addOption($name, $value, $id = 0, $parent_id = 0, array $attributes = [])
143     {
144         $this->options[$this->currentOptgroup][$parent_id][] = [$name, $value, $id, $attributes];
145         ++$this->optCount;
146     }
147 
148     /**
149      * Fügt ein Array von Optionen hinzu, dass eine mehrdimensionale Struktur hat.
150      *
151      * Dim   Wert
152      * 0.    Name
153      * 1.    Value
154      * 2.    Id
155      * 3.    parent_id
156      * 4.    Selected
157      * 5.    Attributes
158      */
159     public function addOptions($options, $useOnlyValues = false)
160     {
161         if (is_array($options) && count($options) > 0) {
162             // Hier vorher auf is_array abfragen, da bei Strings auch die Syntax mit [] funktioniert
163             // $ab = "hallo"; $ab[2] -> "l"
164             $grouped = isset($options[0]) && is_array($options[0]) && isset($options[0][2]) && isset($options[0][3]);
165             foreach ($options as $key => $option) {
166                 $option = (array) $option;
167                 $attributes = [];
168                 if (isset($option[5]) && is_array($option[5])) {
169                     $attributes = $option[5];
170                 }
171                 if ($grouped) {
172                     $this->addOption($option[0], $option[1], $option[2], $option[3], $attributes);
173                     if (isset($option[4]) && $option[4]) {
174                         $this->setSelected($option[1]);
175                     }
176                 } else {
177                     if ($useOnlyValues) {
178                         $this->addOption($option[0], $option[0]);
179                     } else {
180                         if (!isset($option[1])) {
181                             $option[1] = $key;
182                         }
183 
184                         $this->addOption($option[0], $option[1]);
185                     }
186                 }
187             }
188         }
189     }
190 
191     /**
192      * Fügt ein Array von Optionen hinzu, dass eine Key/Value Struktur hat.
193      * Wenn $use_keys mit false, werden die Array-Keys mit den Array-Values überschrieben.
194      */
195     public function addArrayOptions(array $options, $use_keys = true)
196     {
197         foreach ($options as $key => $value) {
198             if (!$use_keys) {
199                 $key = $value;
200             }
201 
202             $this->addOption($value, $key);
203         }
204     }
205 
206     public function countOptions()
207     {
208         return $this->optCount;
209     }
210 
211     /**
212      * Fügt Optionen anhand der Übergeben SQL-Select-Abfrage hinzu.
213      */
214     public function addSqlOptions($qry)
215     {
216         $sql = rex_sql::factory();
217         $this->addOptions($sql->getArray($qry, [], PDO::FETCH_NUM));
218     }
219 
220     /**
221      * Fügt Optionen anhand der Übergeben DBSQL-Select-Abfrage hinzu.
222      */
223     public function addDBSqlOptions($qry)
224     {
225         $sql = rex_sql::factory();
226         $this->addOptions($sql->getDBArray($qry, [], PDO::FETCH_NUM));
227     }
228 
229     public function get()
230     {
231         $useRexSelectStyle = false;
232 
233         // RexSelectStyle im Backend nutzen
234         if (rex::isBackend()) {
235             $useRexSelectStyle = true;
236         }
237         // RexSelectStyle nicht nutzen, wenn die Klasse `.selectpicker` gesetzt ist
238         if (isset($this->attributes['class']) && strpos($this->attributes['class'], 'selectpicker') !== false) {
239             $useRexSelectStyle = false;
240         }
241         // RexSelectStyle nicht nutzen, wenn das Selectfeld mehrzeilig ist
242         if (isset($this->attributes['size']) && (int) $this->attributes['size'] > 1) {
243             $useRexSelectStyle = false;
244         }
245 
246         $attr = '';
247         foreach ($this->attributes as $name => $value) {
248             $attr .= ' ' . rex_escape($name, 'html_attr') . '="' . rex_escape($value) . '"';
249         }
250 
251         $ausgabe = "\n";
252         if ($useRexSelectStyle) {
253             $ausgabe .= '<div class="rex-select-style">' . "\n";
254         }
255         $ausgabe .= '<select' . $attr . '>' . "\n";
256 
257         foreach ($this->options as $optgroup => $options) {
258             $this->currentOptgroup = $optgroup;
259             if ($optgroupLabel = isset($this->optgroups[$optgroup]) ? $this->optgroups[$optgroup] : null) {
260                 $ausgabe .= '  <optgroup label="' . rex_escape($optgroupLabel) . '">' . "\n";
261             }
262             if (is_array($options)) {
263                 $ausgabe .= $this->outGroup(0);
264             }
265             if ($optgroupLabel) {
266                 $ausgabe .= '  </optgroup>' . "\n";
267             }
268         }
269 
270         $ausgabe .= '</select>' . "\n";
271         if ($useRexSelectStyle) {
272             $ausgabe .= '</div>' . "\n";
273         }
274 
275         return $ausgabe;
276     }
277 
278     public function show()
279     {
280         echo $this->get();
281     }
282 
283     protected function outGroup($parent_id, $level = 0)
284     {
285         if ($level > 100) {
286             // nur mal so zu sicherheit .. man weiss nie ;)
287             throw new rex_exception('rex_select->outGroup overflow');
288         }
289 
290         $ausgabe = '';
291         $group = $this->getGroup($parent_id);
292         if (!is_array($group)) {
293             return '';
294         }
295         foreach ($group as $option) {
296             $name = $option[0];
297             $value = $option[1];
298             $id = $option[2];
299             $attributes = [];
300             if (isset($option[3]) && is_array($option[3])) {
301                 $attributes = $option[3];
302             }
303             $ausgabe .= $this->outOption($name, $value, $level, $attributes);
304 
305             $subgroup = $this->getGroup($id, true);
306             if ($subgroup !== false) {
307                 $ausgabe .= $this->outGroup($id, $level + 1);
308             }
309         }
310         return $ausgabe;
311     }
312 
313     protected function outOption($name, $value, $level = 0, array $attributes = [])
314     {
315         $name = rex_escape($name);
316         // for BC reasons, we always expect value to be a string.
317         // this also makes sure that the strict in_array() check below works.
318         $value = (string) rex_escape($value);
319 
320         $bsps = '';
321         if ($level > 0) {
322             $bsps = str_repeat('&nbsp;&nbsp;&nbsp;', $level);
323         }
324 
325         if ($this->option_selected !== null && in_array($value, $this->option_selected, true)) {
326             $attributes['selected'] = 'selected';
327         }
328 
329         $attr = '';
330         foreach ($attributes as $n => $v) {
331             $attr .= ' ' . rex_escape($n, 'html_attr') . '="' . rex_escape($v) . '"';
332         }
333 
334         return '        <option value="' . $value . '"' . $attr . '>' . $bsps . $name . '</option>' . "\n";
335     }
336 
337     protected function getGroup($parent_id, $ignore_main_group = false)
338     {
339         if ($ignore_main_group && $parent_id == 0) {
340             return false;
341         }
342 
343         if (isset($this->options[$this->currentOptgroup][$parent_id])) {
344             return $this->options[$this->currentOptgroup][$parent_id];
345         }
346 
347         return false;
348     }
349 }
350