1<?php
2/**
3 * TbHtml class file.
4 * @author Antonio Ramirez <ramirez.cobos@gmail.com>
5 * @author Christoffer Niska <christoffer.niska@gmail.com>
6 * @copyright Copyright &copy; Christoffer Niska 2013-
7 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
8 * @package bootstrap.helpers
9 */
10
11/**
12 * Bootstrap HTML helper.
13 */
14class TbHtml extends CHtml // required in order to access the protected methods in CHtml
15{
16    //
17    // TYPOGRAPHY
18    // --------------------------------------------------
19
20    const TEXT_ALIGN_LEFT = 'left';
21    const TEXT_ALIGN_CENTER = 'center';
22    const TEXT_ALIGN_RIGHT = 'right';
23    const TEXT_ALIGN_JUSTIFY = 'justify';
24    const TEXT_ALIGN_NOWRAP = 'nowrap';
25
26    const TEXT_COLOR_DEFAULT = '';
27    const TEXT_COLOR_WARNING = 'warning';
28    const TEXT_COLOR_ERROR = 'error';
29    const TEXT_COLOR_INFO = 'info';
30    const TEXT_COLOR_SUCCESS = 'success';
31
32    const HELP_TYPE_INLINE = 'inline';
33    const HELP_TYPE_BLOCK = 'block';
34
35    //
36    // FORM
37    // --------------------------------------------------
38
39    const FORM_LAYOUT_VERTICAL = 'vertical';
40    const FORM_LAYOUT_HORIZONTAL = 'horizontal';
41    const FORM_LAYOUT_INLINE = 'inline';
42    const FORM_LAYOUT_SEARCH = 'search';
43
44    const INPUT_TYPE_TEXT = 'textField';
45    const INPUT_TYPE_PASSWORD = 'passwordField';
46    const INPUT_TYPE_URL = 'urlField';
47    const INPUT_TYPE_EMAIL = 'emailField';
48    const INPUT_TYPE_NUMBER = 'numberField';
49    const INPUT_TYPE_RANGE = 'rangeField';
50    const INPUT_TYPE_DATE = 'dateField';
51    const INPUT_TYPE_TEXTAREA = 'textArea';
52    const INPUT_TYPE_FILE = 'fileField';
53    const INPUT_TYPE_RADIOBUTTON = 'radioButton';
54    const INPUT_TYPE_CHECKBOX = 'checkBox';
55    const INPUT_TYPE_DROPDOWNLIST = 'dropDownList';
56    const INPUT_TYPE_LISTBOX = 'listBox';
57    const INPUT_TYPE_CHECKBOXLIST = 'checkBoxList';
58    const INPUT_TYPE_INLINECHECKBOXLIST = 'inlineCheckBoxList';
59    const INPUT_TYPE_RADIOBUTTONLIST = 'radioButtonList';
60    const INPUT_TYPE_INLINERADIOBUTTONLIST = 'inlineRadioButtonList';
61    const INPUT_TYPE_UNEDITABLE = 'uneditableField';
62    const INPUT_TYPE_SEARCH = 'searchQuery';
63    const INPUT_TYPE_HIDDEN = 'hidden';
64    const INPUT_TYPE_CUSTOM = 'widget';
65
66    // Input sizes are deprecated in BS3, use col-*-* instead.
67    const INPUT_SIZE_MINI = 'mini';
68    const INPUT_SIZE_SMALL = 'small';
69    const INPUT_SIZE_DEFAULT = '';
70    const INPUT_SIZE_MEDIUM = 'medium';
71    const INPUT_SIZE_LARGE = 'large';
72    const INPUT_SIZE_XLARGE = 'xlarge';
73    const INPUT_SIZE_XXLARGE = 'xxlarge';
74
75    const INPUT_HEIGHT_SMALL = 'sm';
76    const INPUT_HEIGHT_DEFAULT = '';
77    const INPUT_HEIGHT_LARGE = 'lg';
78
79    const INPUT_COLOR_DEFAULT = '';
80    const INPUT_COLOR_WARNING = 'has-warning';
81    const INPUT_COLOR_ERROR = 'has-error';
82    const INPUT_COLOR_SUCCESS = 'has-success';
83
84    //
85    // BUTTONS
86    // --------------------------------------------------
87
88    const BUTTON_TYPE_LINK = 'link';
89    const BUTTON_TYPE_HTML = 'htmlButton';
90    const BUTTON_TYPE_SUBMIT = 'submitButton';
91    const BUTTON_TYPE_RESET = 'resetButton';
92    const BUTTON_TYPE_IMAGE = 'imageButton';
93    const BUTTON_TYPE_LINKBUTTON = 'linkButton';
94    const BUTTON_TYPE_AJAXLINK = 'ajaxLink';
95    const BUTTON_TYPE_AJAXBUTTON = 'ajaxButton';
96    const BUTTON_TYPE_INPUTBUTTON = 'inputButton';
97    const BUTTON_TYPE_INPUTSUBMIT = 'inputSubmit';
98
99    const BUTTON_COLOR_DEFAULT = 'default';
100    const BUTTON_COLOR_PRIMARY = 'primary';
101    const BUTTON_COLOR_INFO = 'info';
102    const BUTTON_COLOR_SUCCESS = 'success';
103    const BUTTON_COLOR_WARNING = 'warning';
104    const BUTTON_COLOR_DANGER = 'danger';
105    // todo: remove this as it is deprecated in bs3
106    const BUTTON_COLOR_INVERSE = 'inverse';
107    const BUTTON_COLOR_LINK = 'link';
108
109    const BUTTON_SIZE_MINI = 'xs'; // BS2 compatibility
110    const BUTTON_SIZE_XS = 'xs';
111    const BUTTON_SIZE_SMALL = 'sm'; // BS2 compatibility
112    const BUTTON_SIZE_SM = 'sm';
113    const BUTTON_SIZE_DEFAULT = 'default';
114    const BUTTON_SIZE_LARGE = 'lg'; // BS2 compatibility
115    const BUTTON_SIZE_LG = 'lg';
116
117
118    const BUTTON_TOGGLE_CHECKBOX = 'checkbox';
119    const BUTTON_TOGGLE_RADIO = 'radio';
120
121    //
122    // IMAGES
123    // --------------------------------------------------
124
125    const IMAGE_TYPE_ROUNDED = 'rounded';
126    const IMAGE_TYPE_CIRCLE = 'circle';
127    // todo: remove this as it is deprecated in bs3
128    const IMAGE_TYPE_POLAROID = 'thumbnail';
129    const IMAGE_TYPE_THUMBNAIL = 'thumbnail';
130
131    //
132    // NAV
133    // --------------------------------------------------
134
135    const NAV_TYPE_NONE = '';
136    const NAV_TYPE_TABS = 'tabs';
137    const NAV_TYPE_PILLS = 'pills';
138    const NAV_TYPE_LIST = 'list';
139
140    const TABS_PLACEMENT_ABOVE = '';
141    // todo: remove this as it is deprecated in bs3
142    const TABS_PLACEMENT_BELOW = 'below';
143    const TABS_PLACEMENT_LEFT = 'left';
144    const TABS_PLACEMENT_RIGHT = 'right';
145
146    //
147    // NAVBAR
148    // --------------------------------------------------
149
150    const NAVBAR_DISPLAY_NONE = '';
151    const NAVBAR_DISPLAY_FIXEDTOP = 'fixed-top';
152    const NAVBAR_DISPLAY_FIXEDBOTTOM = 'fixed-bottom';
153    const NAVBAR_DISPLAY_STATICTOP = 'static-top';
154
155    const NAVBAR_COLOR_INVERSE = 'inverse';
156
157    //
158    // PAGINATION
159    // --------------------------------------------------
160
161    const PAGINATION_SIZE_MINI = 'mini'; // deprecated, does not exist in BS3
162    const PAGINATION_SIZE_SMALL = 'sm'; // deprecated, BS3 compatibility
163    const PAGINATION_SIZE_SM = 'sm';
164    const PAGINATION_SIZE_DEFAULT = '';
165    const PAGINATION_SIZE_LARGE = 'lg'; // deprecated, BS3 compatibility
166    const PAGINATION_SIZE_LG = 'lg';
167
168    const PAGINATION_ALIGN_LEFT = 'left'; // deprecated in BS3?
169    const PAGINATION_ALIGN_CENTER = 'centered'; // deprecated in BS3?
170    const PAGINATION_ALIGN_RIGHT = 'right'; // deprecated in BS3?
171
172    //
173    // LABELS AND BADGES
174    // --------------------------------------------------
175
176    const LABEL_COLOR_DEFAULT = 'default';
177    const LABEL_COLOR_PRIMARY = 'primary';
178    const LABEL_COLOR_SUCCESS = 'success';
179    const LABEL_COLOR_INFO = 'info';
180    const LABEL_COLOR_WARNING = 'warning';
181    const LABEL_COLOR_DANGER = 'danger';
182
183    const BADGE_COLOR_DEFAULT = ''; // deprecated, only a single badge color in BS3
184    const BADGE_COLOR_SUCCESS = 'success'; // deprecated, only a single badge color in BS3
185    const BADGE_COLOR_WARNING = 'warning'; // deprecated, only a single badge color in BS3
186    const BADGE_COLOR_IMPORTANT = 'important'; // deprecated, only a single badge color in BS3
187    const BADGE_COLOR_INFO = 'info'; // deprecated, only a single badge color in BS3
188    const BADGE_COLOR_INVERSE = 'inverse'; // deprecated, only a single badge color in BS3
189
190    //
191    // TOOLTIPS AND POPOVERS
192    // --------------------------------------------------
193
194    const TOOLTIP_PLACEMENT_TOP = 'top';
195    const TOOLTIP_PLACEMENT_BOTTOM = 'bottom';
196    const TOOLTIP_PLACEMENT_LEFT = 'left';
197    const TOOLTIP_PLACEMENT_RIGHT = 'right';
198
199    const TOOLTIP_TRIGGER_CLICK = 'click';
200    const TOOLTIP_TRIGGER_HOVER = 'hover';
201    const TOOLTIP_TRIGGER_FOCUS = 'focus';
202    const TOOLTIP_TRIGGER_MANUAL = 'manual';
203
204    const POPOVER_PLACEMENT_TOP = 'top';
205    const POPOVER_PLACEMENT_BOTTOM = 'bottom';
206    const POPOVER_PLACEMENT_LEFT = 'left';
207    const POPOVER_PLACEMENT_RIGHT = 'right';
208
209    const POPOVER_TRIGGER_CLICK = 'click';
210    const POPOVER_TRIGGER_HOVER = 'hover';
211    const POPOVER_TRIGGER_FOCUS = 'focus';
212    const POPOVER_TRIGGER_MANUAL = 'manual';
213
214    //
215    // ALERT
216    // --------------------------------------------------
217
218    const ALERT_COLOR_DEFAULT = '';
219    const ALERT_COLOR_INFO = 'info';
220    const ALERT_COLOR_SUCCESS = 'success';
221    const ALERT_COLOR_WARNING = 'warning';
222    const ALERT_COLOR_DANGER = 'danger';
223
224    //
225    // PROGRESS BARS
226    // --------------------------------------------------
227
228    const PROGRESS_COLOR_DEFAULT = '';
229    const PROGRESS_COLOR_INFO = 'info';
230    const PROGRESS_COLOR_SUCCESS = 'success';
231    const PROGRESS_COLOR_WARNING = 'warning';
232    const PROGRESS_COLOR_DANGER = 'danger';
233
234    //
235    // MISC
236    // --------------------------------------------------
237
238    const WELL_SIZE_SMALL = 'small';
239    const WELL_SIZE_DEFAULT = '';
240    const WELL_SIZE_LARGE = 'large';
241
242    const PULL_LEFT = 'left';
243    const PULL_RIGHT = 'right';
244
245    const CLOSE_DISMISS_ALERT = 'alert';
246    const CLOSE_DISMISS_MODAL = 'modal';
247
248    //
249    // DETAIL VIEW
250    // --------------------------------------------------
251
252    const DETAIL_TYPE_STRIPED = 'striped';
253    const DETAIL_TYPE_BORDERED = 'bordered';
254    const DETAIL_TYPE_CONDENSED = 'condensed';
255    const DETAIL_TYPE_HOVER = 'hover';
256
257    //
258    // GRID VIEW
259    // --------------------------------------------------
260
261    const GRID_TYPE_STRIPED = 'striped';
262    const GRID_TYPE_BORDERED = 'bordered';
263    const GRID_TYPE_CONDENSED = 'condensed';
264    const GRID_TYPE_HOVER = 'hover';
265
266    //
267    // AFFIX
268    // --------------------------------------------------
269
270    const AFFIX_POSITION_TOP = 'top';
271    const AFFIX_POSITION_BOTTOM = 'bottom';
272
273	//
274    // COLUMNS
275    // --------------------------------------------------
276
277    const COLUMN_SIZE_XS = 'xs';
278    const COLUMN_SIZE_SM = 'sm';
279    const COLUMN_SIZE_MD = 'md';
280    const COLUMN_SIZE_LG = 'lg';
281    // Verbose
282    const COLUMN_SIZE_EXTRA_SMALL = 'xs';
283    const COLUMN_SIZE_SMALL = 'sm';
284    const COLUMN_SIZE_MEDIUM = 'md';
285    const COLUMN_SIZE_LARGE = 'lg';
286
287    //
288    // MODAL
289    // --------------------------------------------------
290
291    const MODAL_SIZE_SMALL = ' modal-sm';
292    const MODAL_SIZE_DEFAULT = '';
293    const MODAL_SIZE_LARGE = ' modal-lg';
294
295    //
296    // ICON
297    // --------------------------------------------------
298
299    const ICON_COLOR_DEFAULT = '';
300    const ICON_COLOR_WHITE = 'fa-white';
301
302    const ICON_ADJUST = 'fa-adjust';
303    const ICON_ALIGN_CENTER = 'fa-align-center';
304    const ICON_ALIGN_JUSTIFY = 'fa-align-justify';
305    const ICON_ALIGN_LEFT = 'fa-align-left';
306    const ICON_ALIGN_RIGHT = 'fa-align-right';
307    const ICON_ARROW_DOWN = 'fa-arrow-down';
308    const ICON_ARROW_LEFT = 'fa-arrow-left';
309    const ICON_ARROW_RIGHT = 'fa-arrow-right';
310    const ICON_ARROW_UP = 'fa-arrow-up';
311    const ICON_ASTERISK = 'fa-asterisk';
312    const ICON_BACKWARD = 'fa-backward';
313    const ICON_BAN_CIRCLE = 'fa-ban-circle';
314    const ICON_BARCODE = 'fa-barcode';
315    const ICON_BELL = 'fa-bell';
316    const ICON_BOLD = 'fa-bold';
317    const ICON_BOOK = 'fa-book';
318    const ICON_BOOKMARK = 'fa-bookmark';
319    const ICON_BRIEFCASE = 'fa-briefcase';
320    const ICON_BULLHORN = 'fa-bullhorn';
321    const ICON_CALENDAR = 'fa-calendar';
322    const ICON_CAMERA = 'fa-camera';
323    const ICON_CERTIFICATE = 'fa-certificate';
324    const ICON_CHECK = 'fa-check';
325    const ICON_CHEVRON_DOWN = 'fa-chevron-down';
326    const ICON_CHEVRON_LEFT = 'fa-chevron-left';
327    const ICON_CHEVRON_RIGHT = 'fa-chevron-right';
328    const ICON_CHEVRON_UP = 'fa-chevron-up';
329    const ICON_CIRCLE_ARROW_DOWN = 'fa-circle-arrow-down';
330    const ICON_CIRCLE_ARROW_LEFT = 'fa-circle-arrow-left';
331    const ICON_CIRCLE_ARROW_RIGHT = 'fa-circle-arrow-right';
332    const ICON_CIRCLE_ARROW_UP = 'fa-circle-arrow-up';
333    const ICON_CLOUD = 'fa-cloud';
334    const ICON_CLOUD_DOWNLOAD = 'fa-cloud-download';
335    const ICON_CLOUD_UPLOAD = 'fa-cloud-upload';
336    const ICON_COG = 'fa-cog';
337    const ICON_COLLAPSE_DOWN = 'fa-collapse-down';
338    const ICON_COLLAPSE_UP = 'fa-collapse-up';
339    const ICON_COMMENT = 'fa-comment';
340    const ICON_COMPRESSED = 'fa-compressed';
341    const ICON_COPYRIGHT_MARK = 'fa-copyright-mark';
342    const ICON_CREDIT_CARD = 'fa-credit-card';
343    const ICON_CUTLERY = 'fa-cutlery';
344    const ICON_DASHBOARD = 'fa-dashboard';
345    const ICON_DOWNLOAD = 'fa-download';
346    const ICON_DOWNLOAD_ALT = 'fa-download-alt';
347    const ICON_EARPHONE = 'fa-earphone';
348    const ICON_EDIT = 'fa-edit';
349    const ICON_EJECT = 'fa-eject';
350    const ICON_ENVELOPE = 'fa-envelope';
351    const ICON_EURO = 'fa-euro';
352    const ICON_EXCLAMATION_SIGN = 'fa-exclamation-sign';
353    const ICON_EXPAND = 'fa-expand';
354    const ICON_EXPORT = 'fa-export';
355    const ICON_EYE_CLOSE = 'fa-eye-close';
356    const ICON_EYE_OPEN = 'fa-eye';
357    const ICON_FACETIME_VIDEO = 'fa-facetime-video';
358    const ICON_FAST_BACKWARD = 'fa-fast-backward';
359    const ICON_FAST_FORWARD = 'fa-fast-forward';
360    const ICON_FILE = 'fa-file';
361    const ICON_FILM = 'fa-film';
362    const ICON_FILTER = 'fa-filter';
363    const ICON_FIRE = 'fa-fire';
364    const ICON_FLAG = 'fa-flag';
365    const ICON_FLASH = 'fa-flash';
366    const ICON_FLOPPY_DISK = 'fa-floppy-disk';
367    const ICON_FLOPPY_OPEN = 'fa-floppy-open';
368    const ICON_FLOPPY_REMOVE = 'fa-floppy-remove';
369    const ICON_FLOPPY_SAVE = 'fa-floppy-save';
370    const ICON_FLOPPY_SAVED = 'fa-floppy-saved';
371    const ICON_FOLDER_CLOSE = 'fa-folder-close';
372    const ICON_FOLDER_OPEN = 'fa-folder-open';
373    const ICON_FONT = 'fa-font';
374    const ICON_FORWARD = 'fa-forward';
375    const ICON_FULLSCREEN = 'fa-fullscreen';
376    const ICON_GBP = 'fa-gbp';
377    const ICON_GIFT = 'fa-gift';
378    const ICON_GLASS = 'fa-glass';
379    const ICON_GLOBE = 'fa-globe';
380    const ICON_HAND_DOWN = 'fa-hand-down';
381    const ICON_HAND_LEFT = 'fa-hand-left';
382    const ICON_HAND_RIGHT = 'fa-hand-right';
383    const ICON_HAND_UP = 'fa-hand-up';
384    const ICON_HD_VIDEO = 'fa-hd-video';
385    const ICON_HDD = 'fa-hdd';
386    const ICON_HEADER = 'fa-header';
387    const ICON_HEADPHONES = 'fa-headphones';
388    const ICON_HEART = 'fa-heart';
389    const ICON_HEART_EMPTY = 'fa-heart-empty';
390    const ICON_HOME = 'fa-home';
391    const ICON_IMPORT = 'fa-import';
392    const ICON_INBOX = 'fa-inbox';
393    const ICON_INDENT_LEFT = 'fa-indent-left';
394    const ICON_INDENT_RIGHT = 'fa-indent-right';
395    const ICON_INFO_SIGN = 'fa-info-sign';
396    const ICON_ITALIC = 'fa-italic';
397    const ICON_LEAF = 'fa-leaf';
398    const ICON_LINK = 'fa-link';
399    const ICON_LIST = 'fa-list';
400    const ICON_LIST_ALT = 'fa-list-alt';
401    const ICON_LOCK = 'fa-lock';
402    const ICON_LOG_IN = 'fa-log-in';
403    const ICON_LOG_OUT = 'fa-log-out';
404    const ICON_MAGNET = 'fa-magnet';
405    const ICON_MAP_MARKER = 'fa-map-marker';
406    const ICON_MINUS = 'fa-minus';
407    const ICON_MINUS_SIGN = 'fa-minus-sign';
408    const ICON_MOVE = 'fa-bars bigIcons';
409    const ICON_MUSIC = 'fa-music';
410    const ICON_NEW_WINDOW = 'fa-new-window';
411    const ICON_OFF = 'fa-off';
412    const ICON_OK = 'fa-ok';
413    const ICON_OK_CIRCLE = 'fa-ok-circle';
414    const ICON_OK_SIGN = 'fa-ok-sign';
415    const ICON_OPEN = 'fa-open';
416    const ICON_PAPERCLIP = 'fa-paperclip';
417    const ICON_PAUSE = 'fa-pause';
418    const ICON_PENCIL = 'fa-pencil';
419    const ICON_PHONE = 'fa-phone';
420    const ICON_PHONE_ALT = 'fa-phone-alt';
421    const ICON_PICTURE = 'fa-picture';
422    const ICON_PLANE = 'fa-plane';
423    const ICON_PLAY = 'fa-play';
424    const ICON_PLAY_CIRCLE = 'fa-play-circle';
425    const ICON_PLUS = 'fa-plus';
426    const ICON_PLUS_SIGN = 'fa-plus-sign';
427    const ICON_PRINT = 'fa-print';
428    const ICON_PUSHPIN = 'fa-pushpin';
429    const ICON_QRCODE = 'fa-qrcode';
430    const ICON_QUESTION_SIGN = 'fa-question-sign';
431    const ICON_RANDOM = 'fa-random';
432    const ICON_RECORD = 'fa-record';
433    const ICON_REFRESH = 'fa-refresh';
434    const ICON_REGISTRATION_MARK = 'fa-registration-mark';
435    const ICON_REMOVE = 'fa-remove';
436    const ICON_REMOVE_CIRCLE = 'fa-remove-circle';
437    const ICON_REMOVE_SIGN = 'fa-remove-sign';
438    const ICON_REPEAT = 'fa-repeat';
439    const ICON_RESIZE_FULL = 'fa-resize-full';
440    const ICON_RESIZE_HORIZONTAL = 'fa-resize-horizontal';
441    const ICON_RESIZE_SMALL = 'fa-resize-small';
442    const ICON_RESIZE_VERTICAL = 'fa-resize-vertical';
443    const ICON_RETWEET = 'fa-retweet';
444    const ICON_ROAD = 'fa-road';
445    const ICON_SAVE = 'fa-save';
446    const ICON_SAVED = 'fa-saved';
447    const ICON_SCREENSHOT = 'fa-screenshot';
448    const ICON_SD_VIDEO = 'fa-sd-video';
449    const ICON_SEARCH = 'fa-search';
450    const ICON_SEND = 'fa-send';
451    const ICON_SHARE = 'fa-share';
452    const ICON_SHARE_ALT = 'fa-share-alt';
453    const ICON_SHOPPING_CART = 'fa-shopping-cart';
454    const ICON_SIGNAL = 'fa-signal';
455    const ICON_SORT = 'fa-sort';
456    const ICON_SORT_BY_ALPHABET = 'fa-sort-by-alphabet';
457    const ICON_SORT_BY_ALPHABET_ALT = 'fa-sort-by-alphabet-alt';
458    const ICON_SORT_BY_ATTRIBUTES = 'fa-sort-by-attributes';
459    const ICON_SORT_BY_ATTRIBUTES_ALT = 'fa-sort-by-attributes-alt';
460    const ICON_SORT_BY_ORDER = 'fa-sort-by-order';
461    const ICON_SORT_BY_ORDER_ALT = 'fa-sort-by-order-alt';
462    const ICON_SOUND_5_1 = 'fa-sound-5-1';
463    const ICON_SOUND_6_1 = 'fa-sound-6-1';
464    const ICON_SOUND_7_1 = 'fa-sound-7-1';
465    const ICON_SOUND_DOLBY = 'fa-sound-dolby';
466    const ICON_SOUND_STEREO = 'fa-sound-stereo';
467    const ICON_STAR = 'fa-star';
468    const ICON_STAR_EMPTY = 'fa-star-empty';
469    const ICON_STATS = 'fa-bar-chart';
470    const ICON_STEP_BACKWARD = 'fa-step-backward';
471    const ICON_STEP_FORWARD = 'fa-step-forward';
472    const ICON_STOP = 'fa-stop';
473    const ICON_SUBTITLES = 'fa-subtitles';
474    const ICON_TAG = 'fa-tag';
475    const ICON_TAGS = 'fa-tags';
476    const ICON_TASKS = 'fa-tasks';
477    const ICON_TEXT_HEIGHT = 'fa-text-height';
478    const ICON_TEXT_WIDTH = 'fa-text-width';
479    const ICON_TH = 'fa-th';
480    const ICON_TH_LARGE = 'fa-th-large';
481    const ICON_TH_LIST = 'fa-th-list';
482    const ICON_THUMBS_DOWN = 'fa-thumbs-down';
483    const ICON_THUMBS_UP = 'fa-thumbs-up';
484    const ICON_TIME = 'fa-time';
485    const ICON_TINT = 'fa-tint';
486    const ICON_TOWER = 'fa-tower';
487    const ICON_TRANSFER = 'fa-transfer';
488    const ICON_TRASH = 'fa-trash';
489    const ICON_TREE_CONIFER = 'fa-tree-conifer';
490    const ICON_TREE_DECIDUOUS = 'fa-tree-deciduous';
491    const ICON_UNCHECKED = 'fa-unchecked';
492    const ICON_UPLOAD = 'fa-upload';
493    const ICON_USD = 'fa-usd';
494    const ICON_USER = 'fa-user';
495    const ICON_VOLUME_DOWN = 'fa-volume-down';
496    const ICON_VOLUME_OFF = 'fa-volume-off';
497    const ICON_VOLUME_UP = 'fa-volume-up';
498    const ICON_WARNING_SIGN = 'fa-warning-sign';
499    const ICON_WRENCH = 'fa-wrench';
500    const ICON_ZOOM_IN = 'fa-zoom-in';
501    const ICON_ZOOM_OUT = 'fa-zoom-out';
502
503    // Default close text.
504    const CLOSE_TEXT = '&times;';
505
506    /**
507     * @var string the CSS class for displaying error summaries.
508     */
509    public static $errorSummaryCss = 'alert alert-block alert-danger';
510    /**
511     * @var string the CSS class for displaying error inputs
512     */
513    public static $errorCss = 'has-error';
514    /**
515     * @var string the icon vendor
516     */
517    public static $iconVendor = 'fa';
518    /**
519     * @var string default form label width
520     */
521    // todo: remove this.
522    protected static $defaultFormLabelWidthClass = 'col-sm-2';
523    /**
524     * @var string default form control width
525     */
526    // todo: remove this.
527    protected static $defaultFormControlWidthClass = 'col-sm-10';
528
529    //
530    // BASE CSS
531    // --------------------------------------------------
532
533    // Typography
534    // http://getbootstrap.com/css/#type
535    // --------------------------------------------------
536
537    /**
538     * Generates a paragraph that stands out.
539     * @param string $text the lead text.
540     * @param array $htmlOptions additional HTML attributes.
541     * @return string the generated paragraph.
542     */
543    public static function lead($text, $htmlOptions = array())
544    {
545        self::addCssClass('lead', $htmlOptions);
546        return self::tag('p', $htmlOptions, $text);
547    }
548
549    /**
550     * Generates small text.
551     * @param string $text the text to style.
552     * @param array $htmlOptions additional HTML attributes.
553     * @return string the generated text.
554     */
555    public static function small($text, $htmlOptions = array())
556    {
557        return self::tag('small', $htmlOptions, $text);
558    }
559
560    /**
561     * Generates bold text.
562     * @param string $text the text to style.
563     * @param array $htmlOptions additional HTML attributes.
564     * @return string the generated text.
565     */
566    public static function b($text, $htmlOptions = array())
567    {
568        return self::tag('strong', $htmlOptions, $text);
569    }
570
571    /**
572     * Generates italic text.
573     * @param string $text the text to style.
574     * @param array $htmlOptions additional HTML attributes.
575     * @return string the generated text.
576     */
577    public static function i($text, $htmlOptions = array())
578    {
579        return self::tag('em', $htmlOptions, $text);
580    }
581
582    /**
583     * Generates an emphasized text.
584     * @param string $text the text to emphasize.
585     * @param array $htmlOptions additional HTML attributes.
586     * @param string $tag the HTML tag.
587     * @return string the generated text.
588     */
589    public static function em($text, $htmlOptions = array(), $tag = 'p')
590    {
591        $color = TbArray::popValue('color', $htmlOptions);
592        if (TbArray::popValue('muted', $htmlOptions, false)) {
593            self::addCssClass('muted', $htmlOptions);
594        } else {
595            if (!empty($color)) {
596                self::addCssClass('text-' . $color, $htmlOptions);
597            }
598        }
599        return self::tag($tag, $htmlOptions, $text);
600    }
601
602    /**
603     * Generates a muted text block.
604     * @param string $text the text.
605     * @param array $htmlOptions additional HTML attributes.
606     * @param string $tag the HTML tag.
607     * @return string the generated text block.
608     */
609    public static function muted($text, $htmlOptions = array(), $tag = 'p')
610    {
611        $htmlOptions['muted'] = true;
612        return self::em($text, $htmlOptions, $tag);
613    }
614
615    /**
616     * Generates a muted span.
617     * @param string $text the text.
618     * @param array $htmlOptions additional HTML attributes.
619     * @return string the generated span.
620     */
621    public static function mutedSpan($text, $htmlOptions = array())
622    {
623        return self::muted($text, $htmlOptions, 'span');
624    }
625
626    /**
627     * Generates an abbreviation with a help text.
628     * @param string $text the abbreviation.
629     * @param string $word the word the abbreviation is for.
630     * @param array $htmlOptions additional HTML attributes.
631     * @return string the generated abbreviation.
632     */
633    public static function abbr($text, $word, $htmlOptions = array())
634    {
635        if (TbArray::popValue('small', $htmlOptions, false)) {
636            self::addCssClass('initialism', $htmlOptions);
637        }
638        $htmlOptions['title'] = $word;
639        return self::tag('abbr', $htmlOptions, $text);
640    }
641
642    /**
643     * Generates a small abbreviation with a help text.
644     * @param string $text the abbreviation.
645     * @param string $word the word the abbreviation is for.
646     * @param array $htmlOptions additional HTML attributes.
647     * @return string the generated abbreviation.
648     */
649    public static function smallAbbr($text, $word, $htmlOptions = array())
650    {
651        $htmlOptions['small'] = true;
652        return self::abbr($text, $word, $htmlOptions);
653    }
654
655    /**
656     * Generates an address block.
657     * @param $text
658     * @param array $htmlOptions additional HTML attributes.
659     * @return string the generated block.
660     */
661    public static function address($text, $htmlOptions = array())
662    {
663        return self::tag('address', $htmlOptions, $text);
664    }
665
666    /**
667     * Generates a quote.
668     * @param string $text the quoted text.
669     * @param array $htmlOptions additional HTML attributes.
670     * @return string the generated quote.
671     */
672    public static function quote($text, $htmlOptions = array())
673    {
674        $paragraphOptions = TbArray::popValue('paragraphOptions', $htmlOptions, array());
675        $source = TbArray::popValue('source', $htmlOptions);
676        $sourceOptions = TbArray::popValue('sourceOptions', $htmlOptions, array());
677        $cite = TbArray::popValue('cite', $htmlOptions);
678        $citeOptions = TbArray::popValue('citeOptions', $htmlOptions, array());
679        $cite = isset($cite) ? ' ' . self::tag('cite', $citeOptions, $cite) : '';
680        $source = isset($source) ? self::tag('small', $sourceOptions, $source . $cite) : '';
681        $text = self::tag('p', $paragraphOptions, $text) . $source;
682        return self::tag('blockquote', $htmlOptions, $text);
683    }
684
685    /**
686     * Generates a help text.
687     * @param string $text the help text.
688     * @param array $htmlOptions additional HTML attributes.
689     * @return string the generated text.
690     */
691    public static function help($text, $htmlOptions = array())
692    {
693        $type = TbArray::popValue('type', $htmlOptions, self::HELP_TYPE_BLOCK);
694        self::addCssClass('help-' . self::HELP_TYPE_BLOCK, $htmlOptions);
695        return self::tag($type === self::HELP_TYPE_INLINE ? 'span' : 'p', $htmlOptions, $text);
696    }
697
698    /**
699     * Generates a help block.
700     * @param string $text the help text.
701     * @param array $htmlOptions additional HTML attributes.
702     * @return string the generated block.
703     * @deprecated
704     */
705    public static function helpBlock($text, $htmlOptions = array())
706    {
707        // todo: remove this as it is no longer valid for bs3
708        $htmlOptions['type'] = self::HELP_TYPE_BLOCK;
709        return self::help($text, $htmlOptions);
710    }
711
712    // Code
713    // http://getbootstrap.com/css/#code
714    // --------------------------------------------------
715
716    /**
717     * Generates inline code.
718     * @param string $code the code.
719     * @param array $htmlOptions additional HTML attributes.
720     * @return string the generated code.
721     */
722    public static function code($code, $htmlOptions = array())
723    {
724        return self::tag('code', $htmlOptions, self::encode($code));
725    }
726
727    /**
728     * Generates a code block.
729     * @param string $code the code.
730     * @param array $htmlOptions additional HTML attributes.
731     * @return string the generated block.
732     */
733    public static function codeBlock($code, $htmlOptions = array())
734    {
735        return self::tag('pre', $htmlOptions, self::encode($code));
736    }
737
738    /**
739     * Generates an HTML element.
740     * @param string $tag the tag name.
741     * @param array $htmlOptions the element attributes.
742     * @param mixed $content the content to be enclosed between open and close element tags.
743     * @param boolean $closeTag whether to generate the close tag.
744     * @return string the generated HTML element tag.
745     */
746    public static function tag($tag, $htmlOptions = array(), $content = false, $closeTag = true)
747    {
748        self::addSpanClass($htmlOptions);
749        self::addColClass($htmlOptions);
750        self::addPullClass($htmlOptions);
751        self::addTextAlignClass($htmlOptions);
752        return parent::tag($tag, $htmlOptions, $content, $closeTag);
753    }
754
755    /**
756     * Generates an open HTML element.
757     * @param string $tag the tag name.
758     * @param array $htmlOptions the element attributes.
759     * @return string the generated HTML element tag.
760     */
761    public static function openTag($tag, $htmlOptions = array())
762    {
763        return self::tag($tag, $htmlOptions, false, false);
764    }
765
766    // Tables
767    // http://getbootstrap.com/css/#tables
768    // --------------------------------------------------
769
770    // todo: create table methods here.
771
772    // Forms
773    // http://getbootstrap.com/css/#forms
774    // --------------------------------------------------
775
776    /**
777     * Generates a form tag.
778     * @param string $layout the form layout.
779     * @param string $action the form action URL.
780     * @param string $method form method (e.g. post, get).
781     * @param array $htmlOptions additional HTML attributes.
782     * @return string the generated tag.
783     */
784    public static function formTb(
785        $layout = self::FORM_LAYOUT_VERTICAL,
786        $action = '',
787        $method = 'post',
788        $htmlOptions = array()
789    ) {
790        return self::beginFormTb($layout, $action, $method, $htmlOptions);
791    }
792
793    /**
794     * Generates an open form tag.
795     * @param string $layout the form layout.
796     * @param string $action the form action URL.
797     * @param string $method form method (e.g. post, get).
798     * @param array $htmlOptions additional HTML attributes.
799     * @return string the generated tag.
800     */
801    public static function beginFormTb(
802        $layout = self::FORM_LAYOUT_VERTICAL,
803        $action = '',
804        $method = 'post',
805        $htmlOptions = array()
806    ) {
807        if (!empty($layout)) {
808            // refactor to not use a switch
809            switch ($layout) {
810                case self::FORM_LAYOUT_HORIZONTAL:
811                    self::addCssClass('form-' . self::FORM_LAYOUT_HORIZONTAL, $htmlOptions);
812                    break;
813                case self::FORM_LAYOUT_INLINE:
814                case self::FORM_LAYOUT_SEARCH:
815                    self::addCssClass('form-' . self::FORM_LAYOUT_INLINE, $htmlOptions);
816                    break;
817                default:
818                    self::addCssClass('form-' . $layout, $htmlOptions);
819            }
820        }
821        return parent::beginForm($action, $method, $htmlOptions);
822    }
823
824    /**
825     * Generates a stateful form tag.
826     * @param string string $layout
827     * @param mixed $action the form action URL.
828     * @param string $method form method (e.g. post, get).
829     * @param array $htmlOptions additional HTML attributes.
830     * @return string the generated form tag.
831     */
832    public static function statefulFormTb(
833        $layout = self::FORM_LAYOUT_VERTICAL,
834        $action = '',
835        $method = 'post',
836        $htmlOptions = array()
837    ) {
838        return self::formTb($layout, $action, $method, $htmlOptions)
839        . self::tag('div', array('style' => 'display: none'), parent::pageStateField(''));
840    }
841
842    /**
843     * Generates a text field input.
844     * @param string $name the input name.
845     * @param string $value the input value.
846     * @param array $htmlOptions additional HTML attributes.
847     * @return string the generated input field.
848     * @see self::textInputField
849     */
850    public static function textField($name, $value = '', $htmlOptions = array())
851    {
852        return self::textInputField('text', $name, $value, $htmlOptions);
853    }
854
855    /**
856     * Generates a password field input.
857     * @param string $name the input name.
858     * @param string $value the input value.
859     * @param array $htmlOptions additional HTML attributes.
860     * @return string the generated input field.
861     * @see self::textInputField
862     */
863    public static function passwordField($name, $value = '', $htmlOptions = array())
864    {
865        return self::textInputField('password', $name, $value, $htmlOptions);
866    }
867
868    /**
869     * Generates an url field input.
870     * @param string $name the input name.
871     * @param string $value the input value.
872     * @param array $htmlOptions additional HTML attributes.
873     * @return string the generated input field.
874     * @see self::textInputField
875     */
876    public static function urlField($name, $value = '', $htmlOptions = array())
877    {
878        return self::textInputField('url', $name, $value, $htmlOptions);
879    }
880
881    /**
882     * Generates an email field input.
883     * @param string $name the input name.
884     * @param string $value the input value.
885     * @param array $htmlOptions additional HTML attributes.
886     * @return string the generated input field.
887     * @see self::textInputField
888     */
889    public static function emailField($name, $value = '', $htmlOptions = array())
890    {
891        return self::textInputField('email', $name, $value, $htmlOptions);
892    }
893
894    /**
895     * Generates a number field input.
896     * @param string $name the input name.
897     * @param string $value the input value.
898     * @param array $htmlOptions additional HTML attributes.
899     * @return string the generated input field.
900     * @see self::textInputField
901     */
902    public static function numberField($name, $value = '', $htmlOptions = array())
903    {
904        return self::textInputField('number', $name, $value, $htmlOptions);
905    }
906
907    /**
908     * Generates a range field input.
909     * @param string $name the input name.
910     * @param string $value the input value.
911     * @param array $htmlOptions additional HTML attributes.
912     * @return string the generated input field.
913     * @see self::textInputField
914     */
915    public static function rangeField($name, $value = '', $htmlOptions = array())
916    {
917        return self::textInputField('range', $name, $value, $htmlOptions);
918    }
919
920    /**
921     * Generates a date field input.
922     * @param string $name the input name.
923     * @param string $value the input value.
924     * @param array $htmlOptions additional HTML attributes.
925     * @return string the generated input field.
926     * @see self::textInputField
927     */
928    public static function dateField($name, $value = '', $htmlOptions = array())
929    {
930        return self::textInputField('date', $name, $value, $htmlOptions);
931    }
932
933    /**
934     * Generates a file field input.
935     * @param string $name the input name.
936     * @param string $value the input value.
937     * @param array $htmlOptions additional HTML attributes.
938     * @return string the generated input field.
939     * @see CHtml::fileField
940     */
941    public static function fileField($name, $value = '', $htmlOptions = array())
942    {
943        return parent::fileField($name, $value, $htmlOptions);
944    }
945
946    /**
947     * Generates a text area input.
948     * @param string $name the input name.
949     * @param string $value the input value.
950     * @param array $htmlOptions additional HTML attributes.
951     * @return string the generated text area.
952     */
953    public static function textArea($name, $value = '', $htmlOptions = array())
954    {
955        // In case we do need to create a div container for the text area
956        $containerOptions = array();
957
958        // Get the intended input width before the rest of the options are normalized
959        self::addSpanClass($htmlOptions);
960        self::addColClass($htmlOptions);
961        $col = self::popColClasses($htmlOptions);
962
963        $htmlOptions = self::normalizeInputOptions($htmlOptions);
964        self::addCssClass('form-control', $htmlOptions);
965
966        $output = '';
967        if (!empty($col)) {
968            self::addCssClass($col, $containerOptions);
969            $output .= self::openTag('div', $containerOptions);
970        }
971        $output .= parent::textArea($name, $value, $htmlOptions);
972        if (!empty($col)) {
973            $output .= '</div>';
974        }
975        return $output;
976    }
977
978    /**
979     * Generates a radio button.
980     * @param string $name the input name.
981     * @param boolean $checked whether the radio button is checked.
982     * @param array $htmlOptions additional HTML attributes.
983     * @return string the generated radio button.
984     */
985    public static function radioButton($name, $checked = false, $htmlOptions = array())
986    {
987        $label = TbArray::popValue('label', $htmlOptions, false);
988        $labelOptions = TbArray::popValue('labelOptions', $htmlOptions, array());
989        $input = parent::radioButton($name, $checked, $htmlOptions);
990        // todo: refactor to make a single call to createCheckBoxAndRadioButtonLabel
991        if (TbArray::popValue('useContainer', $htmlOptions, false)) {
992            return self::tag(
993                'div',
994                array('class' => 'radio'),
995                self::createCheckBoxAndRadioButtonLabel($label, $input, $labelOptions)
996            );
997        } else {
998            return self::createCheckBoxAndRadioButtonLabel($label, $input, $labelOptions);
999        }
1000    }
1001
1002    /**
1003     * Generates a check box.
1004     * @param string $name the input name.
1005     * @param boolean $checked whether the check box is checked.
1006     * @param array $htmlOptions additional HTML attributes.
1007     * @return string the generated check box.
1008     */
1009    public static function checkBox($name, $checked = false, $htmlOptions = array())
1010    {
1011        $label = TbArray::popValue('label', $htmlOptions, false);
1012        $labelOptions = TbArray::popValue('labelOptions', $htmlOptions, array());
1013        $input = parent::checkBox($name, $checked, $htmlOptions);
1014        // todo: refactor to make a single call to createCheckBoxAndRadioButtonLabel
1015        if (TbArray::popValue('useContainer', $htmlOptions, false)) {
1016            return self::tag(
1017                'div',
1018                array('class' => 'checkbox'),
1019                self::createCheckBoxAndRadioButtonLabel($label, $input, $labelOptions)
1020            );
1021        } else {
1022            return self::createCheckBoxAndRadioButtonLabel($label, $input, $labelOptions);
1023        }
1024    }
1025
1026    /**
1027     * Generates a drop down list.
1028     * @param string $name the input name.
1029     * @param string $select the selected value.
1030     * @param array $data data for generating the list options (value=>display).
1031     * @param array $htmlOptions
1032     * @return string the generated drop down list.
1033     */
1034    public static function dropDownList($name, $select, $data, $htmlOptions = array())
1035    {
1036        $displaySize = TbArray::popValue('displaySize', $htmlOptions);
1037
1038        // In case we do need to create a div container for the input element (i.e. has addon or defined col)
1039        $containerOptions = array();
1040
1041        // Get the intended input width before the rest of the options are normalized
1042        self::addSpanClass($htmlOptions);
1043        self::addColClass($htmlOptions);
1044        $col = self::popColClasses($htmlOptions);
1045
1046        $htmlOptions = self::normalizeInputOptions($htmlOptions);
1047        self::addCssClass('form-control', $htmlOptions);
1048        if (!empty($displaySize)) {
1049            $htmlOptions['size'] = $displaySize;
1050        }
1051
1052        if (!empty($col)) {
1053            self::addCssClass($col, $containerOptions);
1054        }
1055
1056        $output = '';
1057
1058        if (!empty($containerOptions)) {
1059            $output .= self::openTag('div', $containerOptions);
1060        }
1061        $output .= parent::dropDownList($name, $select, $data, $htmlOptions);
1062
1063        if (!empty($containerOptions)) {
1064            $output .= '</div>';
1065        }
1066
1067        return $output;
1068    }
1069
1070    /**
1071     * Generates a list box.
1072     * @param string $name the input name.
1073     * @param mixed $select the selected value(s).
1074     * @param array $data data for generating the list options (value=>display).
1075     * @param array $htmlOptions additional HTML attributes.
1076     * @return string the generated list box
1077     */
1078    public static function listBox($name, $select, $data, $htmlOptions = array())
1079    {
1080        if (isset($htmlOptions['multiple'])) {
1081            if (substr($name, -2) !== '[]') {
1082                $name .= '[]';
1083            }
1084        }
1085        TbArray::defaultValue('displaySize', 4, $htmlOptions);
1086        return self::dropDownList($name, $select, $data, $htmlOptions);
1087    }
1088
1089    /**
1090     * Generates a radio button list.
1091     * @param string $name name of the radio button list.
1092     * @param mixed $select selection of the radio buttons.
1093     * @param array $data $data value-label pairs used to generate the radio button list.
1094     * @param array $htmlOptions additional HTML attributes.
1095     * @return string the generated list.
1096     */
1097    public static function radioButtonList($name, $select, $data, $htmlOptions = array())
1098    {
1099        $inline = TbArray::popValue('inline', $htmlOptions, false);
1100        $separator = TbArray::popValue('separator', $htmlOptions, ' ');
1101        $container = TbArray::popValue('container', $htmlOptions, 'div');
1102        $containerOptions = TbArray::popValue('containerOptions', $htmlOptions, array());
1103        $labelOptions = TbArray::popValue('labelOptions', $htmlOptions, array());
1104        $empty = TbArray::popValue('empty', $htmlOptions);
1105        if (isset($empty)) {
1106            $empty = !is_array($empty) ? array('' => $empty) : $empty;
1107            $data = TbArray::merge($empty, $data);
1108        }
1109
1110        $items = array();
1111        $baseID = $containerOptions['id'] = TbArray::popValue('baseID', $htmlOptions, parent::getIdByName($name));
1112
1113        $id = 0;
1114        foreach ($data as $value => $label) {
1115            $checked = !strcmp($value, $select);
1116            $htmlOptions['value'] = $value;
1117            $htmlOptions['id'] = $baseID . '_' . $id++;
1118            if ($inline) {
1119                $htmlOptions['label'] = $label;
1120                self::addCssClass('radio-inline', $labelOptions);
1121                $htmlOptions['labelOptions'] = $labelOptions;
1122                $items[] = self::radioButton($name, $checked, $htmlOptions);
1123            } else {
1124                $option = self::radioButton($name, $checked, $htmlOptions);
1125                $items[] = self::tag(
1126                    'div',
1127                    array('class' => 'radio'),
1128                    self::label($option . ' ' . $label, false, $labelOptions)
1129                );
1130            }
1131        }
1132
1133        $inputs = implode($separator, $items);
1134        return !empty($container) ? self::tag($container, $containerOptions, $inputs) : $inputs;
1135    }
1136
1137    /**
1138     * Generates an inline radio button list.
1139     * @param string $name name of the radio button list.
1140     * @param mixed $select selection of the radio buttons.
1141     * @param array $data $data value-label pairs used to generate the radio button list.
1142     * @param array $htmlOptions additional HTML attributes.
1143     * @return string the generated list.
1144     */
1145    public static function inlineRadioButtonList($name, $select, $data, $htmlOptions = array())
1146    {
1147        $htmlOptions['inline'] = true;
1148        return self::radioButtonList($name, $select, $data, $htmlOptions);
1149    }
1150
1151    /**
1152     * Generates a check box list.
1153     * @param string $name name of the check box list.
1154     * @param mixed $select selection of the check boxes.
1155     * @param array $data $data value-label pairs used to generate the check box list.
1156     * @param array $htmlOptions additional HTML attributes.
1157     * @return string the generated list.
1158     */
1159    public static function checkBoxList($name, $select, $data, $htmlOptions = array())
1160    {
1161        $inline = TbArray::popValue('inline', $htmlOptions, false);
1162        $separator = TbArray::popValue('separator', $htmlOptions, ' ');
1163        $container = TbArray::popValue('container', $htmlOptions, 'div');
1164        $containerOptions = TbArray::popValue('containerOptions', $htmlOptions, array());
1165
1166        $labelOptions = TbArray::popValue('labelOptions', $htmlOptions, array());
1167
1168        if (substr($name, -2) !== '[]') {
1169            $name .= '[]';
1170        }
1171
1172        $checkAll = TbArray::popValue('checkAll', $htmlOptions);
1173        $checkAllLast = TbArray::popValue('checkAllLast', $htmlOptions);
1174        if ($checkAll !== null) {
1175            $checkAllLabel = $checkAll;
1176            $checkAllLast = $checkAllLast !== null;
1177        }
1178
1179        $items = array();
1180        $baseID = $containerOptions['id'] = TbArray::popValue('baseID', $htmlOptions, parent::getIdByName($name));
1181        $id = 0;
1182        $checkAll = true;
1183
1184        foreach ($data as $value => $label) {
1185            $checked = !is_array($select) && !strcmp($value, $select) || is_array($select) && in_array($value, $select);
1186            $checkAll = $checkAll && $checked;
1187            $htmlOptions['value'] = $value;
1188            $htmlOptions['id'] = $baseID . '_' . $id++;
1189            if ($inline) {
1190                $htmlOptions['label'] = $label;
1191                self::addCssClass('checkbox-inline', $labelOptions);
1192                $htmlOptions['labelOptions'] = $labelOptions;
1193                $items[] = self::checkBox($name, $checked, $htmlOptions);
1194            } else {
1195                $option = self::checkBox($name, $checked, $htmlOptions);
1196                $items[] = self::tag(
1197                    'div',
1198                    array('class' => 'checkbox'),
1199                    self::label($option . ' ' . $label, false, $labelOptions)
1200                );
1201            }
1202        }
1203
1204        if (isset($checkAllLabel)) {
1205            $htmlOptions['value'] = 1;
1206            $htmlOptions['id'] = $id = $baseID . '_all';
1207            $htmlOptions['label'] = $checkAllLabel;
1208            $htmlOptions['labelOptions'] = $labelOptions;
1209            $item = self::checkBox($id, $checkAll, $htmlOptions);
1210            if ($inline) {
1211                self::addCssClass('checkbox-inline', $labelOptions);
1212            } else {
1213                $item = self::checkBox($id, $checkAll, $htmlOptions);
1214                $item = self::tag(
1215                    'div',
1216                    array('class' => 'checkbox'),
1217                    $item
1218                );
1219            }
1220            if ($checkAllLast) {
1221                $items[] = $item;
1222            } else {
1223                array_unshift($items, $item);
1224            }
1225            $name = strtr($name, array('[' => '\\[', ']' => '\\]'));
1226            $js = <<<EOD
1227jQuery('#$id').on('click', function() {
1228	jQuery("input[name='$name']").prop('checked', this.checked);
1229});
1230jQuery("input[name='$name']").on('click', function() {
1231	jQuery('#$id').prop('checked', !jQuery("input[name='$name']:not(:checked)").length);
1232});
1233jQuery('#$id').prop('checked', !jQuery("input[name='$name']:not(:checked)").length);
1234EOD;
1235            $cs = Yii::app()->getClientScript();
1236            $cs->registerCoreScript('jquery');
1237            $cs->registerScript($id, $js);
1238        }
1239
1240        $inputs = implode($separator, $items);
1241        return !empty($container) ? self::tag($container, $containerOptions, $inputs) : $inputs;
1242    }
1243
1244    /**
1245     * Generates an inline check box list.
1246     * @param string $name name of the check box list.
1247     * @param mixed $select selection of the check boxes.
1248     * @param array $data $data value-label pairs used to generate the check box list.
1249     * @param array $htmlOptions additional HTML attributes.
1250     * @return string the generated list.
1251     */
1252    public static function inlineCheckBoxList($name, $select, $data, $htmlOptions = array())
1253    {
1254        $htmlOptions['inline'] = true;
1255        return self::checkBoxList($name, $select, $data, $htmlOptions);
1256    }
1257
1258    /**
1259     * Generates an uneditable input.
1260     * @param string $value the value.
1261     * @param array $htmlOptions additional HTML attributes.
1262     * @return string the generated input.
1263     */
1264    public static function uneditableField($value, $htmlOptions = array())
1265    {
1266        self::addCssClass('uneditable-input', $htmlOptions);
1267        $htmlOptions = self::normalizeInputOptions($htmlOptions);
1268        return self::tag('span', $htmlOptions, $value);
1269    }
1270
1271    /**
1272     * Generates a search input.
1273     * @param string $name the input name.
1274     * @param string $value the input value.
1275     * @param array $htmlOptions additional HTML attributes.
1276     * @return string the generated input.
1277     */
1278    public static function searchQueryField($name, $value = '', $htmlOptions = array())
1279    {
1280        return self::textInputField('search', $name, $value, $htmlOptions);
1281    }
1282
1283    /**
1284     * Generates a control group with a text field.
1285     * @param string $name the input name.
1286     * @param string $value the input value.
1287     * @param array $htmlOptions additional HTML attributes.
1288     * @return string the generated control group.
1289     * @see self::controlGroup
1290     */
1291    public static function textFieldControlGroup($name, $value = '', $htmlOptions = array())
1292    {
1293        return self::controlGroup(self::INPUT_TYPE_TEXT, $name, $value, $htmlOptions);
1294    }
1295
1296    /**
1297     * Generates a control group with a password field.
1298     * @param string $name the input name.
1299     * @param string $value the input value.
1300     * @param array $htmlOptions additional HTML attributes.
1301     * @return string the generated control group.
1302     * @see self::textInputField
1303     */
1304    public static function passwordFieldControlGroup($name, $value = '', $htmlOptions = array())
1305    {
1306        return self::controlGroup(self::INPUT_TYPE_PASSWORD, $name, $value, $htmlOptions);
1307    }
1308
1309    /**
1310     * Generates a control group with an url field.
1311     * @param string $name the input name.
1312     * @param string $value the input value.
1313     * @param array $htmlOptions additional HTML attributes.
1314     * @return string the generated control group.
1315     * @see self::controlGroup
1316     */
1317    public static function urlFieldControlGroup($name, $value = '', $htmlOptions = array())
1318    {
1319        return self::controlGroup(self::INPUT_TYPE_URL, $name, $value, $htmlOptions);
1320    }
1321
1322    /**
1323     * Generates a control group with an email field.
1324     * @param string $name the input name.
1325     * @param string $value the input value.
1326     * @param array $htmlOptions additional HTML attributes.
1327     * @return string the generated control group.
1328     * @see self::controlGroup
1329     */
1330    public static function emailFieldControlGroup($name, $value = '', $htmlOptions = array())
1331    {
1332        return self::controlGroup(self::INPUT_TYPE_EMAIL, $name, $value, $htmlOptions);
1333    }
1334
1335    /**
1336     * Generates a control group with a number field.
1337     * @param string $name the input name.
1338     * @param string $value the input value.
1339     * @param array $htmlOptions additional HTML attributes.
1340     * @return string the generated control group.
1341     * @see self::textInputField
1342     */
1343    public static function numberFieldControlGroup($name, $value = '', $htmlOptions = array())
1344    {
1345        return self::controlGroup(self::INPUT_TYPE_NUMBER, $name, $value, $htmlOptions);
1346    }
1347
1348    /**
1349     * Generates a control group with a range field.
1350     * @param string $name the input name
1351     * @param string $value the input value
1352     * @param array $htmlOptions additional HTML attributes.
1353     * @return string the generated control group.
1354     * @see self::controlGroup
1355     */
1356    public static function rangeFieldControlGroup($name, $value = '', $htmlOptions = array())
1357    {
1358        return self::controlGroup(self::INPUT_TYPE_RANGE, $name, $value, $htmlOptions);
1359    }
1360
1361    /**
1362     * Generates a control group with a file field.
1363     * @param string $name the input name.
1364     * @param string $value the input value.
1365     * @param array $htmlOptions additional HTML attributes.
1366     * @return string the generated control group.
1367     * @see self::controlGroup
1368     */
1369    public static function dateFieldControlGroup($name, $value = '', $htmlOptions = array())
1370    {
1371        return self::controlGroup(self::INPUT_TYPE_DATE, $name, $value, $htmlOptions);
1372    }
1373
1374    /**
1375     * Generates a control group with a text area.
1376     * @param string $name the input name.
1377     * @param string $value the input value.
1378     * @param array $htmlOptions additional HTML attributes.
1379     * @return string the generated control group.
1380     * @see self::controlGroup
1381     */
1382    public static function textAreaControlGroup($name, $value = '', $htmlOptions = array())
1383    {
1384        return self::controlGroup(self::INPUT_TYPE_TEXTAREA, $name, $value, $htmlOptions);
1385    }
1386
1387    /**
1388     * Generates a control group with a file field.
1389     * @param string $name the input name.
1390     * @param string $value the input value.
1391     * @param array $htmlOptions additional HTML attributes.
1392     * @return string the generated control group.
1393     * @see self::controlGroup
1394     */
1395    public static function fileFieldControlGroup($name, $value = '', $htmlOptions = array())
1396    {
1397        return self::controlGroup(self::INPUT_TYPE_FILE, $name, $value, $htmlOptions);
1398    }
1399
1400    /**
1401     * Generates a control group with a radio button.
1402     * @param string $name the input name.
1403     * @param bool|string $checked whether the radio button is checked.
1404     * @param array $htmlOptions additional HTML attributes.
1405     * @return string the generated control group.
1406     * @see self::controlGroup
1407     */
1408    public static function radioButtonControlGroup($name, $checked = false, $htmlOptions = array())
1409    {
1410        return self::controlGroup(self::INPUT_TYPE_RADIOBUTTON, $name, $checked, $htmlOptions);
1411    }
1412
1413    /**
1414     * Generates a control group with a check box.
1415     * @param string $name the input name.
1416     * @param bool|string $checked whether the check box is checked.
1417     * @param array $htmlOptions additional HTML attributes.
1418     * @return string the generated control group.
1419     * @see self::controlGroup
1420     */
1421    public static function checkBoxControlGroup($name, $checked = false, $htmlOptions = array())
1422    {
1423        return self::controlGroup(self::INPUT_TYPE_CHECKBOX, $name, $checked, $htmlOptions);
1424    }
1425
1426    /**
1427     * Generates a control group with a drop down list.
1428     * @param string $name the input name.
1429     * @param string $select the selected value.
1430     * @param array $data data for generating the list options (value=>display).
1431     * @param array $htmlOptions additional HTML attributes.
1432     * @return string the generated control group.
1433     * @see self::controlGroup
1434     */
1435    public static function dropDownListControlGroup($name, $select = '', $data = array(), $htmlOptions = array())
1436    {
1437        return self::controlGroup(self::INPUT_TYPE_DROPDOWNLIST, $name, $select, $htmlOptions, $data);
1438    }
1439
1440    /**
1441     * Generates a control group with a list box.
1442     * @param string $name the input name.
1443     * @param string $select the selected value.
1444     * @param array $data data for generating the list options (value=>display).
1445     * @param array $htmlOptions additional HTML attributes.
1446     * @return string the generated control group.
1447     * @see self::controlGroup
1448     */
1449    public static function listBoxControlGroup($name, $select = '', $data = array(), $htmlOptions = array())
1450    {
1451        return self::controlGroup(self::INPUT_TYPE_LISTBOX, $name, $select, $htmlOptions, $data);
1452    }
1453
1454    /**
1455     * Generates a control group with a radio button list.
1456     * @param string $name the input name.
1457     * @param string $select the selected value.
1458     * @param array $data data for generating the list options (value=>display).
1459     * @param array $htmlOptions additional HTML attributes.
1460     * @return string the generated control group.
1461     * @see self::controlGroup
1462     */
1463    public static function radioButtonListControlGroup($name, $select = '', $data = array(), $htmlOptions = array())
1464    {
1465        return self::controlGroup(self::INPUT_TYPE_RADIOBUTTONLIST, $name, $select, $htmlOptions, $data);
1466    }
1467
1468    /**
1469     * Generates a control group with an inline radio button list.
1470     * @param string $name the input name.
1471     * @param string $select the selected value.
1472     * @param array $data data for generating the list options (value=>display).
1473     * @param array $htmlOptions additional HTML attributes.
1474     * @return string the generated control group.
1475     * @see self::controlGroup
1476     */
1477    public static function inlineRadioButtonListControlGroup(
1478        $name,
1479        $select = '',
1480        $data = array(),
1481        $htmlOptions = array()
1482    ) {
1483        return self::controlGroup(self::INPUT_TYPE_INLINERADIOBUTTONLIST, $name, $select, $htmlOptions, $data);
1484    }
1485
1486    /**
1487     * Generates a control group with a check box list.
1488     * @param string $name the input name.
1489     * @param string $select the selected value.
1490     * @param array $data data for generating the list options (value=>display).
1491     * @param array $htmlOptions additional HTML attributes.
1492     * @return string the generated control group.
1493     * @see self::controlGroup
1494     */
1495    public static function checkBoxListControlGroup($name, $select = '', $data = array(), $htmlOptions = array())
1496    {
1497        return self::controlGroup(self::INPUT_TYPE_CHECKBOXLIST, $name, $select, $htmlOptions, $data);
1498    }
1499
1500    /**
1501     * Generates a control group with an inline check box list.
1502     * @param string $name the input name.
1503     * @param string $select the selected value.
1504     * @param array $data data for generating the list options (value=>display).
1505     * @param array $htmlOptions additional HTML attributes.
1506     * @return string the generated control group.
1507     * @see self::controlGroup
1508     */
1509    public static function inlineCheckBoxListControlGroup($name, $select = '', $data = array(), $htmlOptions = array())
1510    {
1511        return self::controlGroup(self::INPUT_TYPE_INLINECHECKBOXLIST, $name, $select, $htmlOptions, $data);
1512    }
1513
1514    /**
1515     * Generates a control group with an uneditable field.
1516     * @param string $value
1517     * @param array $htmlOptions additional HTML attributes.
1518     * @return string the generated control group.
1519     * @see self::controlGroup
1520     */
1521    public static function uneditableFieldControlGroup($value = '', $htmlOptions = array())
1522    {
1523        return self::controlGroup(self::INPUT_TYPE_UNEDITABLE, '', $value, $htmlOptions);
1524    }
1525
1526    /**
1527     * Generates a control group with a search field.
1528     * @param string $name the input name.
1529     * @param string $value
1530     * @param array $htmlOptions additional HTML attributes.
1531     * @return string the generated control group.
1532     * @see self::controlGroup
1533     */
1534    public static function searchQueryControlGroup($name, $value = '', $htmlOptions = array())
1535    {
1536        return self::controlGroup(self::INPUT_TYPE_SEARCH, $name, $value, $htmlOptions);
1537    }
1538
1539    /**
1540     * Generates a form group.
1541     * @param string $type the input type.
1542     * @param string $name the input name.
1543     * @param string $value the input value.
1544     * @param array $htmlOptions additional HTML attributes.
1545     * @param array $data data for multiple select inputs.
1546     * @return string the generated control group.
1547     */
1548    public static function controlGroup($type, $name, $value = '', $htmlOptions = array(), $data = array())
1549    {
1550        $color = TbArray::popValue('color', $htmlOptions);
1551        $groupOptions = TbArray::popValue('groupOptions', $htmlOptions, array());
1552        $controlOptions = TbArray::popValue('controlOptions', $htmlOptions, array());
1553        $label = TbArray::popValue('label', $htmlOptions);
1554        $labelOptions = TbArray::popValue('labelOptions', $htmlOptions, array());
1555
1556        // todo: remove everything that has to do with form layouts.
1557        $formLayout = TbArray::popValue('formLayout', $htmlOptions, self::FORM_LAYOUT_VERTICAL);
1558        $labelWidthClass = TbArray::popValue('labelWidthClass', $htmlOptions, self::$defaultFormLabelWidthClass);
1559        // Retrieve the old-style "span" option
1560        $span = TbArray::popValue('span', $htmlOptions);
1561        if (!empty($span)) {
1562            $controlWidthClass = 'col-md-' . $span;
1563        } else {
1564            $controlWidthClass = TbArray::popValue('controlWidthClass', $htmlOptions, self::$defaultFormControlWidthClass);
1565        }
1566        $useFormGroup = true;
1567        $useControls = true;
1568        $output = '';
1569
1570        // Special label case case for individual checkboxes and radios
1571        if ($type == self::INPUT_TYPE_CHECKBOX || $type == self::INPUT_TYPE_RADIOBUTTON) {
1572            $htmlOptions['label'] = $label;
1573            $htmlOptions['labelOptions'] = $labelOptions;
1574            $htmlOptions['useContainer'] = true;
1575            $label = false;
1576            $useFormGroup = false;
1577        }
1578
1579        // Special conditions depending on the form type
1580        if ($formLayout == self::FORM_LAYOUT_HORIZONTAL) {
1581            switch ($type) {
1582                case self::INPUT_TYPE_CHECKBOX:
1583                case self::INPUT_TYPE_RADIOBUTTON:
1584                    self::addCssClass(self::switchColToOffset($labelWidthClass), $controlOptions);
1585                    self::addCssClass(self::switchOffsetToCol($controlWidthClass), $controlOptions);
1586                    $useFormGroup = true;
1587                    break;
1588                default:
1589                    self::addCssClass(self::switchOffsetToCol($labelWidthClass), $labelOptions);
1590                    self::addCssClass(self::switchOffsetToCol($controlWidthClass), $controlOptions);
1591            }
1592        } elseif ($formLayout == self::FORM_LAYOUT_INLINE || $formLayout == self::FORM_LAYOUT_SEARCH) {
1593            switch ($type) {
1594                case self::INPUT_TYPE_TEXT:
1595                case self::INPUT_TYPE_PASSWORD:
1596                case self::INPUT_TYPE_URL:
1597                case self::INPUT_TYPE_EMAIL:
1598                case self::INPUT_TYPE_NUMBER:
1599                case self::INPUT_TYPE_RANGE:
1600                case self::INPUT_TYPE_DATE:
1601                case self::INPUT_TYPE_FILE:
1602                case self::INPUT_TYPE_SEARCH:
1603                    self::addCssClass('sr-only', $labelOptions);
1604                    if (($label !== null) && (TbArray::getValue('placeholder', $htmlOptions) !== null)) {
1605                        $htmlOptions['placeholder'] = $label;
1606                    }
1607                    break;
1608                case self::INPUT_TYPE_CHECKBOX:
1609                case self::INPUT_TYPE_RADIOBUTTON:
1610                    $useControls = false;
1611                    break;
1612            }
1613        }
1614        // remove until here.
1615
1616        $help = TbArray::popValue('help', $htmlOptions, '');
1617        $helpOptions = TbArray::popValue('helpOptions', $htmlOptions, array());
1618        if (!empty($help)) {
1619            $help = self::inputHelp($help, $helpOptions);
1620        }
1621
1622        $input = isset($htmlOptions['input'])
1623            ? $htmlOptions['input']
1624            : self::createInput($type, $name, $value, $htmlOptions, $data);
1625
1626        if (!empty($color)) {
1627            self::addCssClass($color, $groupOptions);
1628        }
1629        self::addCssClass('control-label', $labelOptions);
1630        if ($label !== false) {
1631            $output .= parent::label($label, $name, $labelOptions);
1632        }
1633        if ($useControls) {
1634            $output .= self::controls($input . $help, $controlOptions);
1635        } else {
1636            $output .= $input;
1637        }
1638
1639        if ($useFormGroup) {
1640            self::addCssClass('form-group', $groupOptions);
1641            return self::tag(
1642                'div',
1643                $groupOptions,
1644                $output
1645            );
1646        } else {
1647            return $output;
1648        }
1649    }
1650
1651    /**
1652     * Generates a custom (pre-rendered) form control group.
1653     * @param string $input the rendered input.
1654     * @param string $name the input name.
1655     * @param array $htmlOptions additional HTML attributes.
1656     * @return string the generated control group.
1657     */
1658    public static function customControlGroup($input, $name, $htmlOptions = array())
1659    {
1660        $htmlOptions['input'] = $input;
1661        return self::controlGroup(self::INPUT_TYPE_CUSTOM, $name, '', $htmlOptions);
1662    }
1663
1664    /**
1665     * Creates a form input of the given type.
1666     * @param string $type the input type.
1667     * @param string $name the input name.
1668     * @param string $value the input value.
1669     * @param array $htmlOptions additional HTML attributes.
1670     * @param array $data data for multiple select inputs.
1671     * @return string the input.
1672     * @throws CException if the input type is invalid.
1673     */
1674    public static function createInput($type, $name, $value, $htmlOptions = array(), $data = array())
1675    {
1676        switch ($type) {
1677            case self::INPUT_TYPE_TEXT:
1678                return self::textField($name, $value, $htmlOptions);
1679            case self::INPUT_TYPE_PASSWORD:
1680                return self::passwordField($name, $value, $htmlOptions);
1681            case self::INPUT_TYPE_URL:
1682                return self::urlField($name, $value, $htmlOptions);
1683            case self::INPUT_TYPE_EMAIL:
1684                return self::emailField($name, $value, $htmlOptions);
1685            case self::INPUT_TYPE_NUMBER:
1686                return self::numberField($name, $value, $htmlOptions);
1687            case self::INPUT_TYPE_RANGE:
1688                return self::rangeField($name, $value, $htmlOptions);
1689            case self::INPUT_TYPE_DATE:
1690                return self::dateField($name, $value, $htmlOptions);
1691            case self::INPUT_TYPE_TEXTAREA:
1692                return self::textArea($name, $value, $htmlOptions);
1693            case self::INPUT_TYPE_FILE:
1694                return self::fileField($name, $value, $htmlOptions);
1695            case self::INPUT_TYPE_RADIOBUTTON:
1696                return self::radioButton($name, $value, $htmlOptions);
1697            case self::INPUT_TYPE_CHECKBOX:
1698                return self::checkBox($name, $value, $htmlOptions);
1699            case self::INPUT_TYPE_DROPDOWNLIST:
1700                return self::dropDownList($name, $value, $data, $htmlOptions);
1701            case self::INPUT_TYPE_LISTBOX:
1702                return self::listBox($name, $value, $data, $htmlOptions);
1703            case self::INPUT_TYPE_CHECKBOXLIST:
1704                return self::checkBoxList($name, $value, $data, $htmlOptions);
1705            case self::INPUT_TYPE_INLINECHECKBOXLIST:
1706                return self::inlineCheckBoxList($name, $value, $data, $htmlOptions);
1707            case self::INPUT_TYPE_RADIOBUTTONLIST:
1708                return self::radioButtonList($name, $value, $data, $htmlOptions);
1709            case self::INPUT_TYPE_INLINERADIOBUTTONLIST:
1710                return self::inlineRadioButtonList($name, $value, $data, $htmlOptions);
1711            case self::INPUT_TYPE_UNEDITABLE:
1712                return self::uneditableField($value, $htmlOptions);
1713            case self::INPUT_TYPE_SEARCH:
1714                return self::searchQueryField($name, $value, $htmlOptions);
1715            default:
1716                throw new CException('Invalid input type "' . $type . '".');
1717        }
1718    }
1719
1720    /**
1721     * Generates an input HTML tag.
1722     * This method generates an input HTML tag based on the given input name and value.
1723     * @param string $type the input type.
1724     * @param string $name the input name.
1725     * @param string $value the input value.
1726     * @param array $htmlOptions additional HTML attributes.
1727     * @return string the generated input tag.
1728     */
1729    protected static function textInputField($type, $name, $value, $htmlOptions)
1730    {
1731        parent::clientChange('change', $htmlOptions);
1732
1733        // In case we do need to create a div container for the input element (i.e. has addon or defined col)
1734        $containerOptions = array();
1735
1736        // Get the intended input width before the rest of the options are normalized
1737        self::addSpanClass($htmlOptions);
1738        self::addColClass($htmlOptions);
1739        $col = self::popColClasses($htmlOptions);
1740
1741        $htmlOptions = self::normalizeInputOptions($htmlOptions);
1742        self::addCssClass('form-control', $htmlOptions);
1743
1744        $addOnClass = self::getAddOnClasses($htmlOptions);
1745        $addOnOptions = TbArray::popValue('addOnOptions', $htmlOptions, array());
1746        self::addCssClass($addOnClass, $addOnOptions);
1747
1748        $prepend = TbArray::popValue('prepend', $htmlOptions, '');
1749        $prependOptions = TbArray::popValue('prependOptions', $htmlOptions, array());
1750        if (!empty($prepend)) {
1751            $prepend = self::inputAddOn($prepend, $prependOptions, 'prepend');
1752        }
1753
1754        $append = TbArray::popValue('append', $htmlOptions, '');
1755        $appendOptions = TbArray::popValue('appendOptions', $htmlOptions, array());
1756        if (!empty($append)) {
1757            $append = self::inputAddOn($append, $appendOptions, 'append');
1758        }
1759
1760        if (!empty($addOnClass)) {
1761            $containerOptions = $addOnOptions;
1762        }
1763
1764        if (!empty($col)) {
1765            self::addCssClass($col, $containerOptions);
1766        }
1767
1768        $output = '';
1769        if (!empty($containerOptions)) {
1770            $output .= self::openTag('div', $containerOptions);
1771        }
1772        $output .= $prepend . parent::inputField($type, $name, $value, $htmlOptions) . $append;
1773        if (!empty($containerOptions)) {
1774            $output .= '</div>';
1775        }
1776        return $output;
1777    }
1778
1779    /**
1780     * Generates a text field input for a model attribute.
1781     * @param CModel $model the data model.
1782     * @param string $attribute the attribute.
1783     * @param array $htmlOptions additional HTML attributes.
1784     * @return string the generated input field.
1785     * @see self::activeTextInputField
1786     */
1787    public static function activeTextField($model, $attribute, $htmlOptions = array())
1788    {
1789        return self::activeTextInputField('text', $model, $attribute, $htmlOptions);
1790    }
1791
1792    /**
1793     * Generates a password field input for a model attribute.
1794     * @param CModel $model the data model.
1795     * @param string $attribute the attribute.
1796     * @param array $htmlOptions additional HTML attributes.
1797     * @return string the generated input field.
1798     * @see self::activeTextInputField
1799     */
1800    public static function activePasswordField($model, $attribute, $htmlOptions = array())
1801    {
1802        return self::activeTextInputField('password', $model, $attribute, $htmlOptions);
1803    }
1804
1805    /**
1806     * Generates an url field input for a model attribute.
1807     * @param CModel $model the data model.
1808     * @param string $attribute the attribute.
1809     * @param array $htmlOptions additional HTML attributes.
1810     * @return string the generated input field.
1811     * @see self::activeTextInputField
1812     */
1813    public static function activeUrlField($model, $attribute, $htmlOptions = array())
1814    {
1815        return self::activeTextInputField('url', $model, $attribute, $htmlOptions);
1816    }
1817
1818    /**
1819     * Generates an email field input for a model attribute.
1820     * @param CModel $model the data model.
1821     * @param string $attribute the attribute.
1822     * @param array $htmlOptions additional HTML attributes.
1823     * @return string the generated input field.
1824     * @see self::activeTextInputField
1825     */
1826    public static function activeEmailField($model, $attribute, $htmlOptions = array())
1827    {
1828        return self::activeTextInputField('email', $model, $attribute, $htmlOptions);
1829    }
1830
1831    /**
1832     * Generates a number field input for a model attribute.
1833     * @param CModel $model the data model.
1834     * @param string $attribute the attribute.
1835     * @param array $htmlOptions additional HTML attributes.
1836     * @return string the generated input field.
1837     * @see self::activeTextInputField
1838     */
1839    public static function activeNumberField($model, $attribute, $htmlOptions = array())
1840    {
1841        return self::activeTextInputField('number', $model, $attribute, $htmlOptions);
1842    }
1843
1844    /**
1845     * Generates a range field input for a model attribute.
1846     * @param CModel $model the data model.
1847     * @param string $attribute the attribute.
1848     * @param array $htmlOptions additional HTML attributes.
1849     * @return string the generated input field.
1850     * @see self::activeTextInputField
1851     */
1852    public static function activeRangeField($model, $attribute, $htmlOptions = array())
1853    {
1854        return self::activeTextInputField('range', $model, $attribute, $htmlOptions);
1855    }
1856
1857    /**
1858     * Generates a date field input for a model attribute.
1859     * @param CModel $model the data model.
1860     * @param string $attribute the attribute.
1861     * @param array $htmlOptions additional HTML attributes.
1862     * @return string the generated input field.
1863     * @see self::activeTextInputField
1864     */
1865    public static function activeDateField($model, $attribute, $htmlOptions = array())
1866    {
1867        return self::activeTextInputField('date', $model, $attribute, $htmlOptions);
1868    }
1869
1870    /**
1871     * Generates a file field input for a model attribute.
1872     * @param CModel $model the data model.
1873     * @param string $attribute the attribute.
1874     * @param array $htmlOptions additional HTML attributes.
1875     * @return string the generated input field.
1876     * @see CHtml::activeFileField
1877     */
1878    public static function activeFileField($model, $attribute, $htmlOptions = array())
1879    {
1880        return parent::activeFileField($model, $attribute, $htmlOptions);
1881    }
1882
1883    /**
1884     * Generates a text area input for a model attribute.
1885     * @param CModel $model the data model.
1886     * @param string $attribute the attribute.
1887     * @param array $htmlOptions additional HTML attributes.
1888     * @return string the generated text area.
1889     */
1890    public static function activeTextArea($model, $attribute, $htmlOptions = array())
1891    {
1892        // In case we do need to create a div container for the text area
1893        $containerOptions = array();
1894
1895        // Get the intended input width before the rest of the options are normalized
1896        self::addSpanClass($htmlOptions);
1897        self::addColClass($htmlOptions);
1898        $col = self::popColClasses($htmlOptions);
1899
1900        $htmlOptions = self::normalizeInputOptions($htmlOptions);
1901        self::addCssClass('form-control', $htmlOptions);
1902
1903        $output = '';
1904        if (!empty($col)) {
1905            self::addCssClass($col, $containerOptions);
1906            $output .= self::openTag('div', $containerOptions);
1907        }
1908        $output .= parent::activeTextArea($model, $attribute, $htmlOptions);
1909        if (!empty($col)) {
1910            $output .= '</div>';
1911        }
1912
1913        return $output;
1914    }
1915
1916    /**
1917     * Generates a radio button for a model attribute.
1918     * @param CModel $model the data model.
1919     * @param string $attribute the attribute.
1920     * @param array $htmlOptions additional HTML attributes.
1921     * @return string the generated radio button.
1922     */
1923    public static function activeRadioButton($model, $attribute, $htmlOptions = array())
1924    {
1925        $label = TbArray::popValue('label', $htmlOptions, false);
1926        $labelOptions = TbArray::popValue('labelOptions', $htmlOptions, array());
1927        $input = parent::activeRadioButton($model, $attribute, $htmlOptions);
1928        if (TbArray::popValue('useContainer', $htmlOptions, false)) {
1929            return self::tag(
1930                'div',
1931                array('class' => 'radio'),
1932                self::createCheckBoxAndRadioButtonLabel($label, $input, $labelOptions)
1933            );
1934        } else {
1935            return self::createCheckBoxAndRadioButtonLabel($label, $input, $labelOptions);
1936        }
1937
1938    }
1939
1940    /**
1941     * Generates a check box for a model attribute.
1942     * @param CModel $model the data model.
1943     * @param string $attribute the attribute.
1944     * @param array $htmlOptions additional HTML attributes.
1945     * @return string the generated check box.
1946     */
1947    public static function activeCheckBox($model, $attribute, $htmlOptions = array())
1948    {
1949        $label = TbArray::popValue('label', $htmlOptions, false);
1950        $labelOptions = TbArray::popValue('labelOptions', $htmlOptions, array());
1951        $input = parent::activeCheckBox($model, $attribute, $htmlOptions);
1952        if (TbArray::popValue('useContainer', $htmlOptions, false)) {
1953            return self::tag(
1954                'div',
1955                array('class' => 'checkbox'),
1956                self::createCheckBoxAndRadioButtonLabel($label, $input, $labelOptions)
1957            );
1958        } else {
1959            return self::createCheckBoxAndRadioButtonLabel($label, $input, $labelOptions);
1960        }
1961    }
1962
1963    /**
1964     * Generates a label for a checkbox or radio input by wrapping the input.
1965     * @param string $label the label text.
1966     * @param string $input the input.
1967     * @param array $htmlOptions additional HTML attributes.
1968     * @return string the generated label.
1969     */
1970    protected static function createCheckBoxAndRadioButtonLabel($label, $input, $htmlOptions)
1971    {
1972        list ($hidden, $input) = self::normalizeCheckBoxAndRadio($input);
1973        return $hidden . ($label !== false
1974            ? self::tag('label', $htmlOptions, $input . ' ' . $label)
1975            : $input);
1976    }
1977
1978    /**
1979     * Normalizes the inputs in the given string by splitting them up into an array.
1980     * @param string $input the inputs.
1981     * @return array an array with the following structure: array($hidden, $input)
1982     */
1983    protected static function normalizeCheckBoxAndRadio($input)
1984    {
1985        $parts = preg_split("/(<.*?>)/", $input, 2, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
1986        if (isset($parts[1])) {
1987            return $parts;
1988        } else {
1989            return array('', $parts[0]);
1990        }
1991    }
1992
1993    /**
1994     * Generates a drop down list for a model attribute.
1995     * @param CModel $model the data model.
1996     * @param string $attribute the attribute.
1997     * @param array $data data for generating the list options (value=>display).
1998     * @param array $htmlOptions additional HTML attributes
1999     * @return string the generated drop down list.
2000     */
2001    public static function activeDropDownList($model, $attribute, $data, $htmlOptions = array())
2002    {
2003        $displaySize = TbArray::popValue('displaySize', $htmlOptions);
2004
2005        // In case we do need to create a div container for the input element (i.e. has addon or defined col)
2006        $containerOptions = array();
2007
2008        // Get the intended input width before the rest of the options are normalized
2009        self::addSpanClass($htmlOptions);
2010        self::addColClass($htmlOptions);
2011        $col = self::popColClasses($htmlOptions);
2012
2013        $htmlOptions = self::normalizeInputOptions($htmlOptions);
2014        self::addCssClass('form-control', $htmlOptions);
2015        if (!empty($displaySize)) {
2016            $htmlOptions['size'] = $displaySize;
2017        }
2018
2019        if (!empty($col)) {
2020            self::addCssClass($col, $containerOptions);
2021        }
2022
2023        $output = '';
2024
2025        if (!empty($containerOptions)) {
2026            $output .= self::openTag('div', $containerOptions);
2027        }
2028        $output .= parent::activeDropDownList($model, $attribute, $data, $htmlOptions);
2029
2030        if (!empty($containerOptions)) {
2031            $output .= '</div>';
2032        }
2033
2034        return $output;
2035    }
2036
2037    /**
2038     * Generates a list box for a model attribute.
2039     * @param CModel $model the data model.
2040     * @param string $attribute the attribute.
2041     * @param array $data data for generating the list options (value=>display).
2042     * @param array $htmlOptions additional HTML attributes.
2043     * @return string the generated list box
2044     */
2045    public static function activeListBox($model, $attribute, $data, $htmlOptions = array())
2046    {
2047        TbArray::defaultValue('displaySize', 4, $htmlOptions);
2048        self::addCssClass('form-control', $htmlOptions);
2049        return self::activeDropDownList($model, $attribute, $data, $htmlOptions);
2050    }
2051
2052    /**
2053     * Generates a radio button list for a model attribute.
2054     * @param CModel $model the data model.
2055     * @param string $attribute the attribute.
2056     * @param array $data $data value-label pairs used to generate the radio button list.
2057     * @param array $htmlOptions additional HTML attributes.
2058     * @return string the generated list.
2059     */
2060    public static function activeRadioButtonList($model, $attribute, $data, $htmlOptions = array())
2061    {
2062        parent::resolveNameID($model, $attribute, $htmlOptions);
2063        $selection = parent::resolveValue($model, $attribute);
2064        $name = TbArray::popValue('name', $htmlOptions);
2065        $uncheckValue = TbArray::popValue('uncheckValue', $htmlOptions, '');
2066        $hiddenOptions = isset($htmlOptions['id']) ? array('id' => parent::ID_PREFIX . $htmlOptions['id']) : array('id' => false);
2067        $hidden = $uncheckValue !== null ? parent::hiddenField($name, $uncheckValue, $hiddenOptions) : '';
2068        return $hidden . self::radioButtonList($name, $selection, $data, $htmlOptions);
2069    }
2070
2071    /**
2072     * Generates an inline radio button list for a model attribute.
2073     * @param CModel $model the data model.
2074     * @param string $attribute the attribute.
2075     * @param array $data $data value-label pairs used to generate the radio button list.
2076     * @param array $htmlOptions additional HTML attributes.
2077     * @return string the generated list.
2078     */
2079    public static function activeInlineRadioButtonList($model, $attribute, $data, $htmlOptions = array())
2080    {
2081        $htmlOptions['inline'] = true;
2082        return self::activeRadioButtonList($model, $attribute, $data, $htmlOptions);
2083    }
2084
2085    /**
2086     * Generates a check box list for a model attribute.
2087     * @param CModel $model the data model.
2088     * @param string $attribute the attribute.
2089     * @param array $data $data value-label pairs used to generate the check box list.
2090     * @param array $htmlOptions additional HTML attributes.
2091     * @return string the generated list.
2092     */
2093    public static function activeCheckBoxList($model, $attribute, $data, $htmlOptions = array())
2094    {
2095        parent::resolveNameID($model, $attribute, $htmlOptions);
2096        $selection = parent::resolveValue($model, $attribute);
2097        if ($model->hasErrors($attribute)) {
2098            parent::addErrorCss($htmlOptions);
2099        }
2100        $name = TbArray::popValue('name', $htmlOptions);
2101        $uncheckValue = TbArray::popValue('uncheckValue', $htmlOptions, '');
2102        $hiddenOptions = isset($htmlOptions['id']) ? array('id' => parent::ID_PREFIX . $htmlOptions['id']) : array('id' => false);
2103        $hidden = $uncheckValue !== null ? parent::hiddenField($name, $uncheckValue, $hiddenOptions) : '';
2104        return $hidden . self::checkBoxList($name, $selection, $data, $htmlOptions);
2105    }
2106
2107    /**
2108     * Generates an inline check box list for a model attribute.
2109     * @param CModel $model the data model.
2110     * @param string $attribute the attribute.
2111     * @param array $data $data value-label pairs used to generate the check box list.
2112     * @param array $htmlOptions additional HTML attributes.
2113     * @return string the generated list.
2114     */
2115    public static function activeInlineCheckBoxList($model, $attribute, $data, $htmlOptions = array())
2116    {
2117        $htmlOptions['inline'] = true;
2118        return self::activeCheckBoxList($model, $attribute, $data, $htmlOptions);
2119    }
2120
2121    /**
2122     * Generates an uneditable input for a model attribute.
2123     * @param CModel $model the data model.
2124     * @param string $attribute the attribute.
2125     * @param array $htmlOptions additional HTML attributes.
2126     * @return string the generated input.
2127     */
2128    public static function activeUneditableField($model, $attribute, $htmlOptions = array())
2129    {
2130        parent::resolveNameID($model, $attribute, $htmlOptions);
2131        $value = parent::resolveValue($model, $attribute);
2132        TbArray::removeValues(array('name', 'id'), $htmlOptions);
2133        return self::uneditableField($value, $htmlOptions);
2134    }
2135
2136    /**
2137     * Generates a search query input for a model attribute.
2138     * @param CModel $model the data model.
2139     * @param string $attribute the attribute.
2140     * @param array $htmlOptions additional HTML attributes.
2141     * @return string the generated input.
2142     */
2143    public static function activeSearchQueryField($model, $attribute, $htmlOptions = array())
2144    {
2145        return self::activeTextInputField('search', $model, $attribute, $htmlOptions);
2146    }
2147
2148    /**
2149     * Generates a control group with a text field for a model attribute.
2150     * @param CModel $model the data model.
2151     * @param string $attribute the attribute.
2152     * @param array $htmlOptions additional HTML attributes.
2153     * @return string the generated control group.
2154     * @see self::activeControlGroup
2155     */
2156    public static function activeTextFieldControlGroup($model, $attribute, $htmlOptions = array())
2157    {
2158        return self::activeControlGroup(self::INPUT_TYPE_TEXT, $model, $attribute, $htmlOptions);
2159    }
2160
2161    /**
2162     * Generates a control group with a password field for a model attribute.
2163     * @param CModel $model the data model.
2164     * @param string $attribute the attribute.
2165     * @param array $htmlOptions additional HTML attributes.
2166     * @return string the generated control group.
2167     * @see self::activeControlGroup
2168     */
2169    public static function activePasswordFieldControlGroup($model, $attribute, $htmlOptions = array())
2170    {
2171        return self::activeControlGroup(self::INPUT_TYPE_PASSWORD, $model, $attribute, $htmlOptions);
2172    }
2173
2174    /**
2175     * Generates a control group with a url field for a model attribute.
2176     * @param CModel $model the data model.
2177     * @param string $attribute the attribute.
2178     * @param array $htmlOptions additional HTML attributes.
2179     * @return string the generated control group.
2180     * @see self::activeControlGroup
2181     */
2182    public static function activeUrlFieldControlGroup($model, $attribute, $htmlOptions = array())
2183    {
2184        return self::activeControlGroup(self::INPUT_TYPE_URL, $model, $attribute, $htmlOptions);
2185    }
2186
2187    /**
2188     * Generates a control group with a email field for a model attribute.
2189     * @param CModel $model the data model.
2190     * @param string $attribute the attribute.
2191     * @param array $htmlOptions additional HTML attributes.
2192     * @return string the generated control group.
2193     * @see self::activeControlGroup
2194     */
2195    public static function activeEmailFieldControlGroup($model, $attribute, $htmlOptions = array())
2196    {
2197        return self::activeControlGroup(self::INPUT_TYPE_EMAIL, $model, $attribute, $htmlOptions);
2198    }
2199
2200    /**
2201     * Generates a control group with a number field for a model attribute.
2202     * @param CModel $model the data model.
2203     * @param string $attribute the attribute.
2204     * @param array $htmlOptions additional HTML attributes.
2205     * @return string the generated control group.
2206     * @see self::activeControlGroup
2207     */
2208    public static function activeNumberFieldControlGroup($model, $attribute, $htmlOptions = array())
2209    {
2210        return self::activeControlGroup(self::INPUT_TYPE_NUMBER, $model, $attribute, $htmlOptions);
2211    }
2212
2213    /**
2214     * Generates a control group with a range field for a model attribute.
2215     * @param CModel $model the data model.
2216     * @param string $attribute the attribute.
2217     * @param array $htmlOptions additional HTML attributes.
2218     * @return string the generated control group.
2219     * @see self::activeControlGroup
2220     */
2221    public static function activeRangeFieldControlGroup($model, $attribute, $htmlOptions = array())
2222    {
2223        return self::activeControlGroup(self::INPUT_TYPE_RANGE, $model, $attribute, $htmlOptions);
2224    }
2225
2226    /**
2227     * Generates a control group with a date field for a model attribute.
2228     * @param CModel $model the data model.
2229     * @param string $attribute the attribute.
2230     * @param array $htmlOptions additional HTML attributes.
2231     * @return string the generated control group.
2232     * @see self::activeControlGroup
2233     */
2234    public static function activeDateFieldControlGroup($model, $attribute, $htmlOptions = array())
2235    {
2236        return self::activeControlGroup(self::INPUT_TYPE_DATE, $model, $attribute, $htmlOptions);
2237    }
2238
2239    /**
2240     * Generates a control group with a text area for a model attribute.
2241     * @param CModel $model the data model.
2242     * @param string $attribute the attribute.
2243     * @param array $htmlOptions additional HTML attributes.
2244     * @return string the generated control group.
2245     * @see self::activeControlGroup
2246     */
2247    public static function activeTextAreaControlGroup($model, $attribute, $htmlOptions = array())
2248    {
2249        return self::activeControlGroup(self::INPUT_TYPE_TEXTAREA, $model, $attribute, $htmlOptions);
2250    }
2251
2252    /**
2253     * Generates a control group with a file field for a model attribute.
2254     * @param CModel $model the data model.
2255     * @param string $attribute the attribute.
2256     * @param array $htmlOptions additional HTML attributes.
2257     * @return string the generated control group.
2258     * @see self::activeControlGroup
2259     */
2260    public static function activeFileFieldControlGroup($model, $attribute, $htmlOptions = array())
2261    {
2262        return self::activeControlGroup(self::INPUT_TYPE_FILE, $model, $attribute, $htmlOptions);
2263    }
2264
2265    /**
2266     * Generates a control group with a radio button for a model attribute.
2267     * @param CModel $model the data model.
2268     * @param string $attribute the attribute.
2269     * @param array $htmlOptions additional HTML attributes.
2270     * @return string the generated control group.
2271     * @see self::activeControlGroup
2272     */
2273    public static function activeRadioButtonControlGroup($model, $attribute, $htmlOptions = array())
2274    {
2275        return self::activeControlGroup(self::INPUT_TYPE_RADIOBUTTON, $model, $attribute, $htmlOptions);
2276    }
2277
2278    /**
2279     * Generates a control group with a check box for a model attribute.
2280     * @param CModel $model the data model
2281     * @param string $attribute the attribute.
2282     * @param array $htmlOptions additional HTML attributes.
2283     * @return string the generated control group.
2284     * @see self::activeControlGroup
2285     */
2286    public static function activeCheckBoxControlGroup($model, $attribute, $htmlOptions = array())
2287    {
2288        return self::activeControlGroup(self::INPUT_TYPE_CHECKBOX, $model, $attribute, $htmlOptions);
2289    }
2290
2291    /**
2292     * Generates a control group with a drop down list for a model attribute.
2293     * @param CModel $model the data model.
2294     * @param string $attribute the attribute.
2295     * @param array $data data for generating the list options (value=>display).
2296     * @param array $htmlOptions additional HTML attributes.
2297     * @return string the generated control group.
2298     * @see self::activeControlGroup
2299     */
2300    public static function activeDropDownListControlGroup($model, $attribute, $data = array(), $htmlOptions = array())
2301    {
2302        return self::activeControlGroup(self::INPUT_TYPE_DROPDOWNLIST, $model, $attribute, $htmlOptions, $data);
2303    }
2304
2305    /**
2306     * Generates a control group with a list box for a model attribute.
2307     * @param CModel $model the data model.
2308     * @param string $attribute the attribute.
2309     * @param array $data data for generating the list options (value=>display).
2310     * @param array $htmlOptions additional HTML attributes.
2311     * @return string the generated control group.
2312     * @see self::activeControlGroup
2313     */
2314    public static function activeListBoxControlGroup($model, $attribute, $data = array(), $htmlOptions = array())
2315    {
2316        return self::activeControlGroup(self::INPUT_TYPE_LISTBOX, $model, $attribute, $htmlOptions, $data);
2317    }
2318
2319    /**
2320     * Generates a control group with a radio button list for a model attribute.
2321     * @param CModel $model the data model.
2322     * @param string $attribute the attribute.
2323     * @param array $data data for generating the list options (value=>display).
2324     * @param array $htmlOptions additional HTML attributes.
2325     * @return string the generated control group.
2326     * @see self::activeControlGroup
2327     */
2328    public static function activeRadioButtonListControlGroup(
2329        $model,
2330        $attribute,
2331        $data = array(),
2332        $htmlOptions = array()
2333    ) {
2334        return self::activeControlGroup(self::INPUT_TYPE_RADIOBUTTONLIST, $model, $attribute, $htmlOptions, $data);
2335    }
2336
2337    /**
2338     * Generates a control group with an inline radio button list for a model attribute.
2339     * @param CModel $model the data model.
2340     * @param string $attribute the attribute.
2341     * @param array $data data for generating the list options (value=>display).
2342     * @param array $htmlOptions additional HTML attributes.
2343     * @return string the generated control group.
2344     * @see self::activeControlGroup
2345     */
2346    public static function activeInlineRadioButtonListControlGroup(
2347        $model,
2348        $attribute,
2349        $data = array(),
2350        $htmlOptions = array()
2351    ) {
2352        return self::activeControlGroup(
2353            self::INPUT_TYPE_INLINERADIOBUTTONLIST,
2354            $model,
2355            $attribute,
2356            $htmlOptions,
2357            $data
2358        );
2359    }
2360
2361    /**
2362     * Generates a control group with a check box list for a model attribute.
2363     * @param CModel $model the data model.
2364     * @param string $attribute the attribute.
2365     * @param array $data data for generating the list options (value=>display).
2366     * @param array $htmlOptions additional HTML attributes.
2367     * @return string the generated control group.
2368     * @see self::activeControlGroup
2369     */
2370    public static function activeCheckBoxListControlGroup($model, $attribute, $data = array(), $htmlOptions = array())
2371    {
2372        return self::activeControlGroup(self::INPUT_TYPE_CHECKBOXLIST, $model, $attribute, $htmlOptions, $data);
2373    }
2374
2375    /**
2376     * Generates a control group with an inline check box list for a model attribute.
2377     * @param CModel $model the data model.
2378     * @param string $attribute the attribute.
2379     * @param array $data data for generating the list options (value=>display).
2380     * @param array $htmlOptions additional HTML attributes.
2381     * @return string the generated control group.
2382     * @see self::activeControlGroup
2383     */
2384    public static function activeInlineCheckBoxListControlGroup(
2385        $model,
2386        $attribute,
2387        $data = array(),
2388        $htmlOptions = array()
2389    ) {
2390        return self::activeControlGroup(self::INPUT_TYPE_INLINECHECKBOXLIST, $model, $attribute, $htmlOptions, $data);
2391    }
2392
2393    /**
2394     * Generates a control group with a uneditable field for a model attribute.
2395     * @param CModel $model the data model.
2396     * @param string $attribute the attribute.
2397     * @param array $htmlOptions additional HTML attributes.
2398     * @return string the generated control group.
2399     * @see self::activeControlGroup
2400     */
2401    public static function activeUneditableFieldControlGroup($model, $attribute, $htmlOptions = array())
2402    {
2403        return self::activeControlGroup(self::INPUT_TYPE_UNEDITABLE, $model, $attribute, $htmlOptions);
2404    }
2405
2406    /**
2407     * Generates a control group with a search field for a model attribute.
2408     * @param CModel $model the data model.
2409     * @param string $attribute the attribute.
2410     * @param array $htmlOptions additional HTML attributes.
2411     * @return string the generated control group.
2412     * @see self::activeControlGroup
2413     */
2414    public static function activeSearchQueryControlGroup($model, $attribute, $htmlOptions = array())
2415    {
2416        return self::activeControlGroup(self::INPUT_TYPE_SEARCH, $model, $attribute, $htmlOptions);
2417    }
2418
2419    /**
2420     * Generates an active form row.
2421     * @param string $type the input type.
2422     * @param CModel $model the data model.
2423     * @param string $attribute the attribute.
2424     * @param array $htmlOptions additional HTML attributes.
2425     * @param array $data data for multiple select inputs.
2426     * @return string the generated control group.
2427     */
2428    public static function activeControlGroup($type, $model, $attribute, $htmlOptions = array(), $data = array())
2429    {
2430        $color = TbArray::popValue('color', $htmlOptions);
2431        $groupOptions = TbArray::popValue('groupOptions', $htmlOptions, array());
2432        $controlOptions = TbArray::popValue('controlOptions', $htmlOptions, array());
2433        $label = TbArray::popValue('label', $htmlOptions);
2434        $labelOptions = TbArray::popValue('labelOptions', $htmlOptions, array());
2435
2436        // todo: remove everything that has to do with form layout
2437        $formLayout = TbArray::popValue('formLayout', $htmlOptions, self::FORM_LAYOUT_VERTICAL);
2438        $labelWidthClass = TbArray::popValue('labelWidthClass', $htmlOptions, self::$defaultFormLabelWidthClass);
2439        // Retrieve the old-style "span" option
2440        $span = TbArray::popValue('span', $htmlOptions);
2441        if (!empty($span)) {
2442            $controlWidthClass = 'col-md-' . $span;
2443        } else {
2444            $controlWidthClass = TbArray::popValue('controlWidthClass', $htmlOptions, self::$defaultFormControlWidthClass);
2445        }
2446        $useFormGroup = true;
2447        $useControls = true;
2448        $output = '';
2449
2450        // Special label case case for individual checkboxes and radios
2451        if ($type == self::INPUT_TYPE_CHECKBOX || $type == self::INPUT_TYPE_RADIOBUTTON) {
2452            $htmlOptions['label'] = isset($label) ? $label : $model->getAttributeLabel($attribute);
2453            $htmlOptions['labelOptions'] = $labelOptions;
2454            $htmlOptions['useContainer'] = true;
2455            $label = false;
2456            $useFormGroup = false;
2457        }
2458
2459        // Special conditions depending on the form type
2460        if ($formLayout == self::FORM_LAYOUT_HORIZONTAL) {
2461            switch ($type) {
2462                case self::INPUT_TYPE_CHECKBOX:
2463                case self::INPUT_TYPE_RADIOBUTTON:
2464                    self::addCssClass(self::switchColToOffset($labelWidthClass), $controlOptions);
2465                    self::addCssClass(self::switchOffsetToCol($controlWidthClass), $controlOptions);
2466                    $useFormGroup = true;
2467                    break;
2468                default:
2469                    self::addCssClass(self::switchOffsetToCol($labelWidthClass), $labelOptions);
2470                    self::addCssClass(self::switchOffsetToCol($controlWidthClass), $controlOptions);
2471            }
2472        } elseif ($formLayout == self::FORM_LAYOUT_INLINE || $formLayout == self::FORM_LAYOUT_SEARCH) {
2473            switch ($type) {
2474                case self::INPUT_TYPE_TEXT:
2475                case self::INPUT_TYPE_PASSWORD:
2476                case self::INPUT_TYPE_URL:
2477                case self::INPUT_TYPE_EMAIL:
2478                case self::INPUT_TYPE_NUMBER:
2479                case self::INPUT_TYPE_RANGE:
2480                case self::INPUT_TYPE_DATE:
2481                case self::INPUT_TYPE_FILE:
2482                case self::INPUT_TYPE_SEARCH:
2483                    self::addCssClass('sr-only', $labelOptions);
2484                    if (($label !== null) && (TbArray::getValue('placeholder', $htmlOptions) !== null)) {
2485                        $htmlOptions['placeholder'] = $label;
2486                    }
2487                    break;
2488                case self::INPUT_TYPE_CHECKBOX:
2489                case self::INPUT_TYPE_RADIOBUTTON:
2490                    $useControls = false;
2491                    break;
2492            }
2493        }
2494        // remove until here.
2495
2496        if (isset($label) && $label !== false) {
2497            $labelOptions['label'] = $label;
2498        }
2499        $help = TbArray::popValue('help', $htmlOptions, '');
2500        $helpOptions = TbArray::popValue('helpOptions', $htmlOptions, array());
2501        if (!empty($help)) {
2502            $help = self::inputHelp($help, $helpOptions);
2503        }
2504        $error = TbArray::popValue('error', $htmlOptions, '');
2505
2506        $input = isset($htmlOptions['input'])
2507            ? $htmlOptions['input']
2508            : self::createActiveInput($type, $model, $attribute, $htmlOptions, $data);
2509
2510        if (!empty($color)) {
2511            self::addCssClass($color, $groupOptions);
2512        }
2513        self::addCssClass('control-label', $labelOptions);
2514        if ($label !== false) {
2515            $output .= parent::activeLabelEx($model, $attribute, $labelOptions);
2516        }
2517        if ($useControls) {
2518            $output .= self::controls($input . $error . $help, $controlOptions);
2519        } else {
2520            $output .= $input;
2521        }
2522
2523        if ($useFormGroup) {
2524            self::addCssClass('form-group', $groupOptions);
2525            return self::tag(
2526                'div',
2527                $groupOptions,
2528                $output
2529            );
2530        } else {
2531            return $output;
2532        }
2533    }
2534
2535    /**
2536     * Generates a custom (pre-rendered) active form control group.
2537     * @param string $input the rendered input.
2538     * @param CModel $model the data model.
2539     * @param string $attribute the attribute.
2540     * @param array $htmlOptions additional HTML attributes.
2541     * @return string the generated control group.
2542     */
2543    public static function customActiveControlGroup($input, $model, $attribute, $htmlOptions = array())
2544    {
2545        $htmlOptions['input'] = $input;
2546        return self::activeControlGroup(self::INPUT_TYPE_CUSTOM, $model, $attribute, $htmlOptions);
2547    }
2548
2549    /**
2550     * Creates an active form input of the given type.
2551     * @param string $type the input type.
2552     * @param CModel $model the model instance.
2553     * @param string $attribute the attribute name.
2554     * @param array $htmlOptions additional HTML attributes.
2555     * @param array $data data for multiple select inputs.
2556     * @return string the input.
2557     * @throws CException if the input type is invalid.
2558     */
2559    public static function createActiveInput($type, $model, $attribute, $htmlOptions = array(), $data = array())
2560    {
2561        switch ($type) {
2562            case self::INPUT_TYPE_TEXT:
2563                return self::activeTextField($model, $attribute, $htmlOptions);
2564            case self::INPUT_TYPE_PASSWORD:
2565                return self::activePasswordField($model, $attribute, $htmlOptions);
2566            case self::INPUT_TYPE_URL:
2567                return self::activeUrlField($model, $attribute, $htmlOptions);
2568            case self::INPUT_TYPE_EMAIL:
2569                return self::activeEmailField($model, $attribute, $htmlOptions);
2570            case self::INPUT_TYPE_NUMBER:
2571                return self::activeNumberField($model, $attribute, $htmlOptions);
2572            case self::INPUT_TYPE_RANGE:
2573                return self::activeRangeField($model, $attribute, $htmlOptions);
2574            case self::INPUT_TYPE_DATE:
2575                return self::activeDateField($model, $attribute, $htmlOptions);
2576            case self::INPUT_TYPE_TEXTAREA:
2577                return self::activeTextArea($model, $attribute, $htmlOptions);
2578            case self::INPUT_TYPE_FILE:
2579                return self::activeFileField($model, $attribute, $htmlOptions);
2580            case self::INPUT_TYPE_RADIOBUTTON:
2581                return self::activeRadioButton($model, $attribute, $htmlOptions);
2582            case self::INPUT_TYPE_CHECKBOX:
2583                return self::activeCheckBox($model, $attribute, $htmlOptions);
2584            case self::INPUT_TYPE_DROPDOWNLIST:
2585                return self::activeDropDownList($model, $attribute, $data, $htmlOptions);
2586            case self::INPUT_TYPE_LISTBOX:
2587                return self::activeListBox($model, $attribute, $data, $htmlOptions);
2588            case self::INPUT_TYPE_CHECKBOXLIST:
2589                return self::activeCheckBoxList($model, $attribute, $data, $htmlOptions);
2590            case self::INPUT_TYPE_INLINECHECKBOXLIST:
2591                return self::activeInlineCheckBoxList($model, $attribute, $data, $htmlOptions);
2592            case self::INPUT_TYPE_RADIOBUTTONLIST:
2593                return self::activeRadioButtonList($model, $attribute, $data, $htmlOptions);
2594            case self::INPUT_TYPE_INLINERADIOBUTTONLIST:
2595                return self::activeInlineRadioButtonList($model, $attribute, $data, $htmlOptions);
2596            case self::INPUT_TYPE_UNEDITABLE:
2597                return self::activeUneditableField($model, $attribute, $htmlOptions);
2598            case self::INPUT_TYPE_SEARCH:
2599                return self::activeSearchQueryField($model, $attribute, $htmlOptions);
2600            default:
2601                throw new CException('Invalid input type "' . $type . '".');
2602        }
2603    }
2604
2605    /**
2606     * Displays a summary of validation errors for one or several models.
2607     * @param mixed $model the models whose input errors are to be displayed.
2608     * @param string $header a piece of HTML code that appears in front of the errors.
2609     * @param string $footer a piece of HTML code that appears at the end of the errors.
2610     * @param array $htmlOptions additional HTML attributes to be rendered in the container div tag.
2611     * @return string the error summary. Empty if no errors are found.
2612     */
2613    public static function errorSummary($model, $header = null, $footer = null, $htmlOptions = array())
2614    {
2615        // kind of a quick fix but it will do for now.
2616        self::addCssClass(self::$errorSummaryCss, $htmlOptions);
2617        return parent::errorSummary($model, $header, $footer, $htmlOptions);
2618    }
2619
2620    /**
2621     * Displays the first validation error for a model attribute.
2622     * @param CModel $model the data model.
2623     * @param string $attribute the attribute name.
2624     * @param array $htmlOptions additional HTML attributes.
2625     * @return string the rendered error. Empty if no errors are found.
2626     */
2627    public static function error($model, $attribute, $htmlOptions = array())
2628    {
2629        parent::resolveName($model, $attribute); // turn [a][b]attr into attr
2630        $error = $model->getError($attribute);
2631        $htmlOptions['type'] = self::HELP_TYPE_INLINE;
2632        return !empty($error) ? self::help($error, $htmlOptions) : '';
2633    }
2634
2635    /**
2636     * Generates an input HTML tag  for a model attribute.
2637     * This method generates an input HTML tag based on the given input name and value.
2638     * @param string $type the input type.
2639     * @param CModel $model the data model.
2640     * @param string $attribute the attribute.
2641     * @param array $htmlOptions additional HTML attributes.
2642     * @return string the generated input tag.
2643     */
2644    protected static function activeTextInputField($type, $model, $attribute, $htmlOptions)
2645    {
2646        parent::resolveNameID($model, $attribute, $htmlOptions);
2647        parent::clientChange('change', $htmlOptions);
2648
2649        // In case we do need to create a div container for the input element (i.e. has addon or defined col)
2650        $containerOptions = array();
2651
2652        // Get the intended input width before the rest of the options are normalized
2653        self::addSpanClass($htmlOptions);
2654        self::addColClass($htmlOptions);
2655        $col = self::popColClasses($htmlOptions);
2656
2657        $htmlOptions = self::normalizeInputOptions($htmlOptions);
2658        self::addCssClass('form-control', $htmlOptions);
2659
2660        $addOnClass = self::getAddOnClasses($htmlOptions);
2661        $addOnOptions = TbArray::popValue('addOnOptions', $htmlOptions, array());
2662        self::addCssClass($addOnClass, $addOnOptions);
2663
2664        $prepend = TbArray::popValue('prepend', $htmlOptions, '');
2665        $prependOptions = TbArray::popValue('prependOptions', $htmlOptions, array());
2666        if (!empty($prepend)) {
2667            $prepend = self::inputAddOn($prepend, $prependOptions);
2668        }
2669
2670        $append = TbArray::popValue('append', $htmlOptions, '');
2671        $appendOptions = TbArray::popValue('appendOptions', $htmlOptions, array());
2672        if (!empty($append)) {
2673            $append = self::inputAddOn($append, $appendOptions);
2674        }
2675
2676        if (!empty($addOnClass)) {
2677            $containerOptions = $addOnOptions;
2678        }
2679
2680        if (!empty($col)) {
2681            self::addCssClass($col, $containerOptions);
2682        }
2683
2684        $output = '';
2685        if (!empty($containerOptions)) {
2686            $output .= self::openTag('div', $containerOptions);
2687        }
2688        $output .= $prepend . parent::activeInputField($type, $model, $attribute, $htmlOptions) . $append;
2689        if (!empty($containerOptions)) {
2690            $output .= '</div>';
2691        }
2692        return $output;
2693    }
2694
2695    /**
2696     * Returns the add-on classes based on the given options.
2697     * @param array $htmlOptions the options.
2698     * @return string the classes.
2699     */
2700    protected static function getAddOnClasses($htmlOptions)
2701    {
2702        return (TbArray::getValue('append', $htmlOptions, false) || TbArray::getValue('prepend', $htmlOptions, false))
2703            ? 'input-group'
2704            : '';
2705    }
2706
2707    /**
2708     * Generates an add-on for an input field.
2709     * @param string|array $addOns the add-on.
2710     * @param array $htmlOptions additional HTML attributes.
2711     * @param string $position either 'prepend' or 'append'. Position is only important if you are passing multiple
2712     * addons and it's a mixture of text/radio/checkboxes or buttons. The current styling needs buttons at the ends.
2713     * @return string the generated add-on.
2714     */
2715    protected static function inputAddOn($addOns, $htmlOptions, $position = 'prepend')
2716    {
2717        // todo: refactor this method
2718        $normal = array();
2719        $buttons = array();
2720        $addOnOptions = TbArray::popValue('addOnOptions', $htmlOptions, array());
2721        $normalAddOnOptions = $addOnOptions;
2722        $buttonAddOnOptions = $addOnOptions;
2723        self::addCssClass('input-group-addon', $normalAddOnOptions);
2724        self::addCssClass('input-group-btn', $buttonAddOnOptions);
2725
2726        if (!is_array($addOns)) {
2727            $addOns = array($addOns);
2728        }
2729
2730        foreach ($addOns as $addOn) {
2731            if (strpos($addOn, 'btn') === false) {
2732                $normal[] = $addOn;
2733            } else { // TbHtml::butonDropdown() requires special parsing
2734                if (preg_match('/^<div.*class="(.*)".*>(.*)<\/div>$/U', $addOn, $matches) > 0
2735                    && (isset($matches[1]))
2736                    && strpos($matches[1], 'btn-group') !== false
2737                ) {
2738                    $buttons[] = $matches[2];
2739                } else {
2740                    $buttons[] = $addOn;
2741                }
2742            }
2743        }
2744        $output = '';
2745
2746        if ($position == 'prepend') {
2747            if (!empty($buttons)) {
2748                $output .= self::tag('span', $buttonAddOnOptions, implode(' ', $buttons));
2749            }
2750            if (!empty($normal)) {
2751                $output .= self::tag('span', $normalAddOnOptions, implode(' ', $normal));
2752            }
2753        } else { // append
2754            if (!empty($normal)) {
2755                $output .= self::tag('span', $normalAddOnOptions, implode(' ', $normal));
2756            }
2757            if (!empty($buttons)) {
2758                $output .= self::tag('span', $buttonAddOnOptions, implode(' ', $buttons));
2759            }
2760        }
2761        return $output;
2762    }
2763
2764    /**
2765     * Generates a help text for an input field.
2766     * @param string $help the help text.
2767     * @param array $htmlOptions additional HTML attributes.
2768     * @return string the generated help text.
2769     */
2770    protected static function inputHelp($help, $htmlOptions)
2771    {
2772        $htmlOptions['type'] = self::HELP_TYPE_INLINE;
2773        return self::help($help, $htmlOptions);
2774    }
2775
2776    /**
2777     * Normalizes input options.
2778     * @param array $options the options.
2779     * @return array the normalized options.
2780     */
2781    protected static function normalizeInputOptions($options)
2782    {
2783        self::addSpanClass($options);
2784        self::addTextAlignClass($options);
2785        $size = TbArray::popValue('size', $options);
2786        if (TbArray::popValue('block', $options, false)) {
2787            self::addCssClass('input-block-level', $options);
2788        } else {
2789            if (!empty($size)) {
2790                self::addCssClass('input-' . $size, $options);
2791            }
2792        }
2793        return $options;
2794    }
2795
2796    /**
2797     * Generates form controls.
2798     * @param mixed $controls the controls.
2799     * @param array $htmlOptions additional HTML attributes.
2800     * @return string the generated controls.
2801     */
2802    public static function controls($controls, $htmlOptions = array())
2803    {
2804        if (TbArray::popValue('row', $htmlOptions, false)) {
2805            self::addCssClass('row', $htmlOptions);
2806        }
2807        $before = TbArray::popValue('before', $htmlOptions, '');
2808        $after = TbArray::popValue('after', $htmlOptions, '');
2809        if (is_array($controls)) {
2810            $controls = implode('', $controls);
2811        }
2812        $content = $before . $controls . $after;
2813        return self::tag('div', $htmlOptions, $content);
2814    }
2815
2816    /**
2817     * Generates form controls row.
2818     * @deprecated BS3 only requires a div.row container for all inputs if you want a controls row
2819     * @param mixed $controls the controls.
2820     * @param array $htmlOptions additional HTML attributes.
2821     * @return string the generated controls.
2822     */
2823    public static function controlsRow($controls, $htmlOptions = array())
2824    {
2825        $htmlOptions['row'] = true;
2826        return self::controls($controls, $htmlOptions);
2827    }
2828
2829    /**
2830     * Generates form actions div. This is no longer necessary in Bootstrap 3, but it is still useful to use for
2831     * horizontal forms. When used with a horizontal form, it will appropriately align the actions below other form
2832     * controls.
2833     * @param mixed $actions the actions.
2834     * @param array $htmlOptions additional HTML attributes.
2835     * @return string the generated actions.
2836     */
2837    public static function formActions($actions, $htmlOptions = array())
2838    {
2839        self::addCssClass('form-actions', $htmlOptions);
2840        if (is_array($actions)) {
2841            $actions = implode(' ', $actions);
2842        }
2843        // todo: remove this
2844        $labelWidthClass = TbArray::popValue('labelWidthClass', $htmlOptions, self::$defaultFormLabelWidthClass);
2845        $controlWidthClass = TbArray::popValue('controlWidthClass', $htmlOptions, self::$defaultFormControlWidthClass);
2846
2847        // todo: remove everything that has to do with form layout
2848        if (TbArray::popValue('formLayout', $htmlOptions, self::FORM_LAYOUT_VERTICAL) == self::FORM_LAYOUT_HORIZONTAL) {
2849            self::addCssClass(self::switchColToOffset($labelWidthClass), $htmlOptions);
2850            self::addCssClass(self::switchOffsetToCol($controlWidthClass), $htmlOptions);
2851
2852            return self::tag('div', array('class' => 'form-group'), self::tag('div', $htmlOptions, $actions));
2853        } else {
2854            return self::tag('div', $htmlOptions, $actions);
2855        }
2856    }
2857
2858    /**
2859     * Generates a search form.
2860     * @param mixed $action the form action URL.
2861     * @param string $method form method (e.g. post, get).
2862     * @param array $htmlOptions additional HTML options.
2863     * @return string the generated form.
2864     */
2865    public static function searchForm($action, $method = 'post', $htmlOptions = array())
2866    {
2867        self::addCssClass('form-search', $htmlOptions);
2868        $inputOptions = TbArray::popValue('inputOptions', $htmlOptions, array());
2869        $inputOptions = TbArray::merge(array('type' => 'text', 'placeholder' => 'Search'), $inputOptions);
2870        $name = TbArray::popValue('name', $inputOptions, 'search');
2871        $value = TbArray::popValue('value', $inputOptions, '');
2872        $output = self::beginFormTb(self::FORM_LAYOUT_SEARCH, $action, $method, $htmlOptions);
2873        $output .= self::searchQueryField($name, $value, $inputOptions);
2874        $output .= parent::endForm();
2875        return $output;
2876    }
2877
2878    // Buttons
2879    // http://getbootstrap.com/css/#buttons
2880    // --------------------------------------------------
2881
2882    /**
2883     * Generates a hyperlink tag.
2884     * @param string $text link body. It will NOT be HTML-encoded.
2885     * @param mixed $url a URL or an action route that can be used to create a URL.
2886     * @param array $htmlOptions additional HTML attributes.
2887     * @return string the generated hyperlink
2888     */
2889    public static function link($text, $url = '#', $htmlOptions = array())
2890    {
2891        $htmlOptions['href'] = parent::normalizeUrl($url);
2892        self::clientChange('click', $htmlOptions);
2893        return self::tag('a', $htmlOptions, $text);
2894    }
2895
2896    /**
2897     * Generates an button.
2898     * @param string $label the button label text.
2899     * @param array $htmlOptions additional HTML attributes.
2900     * @return string the generated button.
2901     */
2902    public static function button($label = 'Button', $htmlOptions = array())
2903    {
2904        return self::htmlButton($label, $htmlOptions);
2905    }
2906
2907    /**
2908     * Generates an image submit button.
2909     * @param string $label
2910     * @param array $htmlOptions additional HTML attributes.
2911     * @return string the generated button.
2912     */
2913    public static function htmlButton($label = 'Button', $htmlOptions = array())
2914    {
2915        return self::btn(self::BUTTON_TYPE_HTML, $label, $htmlOptions);
2916    }
2917
2918    /**
2919     * Generates a submit button.
2920     * @param string $label the button label
2921     * @param array $htmlOptions additional HTML attributes.
2922     * @return string the generated button.
2923     */
2924    public static function submitButton($label = 'Submit', $htmlOptions = array())
2925    {
2926        return self::btn(self::BUTTON_TYPE_SUBMIT, $label, $htmlOptions);
2927    }
2928
2929    /**
2930     * Generates a reset button.
2931     * @param string $label the button label
2932     * @param array $htmlOptions additional HTML attributes.
2933     * @return string the generated button.
2934     */
2935    public static function resetButton($label = 'Reset', $htmlOptions = array())
2936    {
2937        return self::btn(self::BUTTON_TYPE_RESET, $label, $htmlOptions);
2938    }
2939
2940    /**
2941     * Generates an image submit button.
2942     * @param string $src the image URL
2943     * @param array $htmlOptions additional HTML attributes.
2944     * @return string the generated button.
2945     */
2946    public static function imageButton($src, $htmlOptions = array())
2947    {
2948        return self::btn(self::BUTTON_TYPE_IMAGE, $src, $htmlOptions);
2949    }
2950
2951    /**
2952     * Generates a link submit button.
2953     * @param string $label the button label.
2954     * @param array $htmlOptions additional HTML attributes.
2955     * @return string the generated button tag.
2956     */
2957    public static function linkButton($label = 'Submit', $htmlOptions = array())
2958    {
2959        return self::btn(self::BUTTON_TYPE_LINK, $label, $htmlOptions);
2960    }
2961
2962    /**
2963     * Generates a link that can initiate AJAX requests.
2964     * @param string $text the link body (it will NOT be HTML-encoded.)
2965     * @param mixed $url the URL for the AJAX request.
2966     * @param array $ajaxOptions AJAX options.
2967     * @param array $htmlOptions additional HTML attributes.
2968     * @return string the generated link.
2969     */
2970    public static function ajaxLink($text, $url, $ajaxOptions = array(), $htmlOptions = array())
2971    {
2972        if (!isset($htmlOptions['href'])) {
2973            $htmlOptions['href'] = '#';
2974        }
2975        $ajaxOptions['url'] = $url;
2976        $htmlOptions['ajax'] = $ajaxOptions;
2977        parent::clientChange('click', $htmlOptions);
2978        return self::tag('a', $htmlOptions, $text);
2979    }
2980
2981    /**
2982     * Generates a push button that can initiate AJAX requests.
2983     * @param string $label the button label.
2984     * @param mixed $url the URL for the AJAX request.
2985     * @param array $ajaxOptions AJAX options.
2986     * @param array $htmlOptions additional HTML attributes.
2987     * @return string the generated button.
2988     */
2989    public static function ajaxButton($label, $url, $ajaxOptions = array(), $htmlOptions = array())
2990    {
2991        $ajaxOptions['url'] = $url;
2992        $htmlOptions['ajaxOptions'] = $ajaxOptions;
2993        return self::btn(self::BUTTON_TYPE_AJAXBUTTON, $label, $htmlOptions);
2994    }
2995
2996    /**
2997     * Generates a push button that can submit the current form in POST method.
2998     * @param string $label the button label
2999     * @param mixed $url the URL for the AJAX request.
3000     * @param array $ajaxOptions AJAX options.
3001     * @param array $htmlOptions additional HTML attributes.
3002     * @return string the generated button.
3003     */
3004    public static function ajaxSubmitButton($label, $url, $ajaxOptions = array(), $htmlOptions = array())
3005    {
3006        $ajaxOptions['type'] = 'POST';
3007        $htmlOptions['type'] = 'submit';
3008        return self::ajaxButton($label, $url, $ajaxOptions, $htmlOptions);
3009    }
3010
3011     /**
3012     * Generates a form input push button.
3013     * @param string $label the button label
3014     * @param array $htmlOptions additional HTML attributes.
3015     * @return string the generated button.
3016     */
3017    public static function inputButton($label, $htmlOptions = array()) {
3018        return self::btn(self::BUTTON_TYPE_INPUTBUTTON, $label, $htmlOptions);
3019    }
3020
3021	/**
3022     * Generates a form input submit push button.
3023     * @param string $label the button label
3024     * @param array $htmlOptions additional HTML attributes.
3025     * @return string the generated button.
3026     */
3027    public static function inputSubmit($label = 'Submit', $htmlOptions = array()) {
3028        return self::btn(self::BUTTON_TYPE_INPUTSUBMIT, $label, $htmlOptions);
3029    }
3030
3031    /**
3032     * Generates a button.
3033     * @param string $type the button type.
3034     * @param string $label the button label text.
3035     * @param array $htmlOptions additional HTML attributes.
3036     * @return string the generated button.
3037     */
3038    public static function btn($type, $label, $htmlOptions = array())
3039    {
3040        self::addCssClass('btn', $htmlOptions);
3041        $color = TbArray::popValue('color', $htmlOptions, self::BUTTON_COLOR_DEFAULT);
3042        if (!empty($color)) {
3043            self::addCssClass('btn-' . $color, $htmlOptions);
3044        }
3045        $size = TbArray::popValue('size', $htmlOptions);
3046        if (!empty($size)) {
3047            self::addCssClass('btn-' . $size, $htmlOptions);
3048        }
3049        if (TbArray::popValue('block', $htmlOptions, false)) {
3050            self::addCssClass('btn-block', $htmlOptions);
3051        }
3052        if (TbArray::popValue('disabled', $htmlOptions, false)) {
3053            self::addCssClass('disabled', $htmlOptions);
3054            $htmlOptions['disabled'] = 'disabled';
3055        }
3056        $loading = TbArray::popValue('loading', $htmlOptions);
3057        if (!empty($loading)) {
3058            $htmlOptions['data-loading-text'] = $loading;
3059        }
3060        if (TbArray::popValue('toggle', $htmlOptions, false)) {
3061            $htmlOptions['data-toggle'] = 'button';
3062        }
3063        $icon = TbArray::popValue('icon', $htmlOptions);
3064        $iconOptions = TbArray::popValue('iconOptions', $htmlOptions, array());
3065        if (!is_array($type) && strpos($type, 'input') === false) {
3066            if (!empty($icon)) {
3067                $label = self::icon($icon, $iconOptions) . ' ' . $label;
3068            }
3069            $items = TbArray::popValue('items', $htmlOptions);
3070        }
3071        $dropdownOptions = $htmlOptions;
3072        TbArray::removeValues(array('groupOptions', 'menuOptions', 'dropup'), $htmlOptions);
3073        self::addSpanClass($htmlOptions); // must be called here as parent renders buttons
3074        self::addPullClass($htmlOptions); // must be called here as parent renders buttons
3075        return isset($items)
3076            ? self::btnDropdown($type, $label, $items, $dropdownOptions)
3077            : self::createButton($type, $label, $htmlOptions);
3078    }
3079
3080    /**
3081     * Generates a button dropdown.
3082     * @param string $type the button type.
3083     * @param string $label the button label text.
3084     * @param array $items the menu items.
3085     * @param array $htmlOptions additional HTML attributes.
3086     * @return string the generated button.
3087     */
3088    protected static function btnDropdown($type, $label, $items, $htmlOptions)
3089    {
3090        $menuOptions = TbArray::popValue('menuOptions', $htmlOptions, array());
3091        $groupOptions = TbArray::popValue('groupOptions', $htmlOptions, array());
3092        self::addCssClass('btn-group', $groupOptions);
3093        if (TbArray::popValue('dropup', $htmlOptions, false)) {
3094            self::addCssClass('dropup', $groupOptions);
3095        }
3096        $output = self::openTag('div', $groupOptions);
3097        $toggleButtonType = TbArray::popValue('type', $htmlOptions, self::BUTTON_TYPE_HTML);
3098        $toggleButtonType = is_array($toggleButtonType) ? $toggleButtonType[1] : $toggleButtonType;
3099        if (TbArray::popValue('split', $htmlOptions, false)) {
3100            $output .= self::createButton($type, $label, $htmlOptions);
3101            $label = '';
3102        }
3103        if(in_array($toggleButtonType, array(self::BUTTON_TYPE_LINKBUTTON, self::BUTTON_TYPE_LINK))){
3104            $output .= self::dropdownToggleLink($label, $htmlOptions);
3105        } else {
3106            $output .= self::dropdownToggleButton($label, $htmlOptions);
3107        }
3108        $output .= self::dropdown($items, $menuOptions);
3109        $output .= '</div>';
3110        return $output;
3111    }
3112
3113    /**
3114     * Creates a button the of given type.
3115     * @param string $type the button type.
3116     * @param string $label the button label.
3117     * @param array $htmlOptions additional HTML attributes.
3118     * @return string the button.
3119     * @throws CException if the button type is valid.
3120     */
3121    protected static function createButton($type, $label, $htmlOptions)
3122    {
3123        $url = TbArray::popValue('url', $htmlOptions, '#');
3124        $ajaxOptions = TbArray::popValue('ajaxOptions', $htmlOptions, array());
3125        switch ($type) {
3126            case self::BUTTON_TYPE_HTML:
3127                return parent::htmlButton($label, $htmlOptions);
3128
3129            case self::BUTTON_TYPE_SUBMIT:
3130                $htmlOptions['type'] = 'submit';
3131                return parent::htmlButton($label, $htmlOptions);
3132
3133            case self::BUTTON_TYPE_RESET:
3134                $htmlOptions['type'] = 'reset';
3135                return parent::htmlButton($label, $htmlOptions);
3136
3137            case self::BUTTON_TYPE_IMAGE:
3138                return parent::imageButton($label, $htmlOptions);
3139
3140            case self::BUTTON_TYPE_LINKBUTTON:
3141                return parent::linkButton($label, $htmlOptions);
3142
3143            case self::BUTTON_TYPE_AJAXLINK:
3144                return parent::ajaxLink($label, $url, $ajaxOptions, $htmlOptions);
3145
3146            case self::BUTTON_TYPE_AJAXBUTTON:
3147                $htmlOptions['ajax'] = $ajaxOptions;
3148                return parent::htmlButton($label, $htmlOptions);
3149
3150            case self::BUTTON_TYPE_INPUTBUTTON:
3151                return parent::button($label, $htmlOptions);
3152
3153            case self::BUTTON_TYPE_INPUTSUBMIT:
3154                $htmlOptions['type'] = 'submit';
3155                return parent::button($label, $htmlOptions);
3156
3157            case self::BUTTON_TYPE_LINK:
3158                return self::link($label, $url, $htmlOptions);
3159
3160            default:
3161                throw new CException('Invalid button type "' . $type . '".');
3162        }
3163    }
3164
3165    // Images
3166    // http://getbootstrap.com/css/#images
3167    // --------------------------------------------------
3168
3169    /**
3170     * Generates an image tag with rounded corners.
3171     * @param string $src the image URL.
3172     * @param string $alt the alternative text display.
3173     * @param array $htmlOptions additional HTML attributes.
3174     * @return string the generated image tag.
3175     */
3176    public static function imageRounded($src, $alt = '', $htmlOptions = array())
3177    {
3178        $htmlOptions['type'] = self::IMAGE_TYPE_ROUNDED;
3179        return self::image($src, $alt, $htmlOptions);
3180    }
3181
3182    /**
3183     * Generates an image tag with circle.
3184     * @param string $src the image URL.
3185     * @param string $alt the alternative text display.
3186     * @param array $htmlOptions additional HTML attributes.
3187     * @return string the generated image tag.
3188     */
3189    public static function imageCircle($src, $alt = '', $htmlOptions = array())
3190    {
3191        $htmlOptions['type'] = self::IMAGE_TYPE_CIRCLE;
3192        return self::image($src, $alt, $htmlOptions);
3193    }
3194
3195    /**
3196     * Generates an image tag within thumbnail frame.
3197     * @param string $src the image URL.
3198     * @param string $alt the alternative text display.
3199     * @param array $htmlOptions additional HTML attributes.
3200     * @return string the generated image tag.
3201     */
3202    public static function imageThumbnail($src, $alt = '', $htmlOptions = array())
3203    {
3204        $htmlOptions['type'] = self::IMAGE_TYPE_THUMBNAIL;
3205        return self::image($src, $alt, $htmlOptions);
3206    }
3207
3208    /**
3209     * Generates an image tag within polaroid frame.
3210     * @deprecated See {@link imageThumbnail()}
3211     * @param string $src the image URL.
3212     * @param string $alt the alternative text display.
3213     * @param array $htmlOptions additional HTML attributes.
3214     * @return string the generated image tag.
3215     */
3216    public static function imagePolaroid($src, $alt = '', $htmlOptions = array())
3217    {
3218        $htmlOptions['type'] = self::IMAGE_TYPE_POLAROID;
3219        return self::image($src, $alt, $htmlOptions);
3220    }
3221
3222    /**
3223     * Generates an image tag.
3224     * @param string $src the image URL.
3225     * @param string $alt the alternative text display.
3226     * @param array $htmlOptions additional HTML attributes.
3227     * @return string the generated image tag.
3228     */
3229    public static function image($src, $alt = '', $htmlOptions = array())
3230    {
3231        $type = TbArray::popValue('type', $htmlOptions);
3232        if (!empty($type)) {
3233            self::addCssClass('img-' . $type, $htmlOptions);
3234        }
3235        if (TbArray::popValue('responsive', $htmlOptions, false)) {
3236            self::addCssClass('img-responsive', $htmlOptions);
3237        }
3238        return parent::image($src, $alt, $htmlOptions);
3239    }
3240
3241    // Icons by fas
3242    // http://getbootstrap.com/components/#fas
3243    // --------------------------------------------------
3244
3245    /**
3246     * Generates an icon.
3247     * @param string $icon the icon type.
3248     * @param array $htmlOptions additional HTML attributes.
3249     * @param string $tagName the icon HTML tag.
3250     * @param string $vendor the icon vendor.
3251     * @return string the generated icon.
3252     */
3253    public static function icon($icon, $htmlOptions = array(), $tagName = 'span')
3254    {
3255        if (is_string($icon)) {
3256            if (strpos($icon, 'fa-') === false) {
3257                $icon = 'fa-' . implode(' fa-', explode(' ', $icon));
3258            }
3259            self::addCssClass(array('fa', $icon), $htmlOptions);
3260            $color = TbArray::popValue('color', $htmlOptions);
3261            if (!empty($color) && $color === self::ICON_COLOR_WHITE) {
3262                self::addCssClass("fa-white", $htmlOptions);
3263            }
3264            return self::openTag($tagName, $htmlOptions) . parent::closeTag($tagName); // tag won't work in this case
3265        }
3266        return '';
3267    }
3268
3269    //
3270    // COMPONENTS
3271    // --------------------------------------------------
3272
3273    // Dropdowns
3274    // http://getbootstrap.com/components/#dropdowns
3275    // --------------------------------------------------
3276
3277    /**
3278     * Generates a dropdown menu.
3279     * @param array $items the menu items.
3280     * @param array $htmlOptions additional HTML attributes.
3281     * @return string the generated menu.
3282     */
3283    protected static function dropdown($items, $htmlOptions = array())
3284    {
3285        TbArray::defaultValue('role', 'menu', $htmlOptions);
3286        self::addCssClass('dropdown-menu', $htmlOptions);
3287        return self::menu($items, $htmlOptions);
3288    }
3289
3290    /**
3291     * Generates a dropdown toggle link.
3292     * @param string $label the link label text.
3293     * @param array $htmlOptions additional HTML attributes.
3294     * @return string the generated link.
3295     */
3296    public static function dropdownToggleLink($label, $htmlOptions = array())
3297    {
3298        return self::dropdownToggle(self::BUTTON_TYPE_LINK, $label, $htmlOptions);
3299    }
3300
3301    /**
3302     * Generates a dropdown toggle button.
3303     * @param string $label the button label text.
3304     * @param array $htmlOptions additional HTML attributes.
3305     * @return string the generated button.
3306     */
3307    public static function dropdownToggleButton($label = '', $htmlOptions = array())
3308    {
3309        return self::dropdownToggle(self::BUTTON_TYPE_HTML, $label, $htmlOptions);
3310    }
3311
3312    /**
3313     * Generates a dropdown toggle element.
3314     * @param string $type the type of dropdown.
3315     * @param string $label the element text.
3316     * @param array $htmlOptions additional HTML attributes.
3317     * @return string the generated element.
3318     */
3319    protected static function dropdownToggle($type, $label, $htmlOptions)
3320    {
3321        self::addCssClass('dropdown-toggle', $htmlOptions);
3322        $label .= ' <b class="caret"></b>';
3323        $htmlOptions['data-toggle'] = 'dropdown';
3324        return self::btn($type, $label, $htmlOptions);
3325    }
3326
3327    /**
3328     * Generates a dropdown toggle menu item.
3329     * @param string $label the menu item text.
3330     * @param string $url the menu item URL.
3331     * @param array $htmlOptions additional HTML attributes.
3332     * @param int $depth the menu depth at which this link is located
3333     * @return string the generated menu item.
3334     */
3335    public static function dropdownToggleMenuLink($label, $url = '#', $htmlOptions = array(), $depth = 0)
3336    {
3337        self::addCssClass('dropdown-toggle', $htmlOptions);
3338        if ($depth === 0) {
3339            $label .= ' <b class="caret"></b>';
3340        }
3341        $htmlOptions['data-toggle'] = 'dropdown';
3342        return self::link($label, $url, $htmlOptions);
3343    }
3344
3345    // Button groups
3346    // http://getbootstrap.com/components/#btn-groups
3347    // --------------------------------------------------
3348
3349    /**
3350     * Generates a button group.
3351     * @param array $buttons the button configurations.
3352     * @param array $htmlOptions additional HTML options.
3353     * @return string the generated button group.
3354     */
3355    public static function buttonGroup(array $buttons, $htmlOptions = array())
3356    {
3357        if (!empty($buttons)) {
3358            self::addCssClass('btn-group', $htmlOptions);
3359            if (TbArray::popValue('vertical', $htmlOptions, false)) {
3360                self::addCssClass('btn-group-vertical', $htmlOptions);
3361            }
3362            $toggle = TbArray::popValue('toggle', $htmlOptions);
3363            $name = TbArray::popValue('name', $htmlOptions);
3364            if (!empty($name) && substr($name, -2) !== '[]') {
3365                $name .= '[]';
3366            }
3367            if (in_array($toggle, array(self::BUTTON_TOGGLE_CHECKBOX, self::BUTTON_TOGGLE_RADIO))) {
3368                $htmlOptions['data-toggle'] = 'buttons';
3369                if (empty($name)) {
3370                    if ($toggle === self::BUTTON_TOGGLE_CHECKBOX) {
3371                        $name = 'checkbox[]';
3372                    } elseif ($toggle === self::BUTTON_TOGGLE_RADIO) {
3373                        $name = 'radio[]';
3374                    }
3375                }
3376            } else {
3377                $htmlOptions['data-toggle'] = $toggle;
3378            }
3379            $parentOptions = array(
3380                'color' => TbArray::popValue('color', $htmlOptions),
3381                'size' => TbArray::popValue('size', $htmlOptions),
3382                'disabled' => TbArray::popValue('disabled', $htmlOptions)
3383            );
3384            $output = self::openTag('div', $htmlOptions);
3385            foreach ($buttons as $buttonOptions) {
3386                if (isset($buttonOptions['visible']) && $buttonOptions['visible'] === false) {
3387                    continue;
3388                }
3389                // todo: consider removing the support for htmlOptions.
3390                $options = TbArray::popValue('htmlOptions', $buttonOptions, array());
3391                if (!empty($options)) {
3392                    $buttonOptions = TbArray::merge($options, $buttonOptions);
3393                }
3394                $buttonLabel = TbArray::popValue('label', $buttonOptions, '');
3395                $buttonOptions = TbArray::copyValues(array('color', 'size', 'disabled'), $parentOptions, $buttonOptions);
3396                TbArray::defaultValue('color', 'default', $buttonOptions);
3397                $items = TbArray::popValue('items', $buttonOptions, array());
3398                if (!empty($items)) {
3399                    $output .= self::buttonDropdown($buttonLabel, $items, $buttonOptions);
3400                } else {
3401                    if (in_array($toggle, array(self::BUTTON_TOGGLE_CHECKBOX, self::BUTTON_TOGGLE_RADIO))) {
3402                        // Put the "button" label back into its options and add a few label options as well
3403                        $buttonOptions['label'] = $buttonLabel;
3404                        self::addCssClass(
3405                            array('btn', 'btn-' . TbArray::getValue('color', $buttonOptions)),
3406                            $buttonOptions['labelOptions']
3407                        );
3408
3409                        $checked = TbArray::popValue('checked', $buttonOptions, false);
3410                        if ($checked) {
3411                            self::addCssClass('active', $buttonOptions['labelOptions']);
3412                        }
3413                        if ($toggle === self::BUTTON_TOGGLE_CHECKBOX) { // BS3 toggle uses checkbox...
3414                            $output .= self::checkBox($name, $checked, $buttonOptions);
3415                        } elseif ($toggle === self::BUTTON_TOGGLE_RADIO) { // ...or BS3 toggle uses radio
3416                            $output .= self::radioButton($name, $checked, $buttonOptions);
3417                        }
3418                    } else {
3419                        $output .= self::linkButton($buttonLabel, $buttonOptions);
3420                    }
3421                }
3422            }
3423            $output .= '</div>';
3424            return $output;
3425        }
3426        return '';
3427    }
3428
3429    /**
3430     * Generates a vertical button group.
3431     * @param array $buttons the button configurations.
3432     * @param array $htmlOptions additional HTML options.
3433     * @return string the generated button group.
3434     */
3435    public static function verticalButtonGroup(array $buttons, $htmlOptions = array())
3436    {
3437        $htmlOptions['vertical'] = true;
3438        return self::buttonGroup($buttons, $htmlOptions);
3439    }
3440
3441    /**
3442     * Generates a button toolbar.
3443     * @param array $groups the button group configurations.
3444     * @param array $htmlOptions additional HTML options.
3445     * @return string the generated button toolbar.
3446     */
3447    public static function buttonToolbar(array $groups, $htmlOptions = array())
3448    {
3449        if (!empty($groups)) {
3450            self::addCssClass('btn-toolbar', $htmlOptions);
3451            TbArray::defaultValue('role', 'toolbar', $htmlOptions);
3452            $parentOptions = array(
3453                'color' => TbArray::popValue('color', $htmlOptions),
3454                'size' => TbArray::popValue('size', $htmlOptions),
3455                'disabled' => TbArray::popValue('disabled', $htmlOptions)
3456            );
3457            $output = self::openTag('div', $htmlOptions);
3458            foreach ($groups as $groupOptions) {
3459                if (isset($groupOptions['visible']) && $groupOptions['visible'] === false) {
3460                    continue;
3461                }
3462                $items = TbArray::popValue('items', $groupOptions, array());
3463                if (empty($items)) {
3464                    continue;
3465                }
3466                // todo: consider removing the support for htmlOptions.
3467                $options = TbArray::popValue('htmlOptions', $groupOptions, array());
3468                if (!empty($options)) {
3469                    $groupOptions = TbArray::merge($options, $groupOptions);
3470                }
3471                $groupOptions = TbArray::copyValues(array('color', 'size', 'disabled'), $parentOptions, $groupOptions);
3472                $output .= self::buttonGroup($items, $groupOptions);
3473            }
3474            $output .= '</div>';
3475            return $output;
3476        }
3477        return '';
3478    }
3479
3480    // Button dropdowns
3481    // http://getbootstrap.com/components/#btn-dropdowns
3482    // --------------------------------------------------
3483
3484    /**
3485     * Generates a button with a dropdown menu.
3486     * @param string $label the button label text.
3487     * @param array $items the menu items.
3488     * @param array $htmlOptions additional HTML attributes.
3489     * @return string the generated button.
3490     */
3491    public static function buttonDropdown($label, $items, $htmlOptions = array())
3492    {
3493        $htmlOptions['items'] = $items;
3494        $type = isset($htmlOptions['type']) ? $htmlOptions['type'] : self::BUTTON_TYPE_SUBMIT;
3495        $type = is_array($type) ? $type[0] : $type;
3496        return self::btn($type, $label, $htmlOptions);
3497    }
3498
3499    /**
3500     * Generates a button with a split dropdown menu.
3501     * @param string $label the button label text.
3502     * @param array $items the menu items.
3503     * @param array $htmlOptions additional HTML attributes.
3504     * @return string the generated button.
3505     */
3506    public static function splitButtonDropdown($label, $items, $htmlOptions = array())
3507    {
3508        $htmlOptions['split'] = true;
3509        return self::buttonDropdown($label, $items, $htmlOptions);
3510    }
3511
3512    // Navs
3513    // http://getbootstrap.com/components/#nav
3514    // --------------------------------------------------
3515
3516    /**
3517     * Generates a tab navigation.
3518     * @param array $items the menu items.
3519     * @param array $htmlOptions additional HTML attributes.
3520     * @return string the generated menu.
3521     */
3522    public static function tabs($items, $htmlOptions = array())
3523    {
3524        return self::nav(self::NAV_TYPE_TABS, $items, $htmlOptions);
3525    }
3526
3527    /**
3528     * Generates a stacked tab navigation.
3529     * @deprecated Style does not exist in BS3
3530     * @param array $items the menu items.
3531     * @param array $htmlOptions additional HTML attributes.
3532     * @return string the generated menu.
3533     */
3534    public static function stackedTabs($items, $htmlOptions = array())
3535    {
3536        $htmlOptions['stacked'] = true;
3537        return self::tabs($items, $htmlOptions);
3538    }
3539
3540    /**
3541     * Generates a pills navigation.
3542     * @param array $items the menu items.
3543     * @param array $htmlOptions additional HTML attributes.
3544     * @return string the generated menu.
3545     */
3546    public static function pills($items, $htmlOptions = array())
3547    {
3548        return self::nav(self::NAV_TYPE_PILLS, $items, $htmlOptions);
3549    }
3550
3551    /**
3552     * Generates a stacked pills navigation.
3553     * @param array $items the menu items.
3554     * @param array $htmlOptions additional HTML attributes.
3555     * @return string the generated menu.
3556     */
3557    public static function stackedPills($items, $htmlOptions = array())
3558    {
3559        $htmlOptions['stacked'] = true;
3560        return self::pills($items, $htmlOptions);
3561    }
3562
3563    /**
3564     * Generates a list navigation.
3565     * @param array $items the menu items.
3566     * @param array $htmlOptions additional HTML attributes.
3567     * @return string the generated menu.
3568     */
3569    public static function navList($items, $htmlOptions = array())
3570    {
3571        foreach ($items as $i => $itemOptions) {
3572            if (is_string($itemOptions)) {
3573                continue;
3574            }
3575            if (!isset($itemOptions['url']) && !isset($itemOptions['items'])) {
3576                $label = TbArray::popValue('label', $itemOptions, '');
3577                $items[$i] = self::menuHeader($label, $itemOptions);
3578            }
3579        }
3580        return self::nav(self::NAV_TYPE_LIST, $items, $htmlOptions);
3581    }
3582
3583    /**
3584     * Generates a navigation menu.
3585     * @param string $type the menu type.
3586     * @param array $items the menu items.
3587     * @param array $htmlOptions additional HTML attributes.
3588     * @return string the generated menu.
3589     */
3590    public static function nav($type, $items, $htmlOptions = array())
3591    {
3592        self::addCssClass('nav', $htmlOptions);
3593        if (!empty($type)) {
3594            self::addCssClass('nav-' . $type, $htmlOptions);
3595        } else {
3596            self::addCssClass('navbar-nav', $htmlOptions);
3597        }
3598        $stacked = TbArray::popValue('stacked', $htmlOptions, false);
3599        if ($type !== self::NAV_TYPE_LIST && $stacked) {
3600            self::addCssClass('nav-stacked', $htmlOptions);
3601        }
3602        return self::menu($items, $htmlOptions);
3603    }
3604
3605    /**
3606     * Generates a menu.
3607     * @param array $items the menu items.
3608     * @param array $htmlOptions additional HTML attributes.
3609     * @param integer $depth the current depth.
3610     * @return string the generated menu.
3611     */
3612    public static function menu(array $items, $htmlOptions = array(), $depth = 0)
3613    {
3614        // todo: consider making this method protected.
3615        if (!empty($items)) {
3616            $htmlOptions['role'] = 'menu';
3617            $output = self::openTag('ul', $htmlOptions);
3618            foreach ($items as $itemOptions) {
3619                if (is_string($itemOptions)) {
3620                    if ($itemOptions == '---') {
3621                        $output .= self::menuDivider();
3622                    } else {
3623                        $output .= $itemOptions;
3624                    }
3625                } else {
3626                    if (TbArray::popValue('visible', $itemOptions, true)  === false) {
3627                        continue;
3628                    }
3629                    // todo: consider removing the support for htmlOptions.
3630                    $options = TbArray::popValue('htmlOptions', $itemOptions, array());
3631                    if (!empty($options)) {
3632                        $itemOptions = TbArray::merge($options, $itemOptions);
3633                    }
3634                    $label = TbArray::popValue('label', $itemOptions, '');
3635                    if (TbArray::popValue('active', $itemOptions, false)) {
3636                        self::addCssClass('active', $itemOptions);
3637                    }
3638                    if (TbArray::popValue('disabled', $itemOptions, false)) {
3639                        self::addCssClass('disabled', $itemOptions);
3640                    }
3641                    if (!isset($itemOptions['linkOptions'])) {
3642                        $itemOptions['linkOptions'] = array();
3643                    }
3644                    $icon = TbArray::popValue('icon', $itemOptions);
3645                    if (!empty($icon)) {
3646                        $label = self::icon($icon) . ' ' . $label;
3647                    }
3648                    $items = TbArray::popValue('items', $itemOptions, array());
3649                    $url = TbArray::popValue('url', $itemOptions, false);
3650                    if (empty($items)) {
3651                        if (!$url) {
3652                            $output .= self::menuHeader($label);
3653                        } else {
3654                            $itemOptions['linkOptions']['tabindex'] = -1;
3655                            $output .= self::menuLink($label, $url, $itemOptions);
3656                        }
3657                    } else {
3658                        $output .= self::menuDropdown($label, $url, $items, $itemOptions, $depth);
3659                    }
3660                }
3661            }
3662            $output .= '</ul>';
3663            return $output;
3664        } else {
3665            return '';
3666        }
3667    }
3668
3669    /**
3670     * Generates a menu link.
3671     * @param string $label the link label.
3672     * @param array $url the link url.
3673     * @param array $htmlOptions additional HTML attributes.
3674     * @return string the generated menu item.
3675     */
3676    public static function menuLink($label, $url, $htmlOptions = array())
3677    {
3678        TbArray::defaultValue('role', 'menuitem', $htmlOptions);
3679        $linkOptions = TbArray::popValue('linkOptions', $htmlOptions, array());
3680        $content = self::link($label, $url, $linkOptions);
3681        return self::tag('li', $htmlOptions, $content);
3682    }
3683
3684    /**
3685     * Generates a menu dropdown.
3686     * @param string $label the link label.
3687     * @param string $url the link URL.
3688     * @param array $items the menu configuration.
3689     * @param array $htmlOptions additional HTML attributes.
3690     * @param integer $depth the current depth.
3691     * @return string the generated dropdown.
3692     */
3693    protected static function menuDropdown($label, $url, $items, $htmlOptions, $depth = 0)
3694    {
3695        self::addCssClass($depth === 0 ? 'dropdown' : 'dropdown-submenu', $htmlOptions);
3696        TbArray::defaultValue('role', 'menuitem', $htmlOptions);
3697        $linkOptions = TbArray::popValue('linkOptions', $htmlOptions, array());
3698        $menuOptions = TbArray::popValue('menuOptions', $htmlOptions, array());
3699        self::addCssClass('dropdown-menu', $menuOptions);
3700        if ($depth === 0) {
3701            $defaultId = parent::ID_PREFIX . parent::$count++;
3702            TbArray::defaultValue('id', $defaultId, $menuOptions);
3703            $menuOptions['aria-labelledby'] = $menuOptions['id'];
3704            $menuOptions['role'] = 'menu';
3705        }
3706        $output = self::openTag('li', $htmlOptions);
3707        $output .= self::dropdownToggleMenuLink($label, $url, $linkOptions, $depth);
3708        $output .= self::menu($items, $menuOptions, $depth + 1);
3709        $output .= '</li>';
3710        return $output;
3711    }
3712
3713    /**
3714     * Generates a menu header.
3715     * @param string $label the header text.
3716     * @param array $htmlOptions additional HTML options.
3717     * @return string the generated header.
3718     */
3719    public static function menuHeader($label, $htmlOptions = array())
3720    {
3721        self::addCssClass('dropdown-header', $htmlOptions);
3722        return self::tag('li', $htmlOptions, $label);
3723    }
3724
3725    /**
3726     * Generates a menu divider.
3727     * @param array $htmlOptions additional HTML attributes.
3728     * @return string the generated menu item.
3729     */
3730    public static function menuDivider($htmlOptions = array())
3731    {
3732        self::addCssClass('divider', $htmlOptions);
3733        return self::tag('li', $htmlOptions);
3734    }
3735
3736    /**
3737     * Generates a tabbable tabs menu.
3738     * @param array $tabs the tab configurations.
3739     * @param array $htmlOptions additional HTML attributes.
3740     * @return string the generated menu.
3741     */
3742    public static function tabbableTabs($tabs, $htmlOptions = array())
3743    {
3744        return self::tabbable(self::NAV_TYPE_TABS, $tabs, $htmlOptions);
3745    }
3746
3747    /**
3748     * Generates a tabbable pills menu.
3749     * @param array $pills the pills.
3750     * @param array $htmlOptions additional HTML attributes.
3751     * @internal param array $tabs the tab configurations.
3752     * @return string the generated menu.
3753     */
3754    public static function tabbablePills($pills, $htmlOptions = array())
3755    {
3756        return self::tabbable(self::NAV_TYPE_PILLS, $pills, $htmlOptions);
3757    }
3758
3759    /**
3760     * Generates a tabbable menu.
3761     * @param string $type the menu type.
3762     * @param array $tabs the tab configurations.
3763     * @param array $htmlOptions additional HTML attributes.
3764     * @return string the generated menu.
3765     */
3766    public static function tabbable($type, $tabs, $htmlOptions = array())
3767    {
3768        self::addCssClass('tabbable', $htmlOptions);
3769        $placement = TbArray::popValue('placement', $htmlOptions);
3770        if (!empty($placement)) {
3771            self::addCssClass('tabs-' . $placement, $htmlOptions);
3772        }
3773        $menuOptions = TbArray::popValue('menuOptions', $htmlOptions, array());
3774        $contentOptions = TbArray::popValue('contentOptions', $htmlOptions, array());
3775        self::addCssClass('tab-content', $contentOptions);
3776        $panes = array();
3777        $items = self::normalizeTabs($tabs, $panes);
3778        $menu = self::nav($type, $items, $menuOptions);
3779        $content = self::tag('div', $contentOptions, implode('', $panes));
3780        $output = self::openTag('div', $htmlOptions);
3781        $output .= $placement === self::TABS_PLACEMENT_BELOW ? $content . $menu : $menu . $content;
3782        $output .= '</div>';
3783        return $output;
3784    }
3785
3786    /**
3787     * Normalizes the tab configuration.
3788     * @param array $tabs the tab configuration.
3789     * @param array $panes a reference to the panes array.
3790     * @param integer $i the running index.
3791     * @return array the items.
3792     */
3793    protected static function normalizeTabs($tabs, &$panes, $i = 0)
3794    {
3795        $menuItems = array();
3796        foreach ($tabs as $tabOptions) {
3797            if (isset($tabOptions['visible']) && $tabOptions['visible'] === false) {
3798                continue;
3799            }
3800            $menuItem = array();
3801            $menuItem['icon'] = TbArray::popValue('icon', $tabOptions);
3802            $menuItem['label'] = TbArray::popValue('label', $tabOptions, '');
3803            $menuItem['active'] = TbArray::getValue('active', $tabOptions, false);
3804            $menuItem['disabled'] = TbArray::popValue('disabled', $tabOptions, false);
3805            $menuItem['linkOptions'] = TbArray::popValue('linkOptions', $tabOptions, array());
3806            $menuItem['htmlOptions'] = TbArray::popValue('htmlOptions', $tabOptions, array());
3807            $items = TbArray::popValue('items', $tabOptions, array());
3808            if (!empty($items)) {
3809                $menuItem['linkOptions']['data-toggle'] = 'dropdown';
3810                $menuItem['items'] = self::normalizeTabs($items, $panes, $i);
3811            } else {
3812                $paneOptions = TbArray::popValue('paneOptions', $tabOptions, array());
3813                $id = $paneOptions['id'] = TbArray::popValue('id', $tabOptions, 'tab_' . ++$i);
3814                $menuItem['linkOptions']['data-toggle'] = 'tab';
3815                $menuItem['url'] = '#' . $id;
3816                self::addCssClass('tab-pane', $paneOptions);
3817                if (TbArray::popValue('fade', $tabOptions, true)) {
3818                    self::addCssClass('fade', $paneOptions);
3819                }
3820                if (TbArray::popValue('active', $tabOptions, false)) {
3821                    self::addCssClass('active in', $paneOptions);
3822                }
3823                $paneContent = TbArray::popValue('content', $tabOptions, '');
3824                $panes[] = self::tag('div', $paneOptions, $paneContent);
3825            }
3826            $menuItems[] = $menuItem;
3827        }
3828        return $menuItems;
3829    }
3830
3831    // Navbar
3832    // http://getbootstrap.com/components/#navbar
3833    // --------------------------------------------------
3834
3835    /**
3836     * Generates a navbar.
3837     * @param string $content the navbar content.
3838     * @param array $htmlOptions additional HTML attributes.
3839     * @return string the generated navbar.
3840     */
3841    public static function navbar($content, $htmlOptions = array())
3842    {
3843        self::addCssClass('navbar', $htmlOptions);
3844        $display = TbArray::popValue('display', $htmlOptions);
3845        if (!empty($display)) {
3846            self::addCssClass('navbar-' . $display, $htmlOptions);
3847        }
3848        $color = TbArray::popValue('color', $htmlOptions, 'default');
3849        if (!empty($color)) {
3850            self::addCssClass('navbar-' . $color, $htmlOptions);
3851        }
3852        $htmlOptions['role'] = 'navigation';
3853        $output = self::openTag('nav', $htmlOptions);
3854        $output .= $content;
3855        $output .= '</nav>';
3856        return $output;
3857    }
3858
3859    /**
3860     * Generates a brand link for the navbar.
3861     * @param string $label the link label text.
3862     * @param string $url the link url.
3863     * @param array $htmlOptions additional HTML attributes.
3864     * @return string the generated link.
3865     */
3866    public static function navbarBrandLink($label, $url, $htmlOptions = array())
3867    {
3868        self::addCssClass('navbar-brand', $htmlOptions);
3869        return self::link($label, $url, $htmlOptions);
3870    }
3871
3872    /**
3873     * Generates a text for the navbar.
3874     * @param string $text the text.
3875     * @param array $htmlOptions additional HTML attributes.
3876     * @param string $tag the HTML tag.
3877     * @return string the generated text block.
3878     */
3879    public static function navbarText($text, $htmlOptions = array(), $tag = 'p')
3880    {
3881        self::addCssClass('navbar-text', $htmlOptions);
3882        return self::tag($tag, $htmlOptions, $text);
3883    }
3884
3885    /**
3886     * Generates a menu divider for the navbar.
3887     * @param array $htmlOptions additional HTML attributes.
3888     * @return string the generated divider.
3889     */
3890    public static function navbarMenuDivider($htmlOptions = array())
3891    {
3892        self::addCssClass('divider-vertical', $htmlOptions);
3893        return self::tag('li', $htmlOptions);
3894    }
3895
3896    /**
3897     * Generates a navbar form.
3898     * @param mixed $action the form action URL.
3899     * @param string $method form method (e.g. post, get).
3900     * @param array $htmlOptions additional HTML attributes
3901     * @return string the generated form.
3902     */
3903    public static function navbarForm($action, $method = 'post', $htmlOptions = array())
3904    {
3905        self::addCssClass('navbar-form', $htmlOptions);
3906        return self::form($action, $method, $htmlOptions);
3907    }
3908
3909    /**
3910     * Generates a navbar search form.
3911     * @param mixed $action the form action URL.
3912     * @param string $method form method (e.g. post, get).
3913     * @param array $htmlOptions additional HTML attributes
3914     * @return string the generated form.
3915     */
3916    public static function navbarSearchForm($action, $method = 'post', $htmlOptions = array())
3917    {
3918        self::addCssClass('navbar-form', $htmlOptions);
3919        return self::searchForm($action, $method, $htmlOptions);
3920    }
3921
3922    /**
3923     * Generates a collapse element.
3924     * @param string $target the CSS selector for the target element.
3925     * @param array $htmlOptions additional HTML attributes.
3926     * @return string the generated icon.
3927     */
3928    public static function navbarCollapseLink($target, $htmlOptions = array())
3929    {
3930        self::addCssClass('btn btn-navbar', $htmlOptions);
3931        $htmlOptions['type'] = 'button';
3932        $htmlOptions['data-toggle'] = 'collapse';
3933        $htmlOptions['data-target'] = $target;
3934        self::addCssClass('navbar-toggle', $htmlOptions);
3935        $content = self::tag('span', array('class' => 'sr-only'), 'Toggle navigation');
3936        $content .= '<span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span>';
3937        return self::tag('button', $htmlOptions, $content);
3938    }
3939
3940    // Breadcrumbs
3941    // http://getbootstrap.com/components/#breadcrumbs
3942    // --------------------------------------------------
3943
3944    /**
3945     * Generates a breadcrumb menu.
3946     * @param array $links the breadcrumb links.
3947     * @param array $htmlOptions additional HTML attributes.
3948     * @return string the generated breadcrumb.
3949     */
3950    public static function breadcrumbs($links, $htmlOptions = array())
3951    {
3952        self::addCssClass('breadcrumb', $htmlOptions);
3953        $output = self::openTag('ol', $htmlOptions);
3954        foreach ($links as $label => $url) {
3955            if (is_string($label)) {
3956                $output .= self::openTag('li');
3957                $output .= self::link($label, $url);
3958                $output .= '</li>';
3959            } else {
3960                $output .= self::tag('li', array('class' => 'active'), $url);
3961            }
3962        }
3963        $output .= '</ol>';
3964        return $output;
3965    }
3966
3967    // Pagination
3968    // http://getbootstrap.com/components/#pagination
3969    // --------------------------------------------------
3970
3971    /**
3972     * Generates a pagination.
3973     * @param array $items the pagination buttons.
3974     * @param array $htmlOptions additional HTML attributes.
3975     * @return string the generated pagination.
3976     */
3977    public static function pagination(array $items, $htmlOptions = array())
3978    {
3979        if (!empty($items)) {
3980            self::addCssClass('pagination', $htmlOptions);
3981            $size = TbArray::popValue('size', $htmlOptions);
3982            if (!empty($size)) {
3983                self::addCssClass('pagination-' . $size, $htmlOptions);
3984            }
3985            $align = TbArray::popValue('align', $htmlOptions);
3986            if (!empty($align)) {
3987                self::addCssClass('pagination-' . $align, $htmlOptions);
3988            }
3989            $output = self::openTag('ul', $htmlOptions);
3990            foreach ($items as $itemOptions) {
3991                // todo: consider removing the support for htmlOptions.
3992                $options = TbArray::popValue('htmlOptions', $itemOptions, array());
3993                if (!empty($options)) {
3994                    $itemOptions = TbArray::merge($options, $itemOptions);
3995                }
3996                $label = TbArray::popValue('label', $itemOptions, '');
3997                $url = TbArray::popValue('url', $itemOptions, false);
3998                $output .= self::paginationLink($label, $url, $itemOptions);
3999            }
4000            $output .= '</ul>';
4001            return $output;
4002        }
4003        return '';
4004    }
4005
4006    /**
4007     * Generates a pagination link.
4008     * @param string $label the link label text.
4009     * @param mixed $url the link url.
4010     * @param array $htmlOptions additional HTML attributes.
4011     * @return string the generated link.
4012     */
4013    public static function paginationLink($label, $url, $htmlOptions = array())
4014    {
4015        $linkOptions = TbArray::popValue('linkOptions', $htmlOptions, array());
4016        if (TbArray::popValue('active', $htmlOptions, false)) {
4017            self::addCssClass('active', $htmlOptions);
4018            $label .= ' ' . self::tag('span', array('class' => 'sr-only'), '(current)');
4019        }
4020        if (TbArray::popValue('disabled', $htmlOptions, false)) {
4021            self::addCssClass('disabled', $htmlOptions);
4022        }
4023        $content = self::link($label, $url, $linkOptions);
4024        return self::tag('li', $htmlOptions, $content);
4025    }
4026
4027    /**
4028     * Generates a pager.
4029     * @param array $links the pager buttons.
4030     * @param array $htmlOptions additional HTML attributes.
4031     * @return string the generated pager.
4032     */
4033    public static function pager(array $links, $htmlOptions = array())
4034    {
4035        if (!empty($links)) {
4036            self::addCssClass('pager', $htmlOptions);
4037            $output = self::openTag('ul', $htmlOptions);
4038            foreach ($links as $itemOptions) {
4039                // todo: consider removing the support for htmlOptions.
4040                $options = TbArray::popValue('htmlOptions', $itemOptions, array());
4041                if (!empty($options)) {
4042                    $itemOptions = TbArray::merge($options, $itemOptions);
4043                }
4044                $label = TbArray::popValue('label', $itemOptions, '');
4045                $url = TbArray::popValue('url', $itemOptions, false);
4046                $output .= self::pagerLink($label, $url, $itemOptions);
4047            }
4048            $output .= '</ul>';
4049            return $output;
4050        }
4051        return '';
4052    }
4053
4054    /**
4055     * Generates a pager link.
4056     * @param string $label the link label text.
4057     * @param mixed $url the link url.
4058     * @param array $htmlOptions additional HTML attributes.
4059     * @return string the generated link.
4060     */
4061    public static function pagerLink($label, $url, $htmlOptions = array())
4062    {
4063        $linkOptions = TbArray::popValue('linkOptions', $htmlOptions, array());
4064        if (TbArray::popValue('previous', $htmlOptions, false)) {
4065            self::addCssClass('previous', $htmlOptions);
4066        }
4067        if (TbArray::popValue('next', $htmlOptions, false)) {
4068            self::addCssClass('next', $htmlOptions);
4069        }
4070        if (TbArray::popValue('disabled', $htmlOptions, false)) {
4071            self::addCssClass('disabled', $htmlOptions);
4072        }
4073        $content = self::link($label, $url, $linkOptions);
4074        return self::tag('li', $htmlOptions, $content);
4075    }
4076
4077    // Labels and badges
4078    // http://getbootstrap.com/components/#labels
4079    // --------------------------------------------------
4080
4081    /**
4082     * Generates a label span.
4083     * @param string $label the label text.
4084     * @param array $htmlOptions additional HTML attributes.
4085     * @return string the generated span.
4086     */
4087    public static function labelTb($label, $htmlOptions = array())
4088    {
4089        self::addCssClass('label', $htmlOptions);
4090        $color = TbArray::popValue('color', $htmlOptions);
4091        if (!empty($color)) {
4092            self::addCssClass('label-' . $color, $htmlOptions);
4093        } else {
4094            self::addCssClass('label-default', $htmlOptions);
4095        }
4096        return self::tag('span', $htmlOptions, $label);
4097    }
4098
4099    /**
4100     * Generates a badge span.
4101     * @param string $label the badge text.
4102     * @param array $htmlOptions additional HTML attributes.
4103     * @return string the generated span.
4104     */
4105    public static function badge($label, $htmlOptions = array())
4106    {
4107        self::addCssClass('badge', $htmlOptions);
4108        return self::tag('span', $htmlOptions, $label);
4109    }
4110
4111    // Typography
4112    // http://getbootstrap.com/components/#jumbotron
4113    // http://getbootstrap.com/components/#page-header
4114    // --------------------------------------------------
4115
4116    /**
4117     * Generates a jumbotron unit.
4118     * @param string $heading the heading text.
4119     * @param string $content the content text.
4120     * @param array $htmlOptions additional HTML attributes.
4121     * @return string the generated hero unit.
4122     */
4123    public static function heroUnit($heading, $content, $htmlOptions = array())
4124    {
4125        self::addCssClass('jumbotron', $htmlOptions);
4126        $headingOptions = TbArray::popValue('headingOptions', $htmlOptions, array());
4127        $output = self::openTag('div', $htmlOptions);
4128        $output .= self::tag('h1', $headingOptions, $heading);
4129        $output .= $content;
4130        $output .= '</div>';
4131        return $output;
4132    }
4133
4134    /**
4135     * Generates a pager header.
4136     * @param string $heading the heading text.
4137     * @param string $subtext the subtext.
4138     * @param array $htmlOptions additional HTML attributes.
4139     * @return string the generated pager header.
4140     */
4141    public static function pageHeader($heading, $subtext, $htmlOptions = array())
4142    {
4143        self::addCssClass('page-header', $htmlOptions);
4144        $headerOptions = TbArray::popValue('headerOptions', $htmlOptions, array());
4145        $subtextOptions = TbArray::popValue('subtextOptions', $htmlOptions, array());
4146        $output = self::openTag('div', $htmlOptions);
4147        $output .= self::openTag('h1', $headerOptions);
4148        $output .= parent::encode($heading) . ' ' . self::tag('small', $subtextOptions, $subtext);
4149        $output .= '</h1>';
4150        $output .= '</div>';
4151        return $output;
4152    }
4153
4154    // Thumbnails
4155    // http://getbootstrap.com/components/#thumbnails
4156    // --------------------------------------------------
4157
4158    /**
4159     * Generates a list of thumbnails.
4160     * @param array $thumbnails the list configuration.
4161     * @param array $htmlOptions additional HTML attributes.
4162     * @return string the generated thumbnails.
4163     */
4164    public static function thumbnails(array $thumbnails, $htmlOptions = array())
4165    {
4166        if (!empty($thumbnails)) {
4167            self::addCssClass('thumbnails', $htmlOptions);
4168            $defaultSpan = TbArray::popValue('span', $htmlOptions, 3);
4169            $output = self::openTag('ul', $htmlOptions);
4170            foreach ($thumbnails as $thumbnailOptions) {
4171                if (isset($thumbnailOptions['visible']) && $thumbnailOptions['visible'] === false) {
4172                    continue;
4173                }
4174                // todo: consider removing the support for htmlOptions.
4175                $options = TbArray::popValue('htmlOptions', $thumbnailOptions, array());
4176                if (!empty($options)) {
4177                    $thumbnailOptions = TbArray::merge($options, $thumbnailOptions);
4178                }
4179                $thumbnailOptions['itemOptions']['span'] = TbArray::popValue('span', $thumbnailOptions, $defaultSpan);
4180                $caption = TbArray::popValue('caption', $thumbnailOptions, '');
4181                $captionOptions = TbArray::popValue('captionOptions', $thumbnailOptions, array());
4182                self::addCssClass('caption', $captionOptions);
4183                $label = TbArray::popValue('label', $thumbnailOptions);
4184                $labelOptions = TbArray::popValue('labelOptions', $thumbnailOptions, array());
4185                if (!empty($label)) {
4186                    $caption = self::tag('h3', $labelOptions, $label) . $caption;
4187                }
4188                $content = !empty($caption) ? self::tag('div', $captionOptions, $caption) : '';
4189                $image = TbArray::popValue('image', $thumbnailOptions);
4190                $imageOptions = TbArray::popValue('imageOptions', $thumbnailOptions, array());
4191                $imageAlt = TbArray::popValue('alt', $imageOptions, '');
4192                if (!empty($image)) {
4193                    $content = parent::image($image, $imageAlt, $imageOptions) . $content;
4194                }
4195                $url = TbArray::popValue('url', $thumbnailOptions, false);
4196                $output .= $url !== false
4197                    ? self::thumbnailLink($content, $url, $thumbnailOptions)
4198                    : self::thumbnail($content, $thumbnailOptions);
4199            }
4200            $output .= '</ul>';
4201            return $output;
4202        } else {
4203            return '';
4204        }
4205    }
4206
4207    /**
4208     * Generates a thumbnail.
4209     * @param string $content the thumbnail content.
4210     * @param array $htmlOptions additional HTML attributes.
4211     * @return string the generated thumbnail.
4212     */
4213    public static function thumbnail($content, $htmlOptions = array())
4214    {
4215        $itemOptions = TbArray::popValue('itemOptions', $htmlOptions, array());
4216        self::addCssClass('thumbnail', $htmlOptions);
4217        $output = self::openTag('li', $itemOptions);
4218        $output .= self::tag('div', $htmlOptions, $content);
4219        $output .= '</li>';
4220        return $output;
4221    }
4222
4223    /**
4224     * Generates a link thumbnail.
4225     * @param string $content the thumbnail content.
4226     * @param mixed $url the url that the thumbnail links to.
4227     * @param array $htmlOptions additional HTML attributes.
4228     * @return string the generated thumbnail.
4229     */
4230    public static function thumbnailLink($content, $url = '#', $htmlOptions = array())
4231    {
4232        $itemOptions = TbArray::popValue('itemOptions', $htmlOptions, array());
4233        self::addCssClass('thumbnail', $htmlOptions);
4234        $content = self::link($content, $url, $htmlOptions);
4235        return self::tag('li', $itemOptions, $content);
4236    }
4237
4238    // Alerts
4239    // http://getbootstrap.com/components/#alerts
4240    // --------------------------------------------------
4241
4242    /**
4243     * Generates an alert.
4244     * @param string $color the color of the alert.
4245     * @param string $message the message to display.
4246     * @param array $htmlOptions additional HTML options.
4247     * @return string the generated alert.
4248     */
4249    public static function alert($color, $message, $htmlOptions = array())
4250    {
4251        self::addCssClass('alert', $htmlOptions);
4252        if (!empty($color)) {
4253            self::addCssClass('alert-' . $color, $htmlOptions);
4254        }
4255        if (TbArray::popValue('in', $htmlOptions, true)) {
4256            self::addCssClass('in', $htmlOptions);
4257        }
4258        if (TbArray::popValue('block', $htmlOptions, false)) {
4259            self::addCssClass('alert-block', $htmlOptions);
4260        }
4261        if (TbArray::popValue('fade', $htmlOptions, true)) {
4262            self::addCssClass('fade', $htmlOptions);
4263        }
4264        $closeText = TbArray::popValue('closeText', $htmlOptions, self::CLOSE_TEXT);
4265        $closeOptions = TbArray::popValue('closeOptions', $htmlOptions, array());
4266        $closeOptions['dismiss'] = self::CLOSE_DISMISS_ALERT;
4267        $output = self::openTag('div', $htmlOptions);
4268        $output .= $closeText !== false ? self::closeLink($closeText, '#', $closeOptions) : '';
4269        $output .= $message;
4270        $output .= '</div>';
4271        return $output;
4272    }
4273
4274    /**
4275     * Generates an alert block.
4276     * @param string $color the color of the alert.
4277     * @param string $message the message to display.
4278     * @param array $htmlOptions additional HTML options.
4279     * @return string the generated alert.
4280     */
4281    public static function blockAlert($color, $message, $htmlOptions = array())
4282    {
4283        $htmlOptions['block'] = true;
4284        return self::alert($color, $message, $htmlOptions);
4285    }
4286
4287    // Progress bars
4288    // http://getbootstrap.com/components/#progress
4289    // --------------------------------------------------
4290
4291    /**
4292     * Generates a progress bar.
4293     * @param integer $width the progress in percent.
4294     * @param array $htmlOptions additional HTML attributes.
4295     * @return string the generated progress bar.
4296     */
4297    public static function progressBar($width = 0, $htmlOptions = array())
4298    {
4299        self::addCssClass('progress', $htmlOptions);
4300        if (TbArray::popValue('striped', $htmlOptions, false)) {
4301            self::addCssClass('progress-striped', $htmlOptions);
4302        }
4303        if (TbArray::popValue('animated', $htmlOptions, false)) {
4304            self::addCssClass('active', $htmlOptions);
4305        }
4306        $barOptions = TbArray::popValue('barOptions', $htmlOptions, array());
4307        $color = TbArray::popValue('color', $htmlOptions);
4308        if (!empty($color)) {
4309            $barOptions['color'] = $color;
4310        }
4311        $content = TbArray::popValue('content', $htmlOptions);
4312        if (!empty($content)) {
4313            $barOptions['content'] = $content;
4314        }
4315        $content = self::bar($width, $barOptions);
4316        return self::tag('div', $htmlOptions, $content);
4317    }
4318
4319    /**
4320     * Generates a striped progress bar.
4321     * @param integer $width the progress in percent.
4322     * @param array $htmlOptions additional HTML attributes.
4323     * @return string the generated progress bar.
4324     */
4325    public static function stripedProgressBar($width = 0, $htmlOptions = array())
4326    {
4327        $htmlOptions['striped'] = true;
4328        return self::progressBar($width, $htmlOptions);
4329    }
4330
4331    /**
4332     * Generates an animated progress bar.
4333     * @param integer $width the progress in percent.
4334     * @param array $htmlOptions additional HTML attributes.
4335     * @return string the generated progress bar.
4336     */
4337    public static function animatedProgressBar($width = 0, $htmlOptions = array())
4338    {
4339        $htmlOptions['animated'] = true;
4340        return self::stripedProgressBar($width, $htmlOptions);
4341    }
4342
4343    /**
4344     * Generates a stacked progress bar.
4345     * @param array $bars the bar configurations.
4346     * @param array $htmlOptions additional HTML attributes.
4347     * @return string the generated progress bar.
4348     */
4349    public static function stackedProgressBar(array $bars, $htmlOptions = array())
4350    {
4351        if (!empty($bars)) {
4352            self::addCssClass('progress', $htmlOptions);
4353            $output = self::openTag('div', $htmlOptions);
4354            $totalWidth = 0;
4355            foreach ($bars as $barOptions) {
4356                if (isset($barOptions['visible']) && !$barOptions['visible']) {
4357                    continue;
4358                }
4359                $width = TbArray::popValue('width', $barOptions, 0);
4360                $tmp = $totalWidth;
4361                $totalWidth += $width;
4362                if ($totalWidth > 100) {
4363                    $width = 100 - $tmp;
4364                }
4365                $output .= self::bar($width, $barOptions);
4366            }
4367            $output .= '</div>';
4368            return $output;
4369        }
4370        return '';
4371    }
4372
4373    /**
4374     * Generates a progress bar.
4375     * @param integer $width the progress in percent.
4376     * @param array $htmlOptions additional HTML attributes.
4377     * @return string the generated bar.
4378     */
4379    protected static function bar($width = 0, $htmlOptions = array())
4380    {
4381        self::addCssClass('progress-bar', $htmlOptions);
4382        $color = TbArray::popValue('color', $htmlOptions);
4383        if (!empty($color)) {
4384            self::addCssClass('progress-bar-' . $color, $htmlOptions);
4385        }
4386        if ($width < 0) {
4387            $width = 0;
4388        }
4389        if ($width > 100) {
4390            $width = 100;
4391        }
4392        if ($width > 0) {
4393            $width .= '%';
4394        }
4395        self::addCssStyle("width: {$width};", $htmlOptions);
4396        $content = TbArray::popValue('content', $htmlOptions, '');
4397        return self::tag('div', $htmlOptions, $content);
4398    }
4399
4400    // Media objects
4401    // http://getbootstrap.com/components/#media
4402    // --------------------------------------------------
4403
4404    /**
4405     * Generates a list of media objects.
4406     * @param array $items item configurations.
4407     * @param array $htmlOptions additional HTML attributes.
4408     * @return string generated list.
4409     */
4410    public static function mediaList(array $items, $htmlOptions = array())
4411    {
4412        if (!empty($items)) {
4413            self::addCssClass('media-list', $htmlOptions);
4414            $output = '';
4415            $output .= self::openTag('ul', $htmlOptions);
4416            $output .= self::medias($items, 'li');
4417            $output .= '</ul>';
4418            return $output;
4419        }
4420        return '';
4421    }
4422
4423    /**
4424     * Generates multiple media objects.
4425     * @param array $items item configurations.
4426     * @param string $tag the item tag name.
4427     * @return string generated objects.
4428     */
4429    public static function medias(array $items, $tag = 'div')
4430    {
4431        if (!empty($items)) {
4432            $output = '';
4433            foreach ($items as $itemOptions) {
4434                if (isset($itemOptions['visible']) && $itemOptions['visible'] === false) {
4435                    continue;
4436                }
4437                // todo: consider removing the support for htmlOptions.
4438                $options = TbArray::popValue('htmlOptions', $itemOptions, array());
4439                if (!empty($options)) {
4440                    $itemOptions = TbArray::merge($options, $itemOptions);
4441                }
4442                $image = TbArray::popValue('image', $itemOptions);
4443                $heading = TbArray::popValue('heading', $itemOptions, '');
4444                $content = TbArray::popValue('content', $itemOptions, '');
4445                TbArray::defaultValue('tag', $tag, $itemOptions);
4446                $output .= self::media($image, $heading, $content, $itemOptions);
4447            }
4448            return $output;
4449        }
4450        return '';
4451    }
4452
4453    /**
4454     * Generates a single media object.
4455     * @param string $image the image url.
4456     * @param string $heading the heading text.
4457     * @param string $content the content text.
4458     * @param array $htmlOptions additional HTML attributes.
4459     * @return string the media object.
4460     */
4461    public static function media($image, $heading, $content, $htmlOptions = array())
4462    {
4463        $tag = TbArray::popValue('tag', $htmlOptions, 'div');
4464        self::addCssClass('media', $htmlOptions);
4465        $linkOptions = TbArray::popValue('linkOptions', $htmlOptions, array());
4466        TbArray::defaultValue('pull', self::PULL_LEFT, $linkOptions);
4467        $imageOptions = TbArray::popValue('imageOptions', $htmlOptions, array());
4468        self::addCssClass('media-object', $imageOptions);
4469        $contentOptions = TbArray::popValue('contentOptions', $htmlOptions, array());
4470        self::addCssClass('media-body', $contentOptions);
4471        $headingOptions = TbArray::popValue('headingOptions', $htmlOptions, array());
4472        self::addCssClass('media-heading', $headingOptions);
4473        $items = TbArray::popValue('items', $htmlOptions);
4474
4475        $output = self::openTag($tag, $htmlOptions);
4476        $alt = TbArray::popValue('alt', $imageOptions, '');
4477        $href = TbArray::popValue('href', $linkOptions, '#');
4478        if (!empty($image)) {
4479            $output .= self::link(parent::image($image, $alt, $imageOptions), $href, $linkOptions);
4480        }
4481        $output .= self::openTag('div', $contentOptions);
4482        $output .= self::tag('h4', $headingOptions, $heading);
4483        $output .= $content;
4484        if (!empty($items)) {
4485            $output .= self::medias($items);
4486        }
4487        $output .= '</div>';
4488        $output .= self::closeTag($tag);
4489        return $output;
4490    }
4491
4492    // Misc
4493    // http://getbootstrap.com/components/#wells
4494    // --------------------------------------------------
4495
4496    /**
4497     * Generates a well element.
4498     * @param string $content the well content.
4499     * @param array $htmlOptions additional HTML attributes.
4500     * @return string the generated well.
4501     */
4502    public static function well($content, $htmlOptions = array())
4503    {
4504        self::addCssClass('well', $htmlOptions);
4505        $size = TbArray::popValue('size', $htmlOptions);
4506        if (!empty($size)) {
4507            self::addCssClass('well-' . $size, $htmlOptions);
4508        }
4509        return self::tag('div', $htmlOptions, $content);
4510    }
4511
4512    /**
4513     * Generates a close link.
4514     * @param string $label the link label text.
4515     * @param mixed $url the link url.
4516     * @param array $htmlOptions additional HTML attributes.
4517     * @return string the generated link.
4518     */
4519    public static function closeLink($label = self::CLOSE_TEXT, $url = '#', $htmlOptions = array())
4520    {
4521        $htmlOptions['href'] = $url;
4522        return self::close('a', $label, $htmlOptions);
4523    }
4524
4525    /**
4526     * Generates a close button.
4527     * @param string $label the button label text.
4528     * @param array $htmlOptions the HTML options for the button.
4529     * @return string the generated button.
4530     */
4531    public static function closeButton($label = self::CLOSE_TEXT, $htmlOptions = array())
4532    {
4533        return self::close('button', $label, $htmlOptions);
4534    }
4535
4536    /**
4537     * Generates a close element.
4538     * @param string $tag the tag name.
4539     * @param string $label the element label text.
4540     * @param array $htmlOptions additional HTML attributes.
4541     * @return string the generated element.
4542     */
4543    protected static function close($tag, $label, $htmlOptions = array())
4544    {
4545        self::addCssClass('close', $htmlOptions);
4546        $dismiss = TbArray::popValue('dismiss', $htmlOptions);
4547        if (!empty($dismiss)) {
4548            $htmlOptions['data-dismiss'] = $dismiss;
4549        }
4550        $htmlOptions['type'] = 'button';
4551        return self::tag($tag, $htmlOptions, $label);
4552    }
4553
4554    /**
4555     * Generates a collapse link.
4556     * @param string $label the link label.
4557     * @param string $target the CSS selector.
4558     * @param array $htmlOptions additional HTML attributes.
4559     * @return string the generated link.
4560     */
4561    public static function collapseLink($label, $target, $htmlOptions = array())
4562    {
4563        $htmlOptions['data-toggle'] = 'collapse';
4564        return self::link($label, $target, $htmlOptions);
4565    }
4566
4567    //
4568    // JAVASCRIPT
4569    // --------------------------------------------------
4570
4571    // Modals
4572    // http://getbootstrap.com/javascript/#modals
4573    // --------------------------------------------------
4574
4575    /**
4576     * Generates a modal header.
4577     * @param string $content the header content.
4578     * @param array $htmlOptions additional HTML attributes.
4579     * @return string the generated header.
4580     */
4581    public static function modalHeader($content, $htmlOptions = array())
4582    {
4583        self::addCssClass('modal-header', $htmlOptions);
4584        $closeOptions = TbArray::popValue('closeOptions', $htmlOptions, array());
4585        $closeOptions['dismiss'] = 'modal';
4586        $headingOptions = TbArray::popValue('headingOptions', $htmlOptions, array());
4587        $closeLabel = TbArray::popValue('closeLabel', $htmlOptions, self::CLOSE_TEXT);
4588        $closeButton = self::closeButton($closeLabel, $closeOptions);
4589        self::addCssClass('modal-title', $headingOptions);
4590        $header = self::tag('h4', $headingOptions, $content);
4591        return self::tag('div', $htmlOptions, $closeButton . $header);
4592    }
4593
4594    /**
4595     * Generates a modal body.
4596     * @param string $content the body content.
4597     * @param array $htmlOptions additional HTML attributes.
4598     * @return string the generated body.
4599     */
4600    public static function modalBody($content, $htmlOptions = array())
4601    {
4602        self::addCssClass('modal-body', $htmlOptions);
4603        return self::tag('div', $htmlOptions, $content);
4604    }
4605
4606    /**
4607     * Generates a modal footer.
4608     * @param string $content the footer content.
4609     * @param array $htmlOptions additional HTML attributes.
4610     * @return string the generated footer.
4611     */
4612    public static function modalFooter($content, $htmlOptions = array())
4613    {
4614        self::addCssClass('modal-footer', $htmlOptions);
4615        return self::tag('div', $htmlOptions, $content);
4616    }
4617
4618    // Tooltips and Popovers
4619    // http://getbootstrap.com/javascript/#tooltips
4620    // http://getbootstrap.com/javascript/#popovers
4621    // --------------------------------------------------
4622
4623    /**
4624     * Generates a tooltip.
4625     * @param string $label the tooltip link label text.
4626     * @param mixed $url the link url.
4627     * @param string $content the tooltip content text.
4628     * @param array $htmlOptions additional HTML attributes.
4629     * @return string the generated tooltip.
4630     */
4631    public static function tooltip($label, $url, $content, $htmlOptions = array())
4632    {
4633        $htmlOptions['rel'] = 'tooltip';
4634        return self::tooltipPopover($label, $url, $content, $htmlOptions);
4635    }
4636
4637    /**
4638     * Generates a popover.
4639     * @param string $label the popover link label text.
4640     * @param string $title the popover title text.
4641     * @param string $content the popover content text.
4642     * @param array $htmlOptions additional HTML attributes.
4643     * @return string the generated popover.
4644     */
4645    public static function popover($label, $title, $content, $htmlOptions = array())
4646    {
4647        $htmlOptions['rel'] = 'popover';
4648        $htmlOptions['data-content'] = $content;
4649        $htmlOptions['data-toggle'] = 'popover';
4650        return self::tooltipPopover($label, '#', $title, $htmlOptions);
4651    }
4652
4653    /**
4654     * Generates a base tooltip.
4655     * @param string $label the tooltip link label text.
4656     * @param mixed $url the link url.
4657     * @param string $title the tooltip title text.
4658     * @param array $htmlOptions additional HTML attributes.
4659     * @return string the generated tooltip.
4660     */
4661    protected static function tooltipPopover($label, $url, $title, $htmlOptions)
4662    {
4663        $htmlOptions['title'] = $title;
4664        if (TbArray::popValue('animation', $htmlOptions)) {
4665            $htmlOptions['data-animation'] = 'true';
4666        }
4667        if (TbArray::popValue('html', $htmlOptions)) {
4668            $htmlOptions['data-html'] = 'true';
4669        }
4670        $selector = TbArray::popValue('selector', $htmlOptions);
4671        if (!empty($selector)) {
4672            $htmlOptions['data-selector'] = $selector;
4673        }
4674        $placement = TbArray::popValue('placement', $htmlOptions);
4675        if (!empty($placement)) {
4676            $htmlOptions['data-placement'] = $placement;
4677        }
4678        $trigger = TbArray::popValue('trigger', $htmlOptions);
4679        if (!empty($trigger)) {
4680            $htmlOptions['data-trigger'] = $trigger;
4681        }
4682        if (($delay = TbArray::popValue('delay', $htmlOptions)) !== null) {
4683            $htmlOptions['data-delay'] = $delay;
4684        }
4685        return self::link($label, $url, $htmlOptions);
4686    }
4687
4688    // Carousel
4689    // http://getbootstrap.com/javascript/#carousel
4690    // --------------------------------------------------
4691
4692    /**
4693     * Generates an image carousel.
4694     * @param array $items the item configurations.
4695     * @param array $htmlOptions additional HTML attributes.
4696     * @return string the generated carousel.
4697     */
4698    public static function carousel(array $items, $htmlOptions = array())
4699    {
4700        if (!empty($items)) {
4701            $id = TbArray::getValue('id', $htmlOptions, parent::ID_PREFIX . parent::$count++);
4702            TbArray::defaultValue('id', $id, $htmlOptions);
4703            $selector = '#' . $id;
4704            self::addCssClass('carousel', $htmlOptions);
4705            if (TbArray::popValue('slide', $htmlOptions, true)) {
4706                self::addCssClass('slide', $htmlOptions);
4707            }
4708            $interval = TbArray::popValue('data-interval', $htmlOptions);
4709            if ($interval) {
4710                $htmlOptions['data-interval'] = $interval;
4711            }
4712            $pause = TbArray::popValue('data-pause', $htmlOptions);
4713            if ($pause) {
4714                $htmlOptions['data-pause'] = $pause;
4715            }
4716            $indicatorOptions = TbArray::popValue('indicatorOptions', $htmlOptions, array());
4717            $innerOptions = TbArray::popValue('innerOptions', $htmlOptions, array());
4718            self::addCssClass('carousel-inner', $innerOptions);
4719            $prevOptions = TbArray::popValue('prevOptions', $htmlOptions, array());
4720            $prevLabel = TbArray::popValue('label', $prevOptions, '&lsaquo;');
4721            $nextOptions = TbArray::popValue('nextOptions', $htmlOptions, array());
4722            $nextLabel = TbArray::popValue('label', $nextOptions, '&rsaquo;');
4723            $hidePrevAndNext = TbArray::popValue('hidePrevAndNext', $htmlOptions, false);
4724            $output = self::openTag('div', $htmlOptions);
4725            $output .= self::carouselIndicators($selector, count($items), $indicatorOptions);
4726            $output .= self::openTag('div', $innerOptions);
4727            foreach ($items as $i => $itemOptions) {
4728                if (isset($itemOptions['visible']) && $itemOptions['visible'] === false) {
4729                    continue;
4730                }
4731                if ($i === 0) { // first item should be active
4732                    self::addCssClass('active', $itemOptions);
4733                }
4734                $content = TbArray::popValue('content', $itemOptions, '');
4735                $image = TbArray::popValue('image', $itemOptions, '');
4736                $imageOptions = TbArray::popValue('imageOptions', $itemOptions, array());
4737                $imageAlt = TbArray::popValue('alt', $imageOptions, '');
4738                if (!empty($image)) {
4739                    $content = parent::image($image, $imageAlt, $imageOptions);
4740                }
4741                $label = TbArray::popValue('label', $itemOptions);
4742                $caption = TbArray::popValue('caption', $itemOptions);
4743                $output .= self::carouselItem($content, $label, $caption, $itemOptions);
4744            }
4745            $output .= '</div>';
4746            if (!$hidePrevAndNext) {
4747                $output .= self::carouselPrevLink($prevLabel, $selector, $prevOptions);
4748                $output .= self::carouselNextLink($nextLabel, $selector, $nextOptions);
4749            }
4750            $output .= '</div>';
4751            return $output;
4752        }
4753        return '';
4754    }
4755
4756    /**
4757     * Generates a carousel item.
4758     * @param string $content the content.
4759     * @param string $label the item label text.
4760     * @param string $caption the item caption text.
4761     * @param array $htmlOptions additional HTML attributes.
4762     * @return string the generated item.
4763     */
4764    public static function carouselItem($content, $label, $caption, $htmlOptions = array())
4765    {
4766        self::addCssClass('item', $htmlOptions);
4767        $overlayOptions = TbArray::popValue('overlayOptions', $htmlOptions, array());
4768        self::addCssClass('carousel-caption', $overlayOptions);
4769        $labelOptions = TbArray::popValue('labelOptions', $htmlOptions, array());
4770        $captionOptions = TbArray::popValue('captionOptions', $htmlOptions, array());
4771        $url = TbArray::popValue('url', $htmlOptions, false);
4772        if ($url !== false) {
4773            $content = self::link($content, $url);
4774        }
4775        $output = self::openTag('div', $htmlOptions);
4776        $output .= $content;
4777        if (isset($label) || isset($caption)) {
4778            $output .= self::openTag('div', $overlayOptions);
4779            if ($label) {
4780                $output .= self::tag('h4', $labelOptions, $label);
4781            }
4782            if ($caption) {
4783                $output .= self::tag('p', $captionOptions, $caption);
4784            }
4785            $output .= '</div>';
4786        }
4787        $output .= '</div>';
4788        return $output;
4789    }
4790
4791    /**
4792     * Generates a previous link for the carousel.
4793     * @param string $label the link label text.
4794     * @param mixed $url the link url.
4795     * @param array $htmlOptions additional HTML attributes.
4796     * @return string the generated link.
4797     */
4798    public static function carouselPrevLink($label, $url = '#', $htmlOptions = array())
4799    {
4800        self::addCssClass('carousel-control left', $htmlOptions);
4801        $htmlOptions['data-slide'] = 'prev';
4802        return self::link($label, $url, $htmlOptions);
4803    }
4804
4805    /**
4806     * Generates a next link for the carousel.
4807     * @param string $label the link label text.
4808     * @param mixed $url the link url.
4809     * @param array $htmlOptions additional HTML attributes.
4810     * @return string the generated link.
4811     */
4812    public static function carouselNextLink($label, $url = '#', $htmlOptions = array())
4813    {
4814        self::addCssClass('carousel-control right', $htmlOptions);
4815        $htmlOptions['data-slide'] = 'next';
4816        return self::link($label, $url, $htmlOptions);
4817    }
4818
4819    /**
4820     * Generates an indicator for the carousel.
4821     * @param string $target the CSS selector for the target element.
4822     * @param integer $numSlides the number of slides.
4823     * @param array $htmlOptions additional HTML attributes.
4824     * @return string the generated indicators.
4825     */
4826    public static function carouselIndicators($target, $numSlides, $htmlOptions = array())
4827    {
4828        self::addCssClass('carousel-indicators', $htmlOptions);
4829        $output = self::openTag('ol', $htmlOptions);
4830        for ($i = 0; $i < $numSlides; $i++) {
4831            $itemOptions = array('data-target' => $target, 'data-slide-to' => $i);
4832            if ($i === 0) {
4833                $itemOptions['class'] = 'active';
4834            }
4835            $output .= self::tag('li', $itemOptions, '', true);
4836        }
4837        $output .= '</ol>';
4838        return $output;
4839    }
4840
4841    // UTILITIES
4842    // --------------------------------------------------
4843
4844    /**
4845     * Appends new class names to the given options..
4846     * @param mixed $className the class(es) to append.
4847     * @param array $htmlOptions the options.
4848     * @return array the options.
4849     */
4850    public static function addCssClass($className, &$htmlOptions)
4851    {
4852        // Always operate on arrays
4853        if (is_string($className)) {
4854            $className = explode(' ', $className);
4855        }
4856        if (isset($htmlOptions['class'])) {
4857            $classes = array_filter(explode(' ', $htmlOptions['class']));
4858            foreach ($className as $class) {
4859                $class = trim($class);
4860                // Don't add the class if it already exists
4861                if (array_search($class, $classes) === false) {
4862                    $classes[] = $class;
4863                }
4864            }
4865            $className = $classes;
4866        }
4867        $htmlOptions['class'] = implode(' ', $className);
4868    }
4869
4870    /**
4871     * Appends a CSS style string to the given options.
4872     * @param string $style the CSS style string.
4873     * @param array $htmlOptions the options.
4874     * @return array the options.
4875     */
4876    public static function addCssStyle($style, &$htmlOptions)
4877    {
4878        if (is_array($style)) {
4879            $style = implode('; ', $style);
4880        }
4881        $style = rtrim($style, ';');
4882        $htmlOptions['style'] = isset($htmlOptions['style'])
4883            ? rtrim($htmlOptions['style'], ';') . '; ' . $style
4884            : $style;
4885    }
4886
4887    /**
4888     * Adds the grid span class to the given options is applicable. BS3 no longer use span classes. During the BS3
4889     * transition, this will use the col-md-* CSS class.
4890     * @param array $htmlOptions the HTML attributes.
4891     * @deprecated
4892     */
4893    protected static function addSpanClass(&$htmlOptions)
4894    {
4895        // todo: remove this method
4896        $span = TbArray::popValue('span', $htmlOptions);
4897        if (!empty($span)) {
4898            self::addCssClass('col-md-' . $span, $htmlOptions);
4899        }
4900    }
4901
4902    /**
4903     * Adds the appropriate column class to the given options applicable. The available columns are 'xs', 'sm', 'md',
4904     * 'lg' for extra small, small, medium, and large to be used for the appropriate screen sizes. It is also possible
4905     * to prevent your columns from stacking on smaller devices by combining a small column with a larger column:
4906     * <code>
4907     *  $htmlOptions = array(
4908     *      'xs' => 12,
4909     *      'md' => 8,
4910     * )
4911     * </code>
4912     * Both classes will be applied.
4913     * @param $htmlOptions
4914     */
4915    protected static function addColClass(&$htmlOptions)
4916    {
4917        $colSizes = array(self::COLUMN_SIZE_XS, self::COLUMN_SIZE_SM, self::COLUMN_SIZE_MD, self::COLUMN_SIZE_LG);
4918
4919        // It's possible to stack an xs and md grid together
4920        foreach ($colSizes as $colSize) {
4921            $span = TbArray::popValue($colSize, $htmlOptions);
4922            if (!empty($span)) {
4923                self::addCssClass('col-' . $colSize . '-' . $span, $htmlOptions);
4924            }
4925        }
4926    }
4927
4928    /**
4929     * Adds the pull class to the given options is applicable.
4930     * @param array $htmlOptions the HTML attributes.
4931     */
4932    protected static function addPullClass(&$htmlOptions)
4933    {
4934        $pull = TbArray::popValue('pull', $htmlOptions);
4935        if (!empty($pull)) {
4936            self::addCssClass('pull-' . $pull, $htmlOptions);
4937        }
4938    }
4939
4940    /**
4941     * Adds the text align class to the given options if applicable.
4942     * @param array $htmlOptions the HTML attributes.
4943     */
4944    protected static function addTextAlignClass(&$htmlOptions)
4945    {
4946        $align = TbArray::popValue('textAlign', $htmlOptions);
4947        if (!empty($align)) {
4948            self::addCssClass('text-' . $align, $htmlOptions);
4949        }
4950    }
4951
4952    /**
4953     * Switches the column class to and from the col width itself to its offset counterpart. For example, passing in
4954     * col-md-2 would be switched to col-md-offset-2
4955     * @param string $class
4956     * @return string
4957     */
4958    protected static function switchOffsetCol($class)
4959    {
4960        // todo: why would you want to do this
4961        if (strpos($class, 'offset') !== false) {
4962            return str_replace('-offset', '', $class);
4963        } else {
4964            preg_match('/^(col-.*-)([0-9]*)$/', $class, $matches);
4965            return $matches[1] . 'offset-' . $matches[2];
4966        }
4967    }
4968
4969    /**
4970     * Nearly identical to {@link switchOffsetCol()} except it forces the class to be returned as its offset
4971     * counterpart. It is also safe to pass in a class that is already an offset and it will just re-return it. For
4972     * example, passing in col-md-2 will return col-md-offset-2. Passing in col-md-offset-4 will still return
4973     * col-md-offset-4.
4974     * @param string $class
4975     * @return string
4976     */
4977    protected static function switchColToOffset($class)
4978    {
4979        // todo: why would you want to do this
4980        if ((strpos($class, 'offset') === false) && (preg_match('/^(col-.*-)([0-9]*)$/', $class, $matches) > 0)) {
4981            return $matches[1] . 'offset-' . $matches[2];
4982        } else {
4983            return $class;
4984        }
4985    }
4986
4987    /**
4988     * Nearly identical to {@link switchOffsetCol()} except it forces teh class to be returned as its column
4989     * (e.g. "span") width counterpart. It is also safe to pass in a class that is already the column width and it will
4990     * re-return it. For example, passing in col-md-offset-2 will return col-md-2. Passing in col-md-4 will still
4991     * return col-md-4.
4992     * @param string $class
4993     * @return string
4994     */
4995    protected static function switchOffsetToCol($class)
4996    {
4997        // todo: why would you want to do this
4998        if (strpos($class, 'offset') !== false) {
4999            return str_replace('-offset', '', $class);
5000        } else {
5001            return $class;
5002        }
5003    }
5004
5005    /**
5006     * Returns the col-* classes
5007     * @param array $htmlOptions with "class" set
5008     * @return string
5009     */
5010    protected static function getColClasses($htmlOptions)
5011    {
5012        // todo: why would you want to do this
5013        $colClasses = array();
5014        if (isset($htmlOptions['class']) && !empty($htmlOptions['class'])) {
5015            $classes = explode(' ', $htmlOptions['class']);
5016            foreach ($classes as $class) {
5017                if (substr($class, 0, 4) == 'col-') {
5018                    $colClasses[] = $class;
5019                }
5020            }
5021        }
5022        return implode(' ', array_unique($colClasses));
5023    }
5024
5025    /**
5026     * Returns the col-* classes and removes the classes from $htmlOptions['class']
5027     * @param string $htmlOptions with class set
5028     * @return string
5029     */
5030    protected static function popColClasses(&$htmlOptions)
5031    {
5032        // todo: why would you want to do this
5033        $colClasses = array();
5034        $returnClasses = array();
5035        if (isset($htmlOptions['class']) && !empty($htmlOptions['class'])) {
5036            $classes = explode(' ', $htmlOptions['class']);
5037            foreach ($classes as $class) {
5038                if (substr($class, 0, 4) == 'col-') {
5039                    $colClasses[] = $class;
5040                } elseif (!empty($class)) {
5041                    $returnClasses[] = $class;
5042                }
5043            }
5044            $htmlOptions['class'] = implode(' ', $returnClasses);
5045        }
5046        return implode(' ', array_unique($colClasses));
5047    }
5048}
5049