Completed
Push — master ( dca322...47e653 )
by Julito
08:54
created

HTML_QuickForm_advmultiselect::toHtml()   F

Complexity

Conditions 31
Paths 4209

Size

Total Lines 355
Code Lines 247

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 31
eloc 247
nc 4209
nop 0
dl 0
loc 355
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Copyright (c) 2005-2009, Laurent Laville <[email protected]>
4
 *
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 *     * Redistributions of source code must retain the above copyright
12
 *       notice, this list of conditions and the following disclaimer.
13
 *     * Redistributions in binary form must reproduce the above copyright
14
 *       notice, this list of conditions and the following disclaimer in the
15
 *       documentation and/or other materials provided with the distribution.
16
 *     * Neither the name of the authors nor the names of its contributors
17
 *       may be used to endorse or promote products derived from this software
18
 *       without specific prior written permission.
19
 *
20
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
24
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30
 * POSSIBILITY OF SUCH DAMAGE.
31
 *
32
 * PHP versions 4 and 5
33
 *
34
 * @category  HTML
35
 * @package   HTML_QuickForm_advmultiselect
36
 * @author    Laurent Laville <[email protected]>
37
 * @copyright 2005-2009 Laurent Laville
38
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD
39
 * @version   CVS: $Id: advmultiselect.php,v 1.36 2009/04/05 07:03:39 farell Exp $
40
 * @link      http://pear.php.net/package/HTML_QuickForm_advmultiselect
41
 * @since     File available since Release 0.4.0
42
 */
43
44
/**
45
 * Basic error codes
46
 *
47
 * @var        integer
48
 * @since      1.5.0
49
 */
50
define('HTML_QUICKFORM_ADVMULTISELECT_ERROR_INVALID_INPUT', 1);
51
52
/**
53
 * @todo clean class to use only with the multiselect.js library
54
 *
55
 * Element for HTML_QuickForm that emulate a multi-select.
56
 *
57
 * The HTML_QuickForm_advmultiselect package adds an element to the
58
 * HTML_QuickForm package that is two select boxes next to each other
59
 * emulating a multi-select.
60
 *
61
 * @category  HTML
62
 * @package   HTML_QuickForm_advmultiselect
63
 * @author    Laurent Laville <[email protected]>
64
 * @copyright 2005-2009 Laurent Laville
65
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD
66
 * @version   Release: @package_version@
67
 * @link      http://pear.php.net/package/HTML_QuickForm_advmultiselect
68
 * @since     Class available since Release 0.4.0
69
 */
70
class HTML_QuickForm_advmultiselect extends HTML_QuickForm_select
71
{
72
    /**
73
     * Associative array of the multi select container attributes
74
     *
75
     * @var        array
76
     * @access     private
77
     * @since      0.4.0
78
     */
79
    public $_tableAttributes;
80
81
    /**
82
     * Associative array of the add button attributes
83
     *
84
     * @var        array
85
     * @access     private
86
     * @since      0.4.0
87
     */
88
    public $_addButtonAttributes;
89
90
    /**
91
     * Associative array of the remove button attributes
92
     *
93
     * @var        array
94
     * @access     private
95
     * @since      0.4.0
96
     */
97
    public $_removeButtonAttributes;
98
99
    /**
100
     * Associative array of the select all button attributes
101
     *
102
     * @var        array
103
     * @access     private
104
     * @since      1.1.0
105
     */
106
    public $_allButtonAttributes;
107
108
    /**
109
     * Associative array of the select none button attributes
110
     *
111
     * @var        array
112
     * @access     private
113
     * @since      1.1.0
114
     */
115
    public $_noneButtonAttributes;
116
117
    /**
118
     * Associative array of the toggle selection button attributes
119
     *
120
     * @var        array
121
     * @access     private
122
     * @since      1.1.0
123
     */
124
    public $_toggleButtonAttributes;
125
126
    /**
127
     * Associative array of the move up button attributes
128
     *
129
     * @var        array
130
     * @access     private
131
     * @since      0.5.0
132
     */
133
    public $_upButtonAttributes;
134
135
    /**
136
     * Associative array of the move up button attributes
137
     *
138
     * @var        array
139
     * @access     private
140
     * @since      0.5.0
141
     */
142
    public $_downButtonAttributes;
143
144
    /**
145
     * Associative array of the move top button attributes
146
     *
147
     * @var        array
148
     * @access     private
149
     * @since      1.5.0
150
     */
151
    public $_topButtonAttributes;
152
153
    /**
154
     * Associative array of the move bottom button attributes
155
     *
156
     * @var        array
157
     * @access     private
158
     * @since      1.5.0
159
     */
160
    public $_bottomButtonAttributes;
161
162
    /**
163
     * Defines if both list (unselected, selected) will have their elements be
164
     * arranged from lowest to highest (or reverse)
165
     * depending on comparaison function.
166
     *
167
     * SORT_ASC  is used to sort in ascending order
168
     * SORT_DESC is used to sort in descending order
169
     *
170
     * @var    string    ('none' == false, 'asc' == SORT_ASC, 'desc' == SORT_DESC)
171
     * @access private
172
     * @since  0.5.0
173
     */
174
    public $_sort;
175
176
    /**
177
     * Associative array of the unselected item box attributes
178
     *
179
     * @var        array
180
     * @access     private
181
     * @since      0.4.0
182
     */
183
    public $_attributesUnselected;
184
185
    /**
186
     * Associative array of the selected item box attributes
187
     *
188
     * @var        array
189
     * @access     private
190
     * @since      0.4.0
191
     */
192
    public $_attributesSelected;
193
194
    /**
195
     * Associative array of the internal hidden box attributes
196
     *
197
     * @var        array
198
     * @access     private
199
     * @since      0.4.0
200
     */
201
    public $_attributesHidden;
202
    public $selectAllCheckBox = false;
203
204
    /**
205
     * Default Element template string
206
     *
207
     * @var        string
208
     * @access     private
209
     * @since      0.4.0
210
     */
211
    public $_elementTemplate;
212
213
    /**
214
     * Default Element stylesheet string
215
     *
216
     * @var        string
217
     * @access     private
218
     * @since      0.4.0
219
     */
220
    public $_elementCSS = '';
221
222
    /**
223
     * Class constructor
224
     *
225
     * @param string  $elementName  Dual Select name attribute
226
     * @param mixed   $elementLabel Label(s) for the select boxes
227
     * @param mixed   $options      Data to be used to populate options
228
     * @param mixed   $attributes   Either a typical HTML attribute string or
229
     *                              an associative array
230
     * @param integer $sort         Either SORT_ASC for auto ascending arrange,
231
     *                                     SORT_DESC for auto descending arrange, or
232
     *                                     NULL for no sort (append at end: default)
233
     *
234
     * @access     public
235
     * @return     void
236
     * @since      version 0.4.0 (2005-06-25)
237
     */
238
    public function __construct(
239
        $elementName = null,
240
        $elementLabel = null,
241
        $options = null,
242
        $attributes = null,
243
        $sort = null
244
    ) {
245
        $opts = $options;
246
        $options = null;  // prevent to use the default select element load options
247
248
        parent::__construct($elementName, $elementLabel, $options, $attributes);
249
        $this->selectAllCheckBox = isset($attributes['select_all_checkbox']) ? $attributes['select_all_checkbox'] : false;
250
251
        // allow to load options at once and take care of fancy attributes
252
        $this->load($opts);
253
254
        // add multiple selection attribute by default if missing
255
        $this->updateAttributes(array('multiple' => 'multiple'));
256
257
        if (is_null($this->getAttribute('size'))) {
258
            // default size is ten item on each select box (left and right)
259
            $this->updateAttributes(array('size' => 10));
260
        }
261
        if (is_null($this->getAttribute('class'))) {
262
            // default width of each select box
263
            $this->updateAttributes(array('class' => 'form-control'));
264
        }
265
266
        $this->removeAttribute('class');
267
        $this->setAttribute('class','form-control');
268
269
        // set default add button attributes
270
        $this->setButtonAttributes('add');
271
        // set default remove button attributes
272
        $this->setButtonAttributes('remove');
273
        // set default selectall button attributes
274
        $this->setButtonAttributes('all');
275
        // set default selectnone button attributes
276
        $this->setButtonAttributes('none');
277
        // set default toggle selection button attributes
278
        $this->setButtonAttributes('toggle');
279
        // set default move up button attributes
280
        $this->setButtonAttributes('moveup');
281
        // set default move up button attributes
282
        $this->setButtonAttributes('movedown');
283
        // set default move top button attributes
284
        $this->setButtonAttributes('movetop');
285
        // set default move bottom button attributes
286
        $this->setButtonAttributes('movebottom');
287
288
        // set select boxes sort order (none by default)
289
        if (!isset($sort)) {
290
            $sort = false;
291
        }
292
        if ($sort === SORT_ASC) {
293
            $this->_sort = 'asc';
294
        } elseif ($sort === SORT_DESC) {
295
            $this->_sort = 'desc';
296
        } else {
297
            $this->_sort = 'none';
298
        }
299
300
        // set the default advmultiselect element template (with javascript embedded)
301
        $this->setElementTemplate();
302
    }
303
304
    /**
305
     * Sets the button attributes
306
     *
307
     * In <b>custom example 1</b>, the <i>add</i> and <i>remove</i> buttons
308
     * have look set by the css class <i>inputCommand</i>.
309
     *
310
     * In <b>custom example 2</b>, the basic text <i>add</i> and <i>remove</i>
311
     * buttons are now replaced by images.
312
     *
313
     * In <b>custom example 5</b>, we have ability to sort the selection list
314
     * (on right side) by :
315
     * <pre>
316
     *  - <b>user-end</b>: with <i>Up</i> and <i>Down</i> buttons
317
     *  - <b>programming</b>: with the QF element constructor $sort option
318
     * </pre>
319
     *
320
     * @param string $button     Button identifier, either 'add', 'remove',
321
     *                                                     'all', 'none', 'toggle',
322
     *                                                     'movetop', 'movebottom'
323
     *                                                     'moveup' or 'movedown'
324
     * @param mixed  $attributes (optional) Either a typical HTML attribute string
325
     *                                      or an associative array
326
     *
327
     * @return     void
328
     * @throws     PEAR_Error   $button argument
329
     *                          is not a string, or not in range
330
     *                          (add, remove, all, none, toggle,
331
     *                           movetop, movebottom, moveup, movedown)
332
     * @access     public
333
     * @since      version 0.4.0 (2005-06-25)
334
     *
335
     * @example    examples/qfams_custom_5.php
336
     *             Custom example 5: source code
337
     * @link       http://www.laurent-laville.org/img/qfams/screenshot/custom5.png
338
     *             Custom example 5: screenshot
339
     *
340
     * @example    examples/qfams_custom_2.php
341
     *             Custom example 2: source code
342
     * @link       http://www.laurent-laville.org/img/qfams/screenshot/custom2.png
343
     *             Custom example 2: screenshot
344
     *
345
     * @example    examples/qfams_custom_1.php
346
     *             Custom example 1: source code
347
     * @link       http://www.laurent-laville.org/img/qfams/screenshot/custom1.png
348
     *             Custom example 1: screenshot
349
     */
350
    public function setButtonAttributes($button, $attributes = null)
351
    {
352
        if (!is_string($button)) {
353
            return PEAR::throwError('Argument 1 of HTML_QuickForm_advmultiselect::' .
354
                'setButtonAttributes is not a string',
355
                HTML_QUICKFORM_ADVMULTISELECT_ERROR_INVALID_INPUT,
356
                array('level' => 'exception'));
357
        }
358
359
        switch ($button) {
360
            case 'add':
361
                if (is_null($attributes)) {
362
                    $this->_addButtonAttributes = array(
363
                        'name'  => 'add',
364
                        'value' => ' ',
365
                        'type'  => 'button',
366
                        'class'=> 'btn btn-primary'
367
                    );
368
                } else {
369
                    $this->_updateAttrArray(
370
                        $this->_addButtonAttributes,
371
                        $this->_parseAttributes($attributes)
372
                    );
373
                }
374
                break;
375
            case 'remove':
376
                if (is_null($attributes)) {
377
                    $this->_removeButtonAttributes = array(
378
                        'name'  => 'remove',
379
                        'value' => '  ',
380
                        'type'  => 'button',
381
                        'class'=> 'btn btn-primary'
382
                    );
383
                } else {
384
                    $this->_updateAttrArray($this->_removeButtonAttributes,
385
                        $this->_parseAttributes($attributes));
386
                }
387
                break;
388
            case 'all':
389
                if (is_null($attributes)) {
390
                    $this->_allButtonAttributes = array(
391
                        'name'  => 'all',
392
                        'value' => ' Select All ',
393
                        'type'  => 'button'
394
                    );
395
                } else {
396
                    $this->_updateAttrArray($this->_allButtonAttributes,
397
                        $this->_parseAttributes($attributes));
398
                }
399
                break;
400
            case 'none':
401
                if (is_null($attributes)) {
402
                    $this->_noneButtonAttributes
403
                        = array('name'  => 'none',
404
                        'value' => ' Select None ',
405
                        'type'  => 'button');
406
                } else {
407
                    $this->_updateAttrArray($this->_noneButtonAttributes,
408
                        $this->_parseAttributes($attributes));
409
                }
410
                break;
411
            case 'toggle':
412
                if (is_null($attributes)) {
413
                    $this->_toggleButtonAttributes
414
                        = array('name'  => 'toggle',
415
                        'value' => ' Toggle Selection ',
416
                        'type'  => 'button');
417
                } else {
418
                    $this->_updateAttrArray($this->_toggleButtonAttributes,
419
                        $this->_parseAttributes($attributes));
420
                }
421
                break;
422
            case 'moveup':
423
                if (is_null($attributes)) {
424
                    $this->_upButtonAttributes
425
                        = array('name'  => 'up',
426
                        'value' => ' Up ',
427
                        'type'  => 'button');
428
                } else {
429
                    $this->_updateAttrArray($this->_upButtonAttributes,
430
                        $this->_parseAttributes($attributes));
431
                }
432
                break;
433
            case 'movedown':
434
                if (is_null($attributes)) {
435
                    $this->_downButtonAttributes
436
                        = array('name'  => 'down',
437
                        'value' => ' Down ',
438
                        'type'  => 'button');
439
                } else {
440
                    $this->_updateAttrArray($this->_downButtonAttributes,
441
                        $this->_parseAttributes($attributes));
442
                }
443
                break;
444
            case 'movetop':
445
                if (is_null($attributes)) {
446
                    $this->_topButtonAttributes
447
                        = array('name'  => 'top',
448
                        'value' => ' Top ',
449
                        'type'  => 'button');
450
                } else {
451
                    $this->_updateAttrArray($this->_topButtonAttributes,
452
                        $this->_parseAttributes($attributes));
453
                }
454
                break;
455
            case 'movebottom':
456
                if (is_null($attributes)) {
457
                    $this->_bottomButtonAttributes
458
                        = array('name'  => 'bottom',
459
                        'value' => ' Bottom ',
460
                        'type'  => 'button');
461
                } else {
462
                    $this->_updateAttrArray($this->_bottomButtonAttributes,
463
                        $this->_parseAttributes($attributes));
464
                }
465
                break;
466
            default;
467
                return PEAR::throwError('Argument 1 of HTML_QuickForm_advmultiselect::' .
468
                    'setButtonAttributes has unexpected value',
469
                    HTML_QUICKFORM_ADVMULTISELECT_ERROR_INVALID_INPUT,
470
                    array('level' => 'error'));
471
        }
472
    }
473
474
    /**
475
     * Sets element template
476
     *
477
     * @param string $html (optional) The HTML surrounding select boxes and buttons
478
     * @param bool   $js   (optional) if we need to include qfams javascript handler
479
     *
480
     * @access     public
481
     * @return     string
482
     * @since      version 0.4.0 (2005-06-25)
483
     */
484
    public function setElementTemplate($html = null, $js = true)
485
    {
486
        $oldTemplate = $this->_elementTemplate;
487
488
        if (isset($html) && is_string($html)) {
489
            $this->_elementTemplate = $html;
490
        } else {
491
            $this->_elementTemplate = '
492
            {javascript}
493
            <div class="row">
494
              <div class="col-sm"><!-- BEGIN label_2 -->{label_2}<!-- END label_2 --> {unselected}</div>
495
              <div class="col-sm-auto"><div class="text-center">{add}{remove}</div></div>
496
              <div class="col-sm"><!-- BEGIN label_3 -->{label_3}<!-- END label_3 -->{selected}</div>
497
            </div>
498
            ';
499
        }
500
        if ($js === false) {
501
            $this->_elementTemplate = str_replace('{javascript}', '',
502
                $this->_elementTemplate);
503
        }
504
505
        return $oldTemplate;
506
    }
507
508
    /**
509
     * Gets default element stylesheet for a single multi-select shape render
510
     *
511
     * In <b>custom example 4</b>, the template defined allows
512
     * a single multi-select checkboxes shape. Useful when javascript is disabled
513
     * (or when browser is not js compliant). In our example, no need to add
514
     * javascript code, but css is mandatory.
515
     *
516
     * @param boolean $raw (optional) html output with style tags or just raw data
517
     *
518
     * @access     public
519
     * @return     string
520
     * @since      version 0.4.0 (2005-06-25)
521
     *
522
     * @example    qfams_custom_4.php
523
     *             Custom example 4: source code
524
     * @link       http://www.laurent-laville.org/img/qfams/screenshot/custom4.png
525
     *             Custom example 4: screenshot
526
     */
527
    public function getElementCss($raw = true)
528
    {
529
        $id  = $this->getAttribute('name');
530
        $css = str_replace('{id}', $id, $this->_elementCSS);
531
532
        if ($raw !== true) {
533
            $css = '<style type="text/css">' . PHP_EOL
534
                . $css . PHP_EOL
535
                . '</style>';
536
        }
537
        return $css;
538
    }
539
540
    /**
541
     * Returns the HTML generated for the advanced mutliple select component
542
     *
543
     * @access     public
544
     * @return     string
545
     * @since      version 0.4.0 (2005-06-25)
546
     */
547
    public function toHtml()
548
    {
549
        if ($this->_flagFrozen) {
550
            return $this->getFrozenHtml();
551
        }
552
553
        $tabs = $this->_getTabs();
554
        $tab = $this->_getTab();
555
556
        $selectId = $this->getName();
557
        $selectName = $this->getName().'[]';
558
        $selectNameFrom = $this->getName().'-f[]';
559
        $selectNameTo = $this->getName().'[]';
560
        $selected_count = 0;
561
        $rightAll = '';
562
        $leftAll = '';
563
564
        // placeholder {unselected} existence determines if we will render
565
        if (strpos($this->_elementTemplate, '{unselected}') === false) {
566
            // ... a single multi-select with checkboxes
567
            $id = $this->getAttribute('name');
568
569
            $strHtmlSelected  = $tab . '<div id="qfams_'.$id.'">'  . PHP_EOL;
570
            $unselected_count = count($this->_options);
571
            $checkbox_id_suffix = 0;
572
573
            foreach ($this->_options as $option) {
574
                $_labelAttributes
575
                    = array('style', 'class', 'onmouseover', 'onmouseout');
576
                $labelAttributes = array();
577
                foreach ($_labelAttributes as $attr) {
578
                    if (isset($option['attr'][$attr])) {
579
                        $labelAttributes[$attr] = $option['attr'][$attr];
580
                        unset($option['attr'][$attr]);
581
                    }
582
                }
583
584
                if (is_array($this->_values) && in_array((string)$option['attr']['value'], $this->_values)) {
585
                    // The items is *selected*
586
                    $checked = ' checked="checked"';
587
                    $selected_count++;
588
                } else {
589
                    // The item is *unselected* so we want to put it
590
                    $checked = '';
591
                }
592
                $checkbox_id_suffix++;
593
                $strHtmlSelected .= $tab
594
                    .'<label'
595
                    .$this->_getAttrString($labelAttributes).'>'
596
                    .'<input type="checkbox"'
597
                    .' id="'.$selectId.$checkbox_id_suffix.'"'
598
                    .' name="'.$selectName.'"'
599
                    .$checked.$this->_getAttrString($option['attr'])
600
                    .' />'.$option['text'].'</label>'
601
                    .PHP_EOL;
602
            }
603
            $strHtmlSelected .= $tab . '</div>'. PHP_EOL;
604
605
            $strHtmlHidden = '';
606
            $strHtmlUnselected = '';
607
            $strHtmlAdd = '';
608
            $strHtmlRemove = '';
609
610
            // build the select all button with all its attributes
611
            $attributes = [];
612
            $this->_allButtonAttributes = array_merge($this->_allButtonAttributes, $attributes);
613
            $attrStrAll = $this->_getAttrString($this->_allButtonAttributes);
614
            $strHtmlAll = "<input$attrStrAll />". PHP_EOL;
615
616
            // build the select none button with all its attributes
617
            $attributes = [];
618
            $this->_noneButtonAttributes
619
                = array_merge($this->_noneButtonAttributes, $attributes);
620
            $attrStrNone = $this->_getAttrString($this->_noneButtonAttributes);
621
            $strHtmlNone = "<input$attrStrNone />". PHP_EOL;
622
623
            // build the toggle selection button with all its attributes
624
            $attributes = [];
625
            $this->_toggleButtonAttributes = array_merge($this->_toggleButtonAttributes, $attributes);
626
            $attrStrToggle = $this->_getAttrString($this->_toggleButtonAttributes);
627
            $strHtmlToggle = "<input$attrStrToggle />". PHP_EOL;
628
629
            $strHtmlMoveUp = '';
630
            $strHtmlMoveDown = '';
631
            $strHtmlMoveTop = '';
632
            $strHtmlMoveBottom = '';
633
634
            // default selection counters
635
            $strHtmlSelectedCount = $selected_count . '/' . $unselected_count;
636
        } else {
637
            // set name of Select From Box
638
            $this->_attributesUnselected
639
                = array(
640
                'id' => $selectId.'',
641
                'name' => $selectNameFrom,
642
            );
643
            $this->_attributesUnselected = array_merge($this->_attributes, $this->_attributesUnselected);
644
            $attrUnselected = $this->_getAttrString($this->_attributesUnselected);
645
646
            // set name of Select To Box
647
            $this->_attributesSelected
648
                = array(
649
                'id' => $selectId.'_to',
650
                'name' => $selectNameTo,
651
            );
652
            $this->_attributesSelected = array_merge($this->_attributes, $this->_attributesSelected);
653
            $attrSelected = $this->_getAttrString($this->_attributesSelected);
654
655
            // set name of Select hidden Box
656
            $this->_attributesHidden
657
                = array(
658
                'name' => $selectName,
659
                'style' => 'overflow: hidden; visibility: hidden; width: 1px; height: 0;',
660
            );
661
            $this->_attributesHidden
662
                = array_merge($this->_attributes, $this->_attributesHidden);
663
            $attrHidden = $this->_getAttrString($this->_attributesHidden);
664
665
            // prepare option tables to be displayed as in POST order
666
            $append = empty($this->_values) ? 0 : count($this->_values);
667
            if ($append > 0) {
668
                $arrHtmlSelected = array_fill(0, $append, ' ');
669
            } else {
670
                $arrHtmlSelected = array();
671
            }
672
673
            $options = count($this->_options);
674
            $arrHtmlUnselected = array();
675
            if ($options > 0) {
676
                $arrHtmlHidden = array_fill(0, $options, ' ');
677
                foreach ($this->_options as $option) {
678
                    if (is_array($this->_values) && in_array((string) $option['attr']['value'], $this->_values)) {
679
                        // Get the post order
680
                        $key = array_search(
681
                            $option['attr']['value'],
682
                            $this->_values
683
                        );
684
685
                        /** The items is *selected* so we want to put it
686
                        in the 'selected' multi-select */
687
                        $arrHtmlSelected[$key] = $option;
688
                        /** Add it to the 'hidden' multi-select
689
                        and set it as 'selected' */
690
                        if (isset($option['attr']['disabled'])) {
691
                            unset($option['attr']['disabled']);
692
                        }
693
                        $option['attr']['selected'] = 'selected';
694
                        $arrHtmlHidden[$key]        = $option;
695
                    } else {
696
                        /** The item is *unselected* so we want to put it
697
                        in the 'unselected' multi-select */
698
                        $arrHtmlUnselected[] = $option;
699
                        // Add it to the hidden multi-select as 'unselected'
700
                        $arrHtmlHidden[$append] = $option;
701
                        $append++;
702
                    }
703
                }
704
            }
705
706
            // The 'unselected' multi-select which appears on the left
707
            $unselected_count = count($arrHtmlUnselected);
708
            if ($unselected_count == 0) {
709
                $this->_attributesUnselected = array_merge($this->_attributes, $this->_attributesUnselected);
710
                $attrUnselected = $this->_getAttrString($this->_attributesUnselected);
711
            }
712
            $strHtmlUnselected = "<select$attrUnselected>". PHP_EOL;
713
            if ($unselected_count > 0) {
714
                foreach ($arrHtmlUnselected as $data) {
715
                    $strHtmlUnselected
716
                        .= $tabs.$tab
717
                        .'<option'.$this->_getAttrString($data['attr']).'>'
718
                        .$data['text'].'</option>'.PHP_EOL;
719
                }
720
            }
721
            $strHtmlUnselected .= '</select>';
722
723
            // The 'selected' multi-select which appears on the right
724
            $selected_count = count($arrHtmlSelected);
725
            if ($selected_count == 0) {
726
                $this->_attributesSelected = array_merge($this->_attributes, $this->_attributesSelected);
727
                $attrSelected = $this->_getAttrString($this->_attributesSelected);
728
            }
729
            $strHtmlSelected = "<select$attrSelected>";
730
            if ($selected_count > 0) {
731
                foreach ($arrHtmlSelected as $data) {
732
                    if (!is_array($data)) {
733
                        continue;
734
                    }
735
                    $attribute  = null;
736
                    if (isset($data['attr'])) {
737
                        $attribute = $this->_getAttrString($data['attr']);
738
                    }
739
740
                    $text  = null;
741
                    if (isset($data['text'])) {
742
                        $text = $data['text'];
743
                    }
744
                    $strHtmlSelected
745
                        .= $tabs.$tab
746
                        .'<option'.$attribute.'>'
747
                        .$text.'</option>';
748
                }
749
            }
750
            $strHtmlSelected .= '</select>';
751
            $strHtmlHidden = '';
752
            $attributes = array('id' => $selectId.'_leftSelected');
753
            $this->_removeButtonAttributes
754
                = array_merge($this->_removeButtonAttributes, $attributes);
755
            $attrStrRemove = $this->_getAttrString($this->_removeButtonAttributes);
756
            $strHtmlRemove = "<button $attrStrRemove  /> <em class='fa fa-arrow-left'></em></button>";
757
758
            // build the add button with all its attributes
759
            $attributes = array('id' => $selectId.'_rightSelected');
760
            $this->_addButtonAttributes = array_merge($this->_addButtonAttributes, $attributes);
761
            $attrStrAdd = $this->_getAttrString($this->_addButtonAttributes);
762
            $strHtmlAdd = "<button $attrStrAdd  /> <em class='fa fa-arrow-right'></em></button><br /><br />";
763
764
            if ($this->selectAllCheckBox) {
765
                $attributes = array('id' => $selectId.'_rightAll');
766
                $this->_addButtonAttributes = array_merge($this->_addButtonAttributes, $attributes);
767
                $attrStrAdd = $this->_getAttrString($this->_addButtonAttributes);
768
                $rightAll = "<button $attrStrAdd  /> <em class='fa fa-forward'></em></button><br /><br />";
769
770
                $attributes = array('id' => $selectId.'_leftAll');
771
                $this->_addButtonAttributes = array_merge($this->_addButtonAttributes, $attributes);
772
                $attrStrAdd = $this->_getAttrString($this->_addButtonAttributes);
773
                $leftAll = "<br /><br /><button $attrStrAdd  /> <em class='fa fa-backward'></em></button>";
774
            }
775
776
            // build the select all button with all its attributes
777
            $strHtmlAll = '';
778
779
            // build the select none button with all its attributes
780
            $attributes = [];
781
            $this->_noneButtonAttributes
782
                = array_merge($this->_noneButtonAttributes, $attributes);
783
            $attrStrNone = $this->_getAttrString($this->_noneButtonAttributes);
784
            $strHtmlNone = "<input$attrStrNone />". PHP_EOL;
785
786
            // build the toggle button with all its attributes
787
            $attributes = [];
788
            $this->_toggleButtonAttributes
789
                = array_merge($this->_toggleButtonAttributes, $attributes);
790
            $attrStrToggle = $this->_getAttrString($this->_toggleButtonAttributes);
791
            $strHtmlToggle = "<input$attrStrToggle />". PHP_EOL;
792
793
            // build the move up button with all its attributes
794
            $attributes = [];
795
            $this->_upButtonAttributes
796
                = array_merge($this->_upButtonAttributes, $attributes);
797
            $attrStrUp     = $this->_getAttrString($this->_upButtonAttributes);
798
            $strHtmlMoveUp = "<input$attrStrUp />". PHP_EOL;
799
800
            // build the move down button with all its attributes
801
            $attributes = [];
802
            $this->_downButtonAttributes
803
                = array_merge($this->_downButtonAttributes, $attributes);
804
            $attrStrDown     = $this->_getAttrString($this->_downButtonAttributes);
805
            $strHtmlMoveDown = "<input$attrStrDown />". PHP_EOL;
806
807
            // build the move top button with all its attributes
808
            $attributes = [];
809
            $this->_topButtonAttributes
810
                = array_merge($this->_topButtonAttributes, $attributes);
811
            $attrStrTop     = $this->_getAttrString($this->_topButtonAttributes);
812
            $strHtmlMoveTop = "<input$attrStrTop />". PHP_EOL;
813
814
            // build the move bottom button with all its attributes
815
            $attributes = [];
816
            $this->_bottomButtonAttributes
817
                = array_merge($this->_bottomButtonAttributes, $attributes);
818
            $attrStrBottom     = $this->_getAttrString($this->_bottomButtonAttributes);
819
            $strHtmlMoveBottom = "<input$attrStrBottom />". PHP_EOL;
820
821
            // default selection counters
822
            $strHtmlSelectedCount = $selected_count;
823
        }
824
        $strHtmlUnselectedCount = $unselected_count;
825
        $strHtmlSelectedCountId   = $selectId .'_selected';
826
        $strHtmlUnselectedCountId = $selectId .'_unselected';
827
828
        // render all part of the multi select component with the template
829
        $strHtml = $this->_elementTemplate;
830
831
        // Prepare multiple labels
832
        $labels = $this->getLabel();
833
        if (is_array($labels)) {
834
            array_shift($labels);
835
        }
836
        // render extra labels, if any
837
        if (is_array($labels)) {
838
            foreach ($labels as $key => $text) {
839
                $key = is_int($key) ? $key + 2 : $key;
840
                $strHtml = str_replace("{label_{$key}}", $text, $strHtml);
841
                $strHtml = str_replace("<!-- BEGIN label_{$key} -->", '', $strHtml);
842
                $strHtml = str_replace("<!-- END label_{$key} -->", '', $strHtml);
843
            }
844
        }
845
846
        // clean up useless label tags
847
        if (strpos($strHtml, '{label_')) {
848
            $strHtml = preg_replace('/\s*<!-- BEGIN label_(\S+) -->'.
849
                '.*<!-- END label_\1 -->\s*/i', '', $strHtml);
850
        }
851
852
        $placeHolders = array(
853
            '{stylesheet}',
854
            '{javascript}',
855
            '{class}',
856
            '{unselected_count_id}',
857
            '{selected_count_id}',
858
            '{unselected_count}',
859
            '{selected_count}',
860
            '{unselected}',
861
            '{selected}',
862
            '{add}',
863
            '{remove}',
864
            '{all}',
865
            '{none}',
866
            '{toggle}',
867
            '{moveup}',
868
            '{movedown}',
869
            '{movetop}',
870
            '{movebottom}',
871
        );
872
873
        $htmlElements = array(
874
            $this->getElementCss(false),
875
            $this->getElementJs(false),
876
            $this->_tableAttributes,
877
            $strHtmlUnselectedCountId,
878
            $strHtmlSelectedCountId,
879
            $strHtmlUnselectedCount,
880
            $strHtmlSelectedCount,
881
            $strHtmlUnselected,
882
            $strHtmlSelected.$strHtmlHidden,
883
            $rightAll.$strHtmlAdd,
884
            $strHtmlRemove.$leftAll,
885
            $strHtmlAll,
886
            $strHtmlNone,
887
            $strHtmlToggle,
888
            $strHtmlMoveUp,
889
            $strHtmlMoveDown,
890
            $strHtmlMoveTop,
891
            $strHtmlMoveBottom,
892
        );
893
894
        $strHtml = str_replace($placeHolders, $htmlElements, $strHtml);
895
        $comment = $this->getComment();
896
897
        if (!empty($comment)) {
898
            $strHtml = $tabs . '<!-- ' . $comment . " //-->" . PHP_EOL . $strHtml;
899
        }
900
901
        return $strHtml;
902
    }
903
904
    /**
905
     * Returns the javascript code generated to handle this element
906
     *
907
     * @param boolean $raw (optional) html output with script tags or just raw data
908
     * @param boolean $min (optional) uses javascript compressed version
909
     *
910
     * @access     public
911
     * @return     string
912
     * @since      version 0.4.0 (2005-06-25)
913
     */
914
    public function getElementJs($raw = true, $min = true)
915
    {
916
        $name = $this->getName();
917
        $js = '';
918
        $search =
919
        '<input type="text" name="q" class="form-control mb-3" placeholder="'.addslashes(get_lang('Search')).'" />';
920
921
        $js .= '<script>
922
                $(function() {
923
                    $(\'#'.$name.'\').multiselect({
924
                        search: {
925
                            left: \''.$search.'\',
926
                            right: \''.$search.'\'
927
                        },
928
                        fireSearch: function(value) {                        
929
                            return value.length > 2;
930
                        }
931
                    });
932
                });
933
                </script>'.PHP_EOL;
934
935
        return $js;
936
    }
937
938
    /**
939
     * Loads options from different types of data sources
940
     *
941
     * This method overloaded parent method of select element, to allow
942
     * loading options with fancy attributes.
943
     *
944
     * @param mixed &$options Options source currently supports assoc array or DB_result
945
     * @param mixed $param1   (optional) See function detail
946
     * @param mixed $param2   (optional) See function detail
947
     * @param mixed $param3   (optional) See function detail
948
     * @param mixed $param4   (optional) See function detail
949
     *
950
     * @access     public
951
     * @since      version 1.5.0 (2009-02-15)
952
     * @return     PEAR_Error|NULL on error and TRUE on success
953
     * @throws     PEAR_Error
954
     * @see        loadArray()
955
     */
956
    public function load(&$options,
957
        $param1 = null, $param2 = null, $param3 = null, $param4 = null)
958
    {
959
        if (is_array($options)) {
960
            $ret = $this->loadArray($options, $param1);
961
        } else {
962
            $ret = parent::load($options, $param1, $param2, $param3, $param4);
963
        }
964
        return $ret;
965
    }
966
967
    /**
968
     * Loads the options from an associative array
969
     *
970
     * This method overloaded parent method of select element, to allow to load
971
     * array of options with fancy attributes.
972
     *
973
     * @param array $arr    Associative array of options
974
     * @param mixed $values (optional) Array or comma delimited string of selected values
975
     *
976
     * @since      version 1.5.0 (2009-02-15)
977
     * @access     public
978
     * @return     PEAR_Error on error and TRUE on success
979
     * @throws     PEAR_Error
980
     * @see        load()
981
     */
982
    public function loadArray($arr, $values = null)
983
    {
984
        if (!is_array($arr)) {
985
            return PEAR::throwError('Argument 1 of HTML_QuickForm_advmultiselect::' .
986
                'loadArray is not a valid array',
987
                HTML_QUICKFORM_ADVMULTISELECT_ERROR_INVALID_INPUT,
988
                array('level' => 'exception'));
989
        }
990
        if (isset($values)) {
991
            $this->setSelected($values);
992
        }
993
        if (is_array($arr)) {
994
            foreach ($arr as $key => $val) {
995
                if (is_array($val)) {
996
                    $this->addOption($val[0], $key, $val[1]);
997
                } else {
998
                    $this->addOption($val, $key);
999
                }
1000
            }
1001
        }
1002
        return true;
1003
    }
1004
}
1005