source: subversion/branches/devel-framework/roundcubemail/program/include/html.php @ 5807

Last change on this file since 5807 was 5807, checked in by alec, 16 months ago
  • Move global functions from main.inc and rcube_shared.inc into classes Leave main.inc with aliases for backward compat. (to be removed)
  • Unified naming of the rest global functions (rcube_ prefix)
  • Property svn:keywords set to Id
File size: 23.1 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/html.php                                              |
6 |                                                                       |
7 | This file is part of the Roundcube Webmail client                     |
8 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
9 | Licensed under the GNU GPL                                            |
10 |                                                                       |
11 | PURPOSE:                                                              |
12 |   Helper class to create valid XHTML code                             |
13 |                                                                       |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16 +-----------------------------------------------------------------------+
17
18 $Id$
19
20 */
21
22
23/**
24 * Class for HTML code creation
25 *
26 * @package HTML
27 */
28class html
29{
30    protected $tagname;
31    protected $attrib = array();
32    protected $allowed = array();
33    protected $content;
34
35    public static $doctype = 'xhtml';
36    public static $lc_tags = true;
37    public static $common_attrib = array('id','class','style','title','align');
38    public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script');
39
40    /**
41     * Constructor
42     *
43     * @param array $attrib Hash array with tag attributes
44     */
45    public function __construct($attrib = array())
46    {
47        if (is_array($attrib)) {
48            $this->attrib = $attrib;
49        }
50    }
51
52    /**
53     * Return the tag code
54     *
55     * @return string The finally composed HTML tag
56     */
57    public function show()
58    {
59        return self::tag($this->tagname, $this->attrib, $this->content, array_merge(self::$common_attrib, $this->allowed));
60    }
61
62    /****** STATIC METHODS *******/
63
64    /**
65     * Generic method to create a HTML tag
66     *
67     * @param string $tagname Tag name
68     * @param array  $attrib  Tag attributes as key/value pairs
69     * @param string $content Optinal Tag content (creates a container tag)
70     * @param array  $allowed_attrib List with allowed attributes, omit to allow all
71     * @return string The XHTML tag
72     */
73    public static function tag($tagname, $attrib = array(), $content = null, $allowed_attrib = null)
74    {
75        if (is_string($attrib))
76            $attrib = array('class' => $attrib);
77
78        $inline_tags = array('a','span','img');
79        $suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : '';
80
81        $tagname = self::$lc_tags ? strtolower($tagname) : $tagname;
82        if (isset($content) || in_array($tagname, self::$containers)) {
83            $suffix = $attrib['noclose'] ? $suffix : '</' . $tagname . '>' . $suffix;
84            unset($attrib['noclose'], $attrib['nl']);
85            return '<' . $tagname  . self::attrib_string($attrib, $allowed_attrib) . '>' . $content . $suffix;
86        }
87        else {
88            return '<' . $tagname  . self::attrib_string($attrib, $allowed_attrib) . '>' . $suffix;
89        }
90    }
91
92    /**
93     *
94     */
95    public static function doctype($type)
96    {
97        $doctypes = array(
98            'html5'        => '<!DOCTYPE html>',
99            'xhtml'        => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
100            'xhtml-trans'  => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
101            'xhtml-strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
102        );
103
104        if ($doctypes[$type]) {
105            self::$doctype = preg_replace('/-\w+$/', '', $type);
106            return $doctypes[$type];
107        }
108
109        return '';
110    }
111
112    /**
113     * Derrived method for <div> containers
114     *
115     * @param mixed  $attr Hash array with tag attributes or string with class name
116     * @param string $cont Div content
117     * @return string HTML code
118     * @see html::tag()
119     */
120    public static function div($attr = null, $cont = null)
121    {
122        if (is_string($attr)) {
123            $attr = array('class' => $attr);
124        }
125        return self::tag('div', $attr, $cont, array_merge(self::$common_attrib, array('onclick')));
126    }
127
128    /**
129     * Derrived method for <p> blocks
130     *
131     * @param mixed  $attr Hash array with tag attributes or string with class name
132     * @param string $cont Paragraph content
133     * @return string HTML code
134     * @see html::tag()
135     */
136    public static function p($attr = null, $cont = null)
137    {
138        if (is_string($attr)) {
139            $attr = array('class' => $attr);
140        }
141        return self::tag('p', $attr, $cont, self::$common_attrib);
142    }
143
144    /**
145     * Derrived method to create <img />
146     *
147     * @param mixed $attr Hash array with tag attributes or string with image source (src)
148     * @return string HTML code
149     * @see html::tag()
150     */
151    public static function img($attr = null)
152    {
153        if (is_string($attr)) {
154            $attr = array('src' => $attr);
155        }
156        return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib,
157            array('src','alt','width','height','border','usemap')));
158    }
159
160    /**
161     * Derrived method for link tags
162     *
163     * @param mixed  $attr Hash array with tag attributes or string with link location (href)
164     * @param string $cont Link content
165     * @return string HTML code
166     * @see html::tag()
167     */
168    public static function a($attr, $cont)
169    {
170        if (is_string($attr)) {
171            $attr = array('href' => $attr);
172        }
173        return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
174            array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
175    }
176
177    /**
178     * Derrived method for inline span tags
179     *
180     * @param mixed  $attr Hash array with tag attributes or string with class name
181     * @param string $cont Tag content
182     * @return string HTML code
183     * @see html::tag()
184     */
185    public static function span($attr, $cont)
186    {
187        if (is_string($attr)) {
188            $attr = array('class' => $attr);
189        }
190        return self::tag('span', $attr, $cont, self::$common_attrib);
191    }
192
193    /**
194     * Derrived method for form element labels
195     *
196     * @param mixed  $attr Hash array with tag attributes or string with 'for' attrib
197     * @param string $cont Tag content
198     * @return string HTML code
199     * @see html::tag()
200     */
201    public static function label($attr, $cont)
202    {
203        if (is_string($attr)) {
204            $attr = array('for' => $attr);
205        }
206        return self::tag('label', $attr, $cont, array_merge(self::$common_attrib, array('for')));
207    }
208
209    /**
210     * Derrived method to create <iframe></iframe>
211     *
212     * @param mixed $attr Hash array with tag attributes or string with frame source (src)
213     * @return string HTML code
214     * @see html::tag()
215     */
216    public static function iframe($attr = null, $cont = null)
217    {
218        if (is_string($attr)) {
219            $attr = array('src' => $attr);
220        }
221        return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib,
222            array('src','name','width','height','border','frameborder')));
223    }
224
225    /**
226     * Derrived method to create <script> tags
227     *
228     * @param mixed $attr Hash array with tag attributes or string with script source (src)
229     * @param string $cont Javascript code to be placed as tag content
230     * @return string HTML code
231     * @see html::tag()
232     */
233    public static function script($attr, $cont = null)
234    {
235        if (is_string($attr)) {
236            $attr = array('src' => $attr);
237        }
238        if ($cont) {
239            if (self::$doctype == 'xhtml')
240                $cont = "\n/* <![CDATA[ */\n" . $cont . "\n/* ]]> */\n";
241            else
242                $cont = "\n" . $cont . "\n";
243        }
244
245        return self::tag('script', $attr + array('type' => 'text/javascript', 'nl' => true),
246            $cont, array_merge(self::$common_attrib, array('src','type','charset')));
247    }
248
249    /**
250     * Derrived method for line breaks
251     *
252     * @return string HTML code
253     * @see html::tag()
254     */
255    public static function br()
256    {
257        return self::tag('br');
258    }
259
260    /**
261     * Create string with attributes
262     *
263     * @param array $attrib Associative arry with tag attributes
264     * @param array $allowed List of allowed attributes
265     * @return string Valid attribute string
266     */
267    public static function attrib_string($attrib = array(), $allowed = null)
268    {
269        if (empty($attrib)) {
270            return '';
271        }
272
273        $allowed_f = array_flip((array)$allowed);
274        $attrib_arr = array();
275        foreach ($attrib as $key => $value) {
276            // skip size if not numeric
277            if (($key=='size' && !is_numeric($value))) {
278                continue;
279            }
280
281            // ignore "internal" or not allowed attributes
282            if ($key == 'nl' || ($allowed && !isset($allowed_f[$key])) || $value === null) {
283                continue;
284            }
285
286            // skip empty eventhandlers
287            if (preg_match('/^on[a-z]+/', $key) && !$value) {
288                continue;
289            }
290
291            // attributes with no value
292            if (in_array($key, array('checked', 'multiple', 'disabled', 'selected'))) {
293                if ($value) {
294                    $attrib_arr[] = $key . '="' . $key . '"';
295                }
296            }
297            else if ($key=='value') {
298                $attrib_arr[] = $key . '="' . Q($value, 'strict', false) . '"';
299            }
300            else {
301                $attrib_arr[] = $key . '="' . Q($value) . '"';
302            }
303        }
304        return count($attrib_arr) ? ' '.implode(' ', $attrib_arr) : '';
305    }
306
307    /**
308     * Convert a HTML attribute string attributes to an associative array (name => value)
309     *
310     * @param string Input string
311     * @return array Key-value pairs of parsed attributes
312     */
313    public static function parse_attrib_string($str)
314    {
315        $attrib = array();
316        $regexp = '/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui';
317
318        preg_match_all($regexp, stripslashes($str), $regs, PREG_SET_ORDER);
319
320        // convert attributes to an associative array (name => value)
321        if ($regs) {
322            foreach ($regs as $attr) {
323                $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
324            }
325        }
326
327        return $attrib;
328    }
329}
330
331/**
332 * Class to create an HTML input field
333 *
334 * @package HTML
335 */
336class html_inputfield extends html
337{
338    protected $tagname = 'input';
339    protected $type = 'text';
340    protected $allowed = array('type','name','value','size','tabindex',
341        'autocomplete','checked','onchange','onclick','disabled','readonly',
342        'spellcheck','results','maxlength','src','multiple');
343
344    /**
345     * Object constructor
346     *
347     * @param array $attrib Associative array with tag attributes
348     */
349    public function __construct($attrib = array())
350    {
351        if (is_array($attrib)) {
352            $this->attrib = $attrib;
353        }
354
355        if ($attrib['type']) {
356            $this->type = $attrib['type'];
357        }
358
359        if ($attrib['newline']) {
360            $this->newline = true;
361        }
362    }
363
364    /**
365     * Compose input tag
366     *
367     * @param string $value Field value
368     * @param array  $attrib Additional attributes to override
369     * @return string HTML output
370     */
371    public function show($value = null, $attrib = null)
372    {
373        // overwrite object attributes
374        if (is_array($attrib)) {
375            $this->attrib = array_merge($this->attrib, $attrib);
376        }
377
378        // set value attribute
379        if ($value !== null) {
380            $this->attrib['value'] = $value;
381        }
382        // set type
383        $this->attrib['type'] = $this->type;
384        return parent::show();
385    }
386}
387
388/**
389 * Class to create an HTML password field
390 *
391 * @package HTML
392 */
393class html_passwordfield extends html_inputfield
394{
395    protected $type = 'password';
396}
397
398/**
399 * Class to create an hidden HTML input field
400 *
401 * @package HTML
402 */
403
404class html_hiddenfield extends html_inputfield
405{
406    protected $type = 'hidden';
407    protected $fields_arr = array();
408    protected $newline = true;
409
410    /**
411     * Constructor
412     *
413     * @param array $attrib Named tag attributes
414     */
415    public function __construct($attrib = null)
416    {
417        if (is_array($attrib)) {
418            $this->add($attrib);
419        }
420    }
421
422    /**
423     * Add a hidden field to this instance
424     *
425     * @param array $attrib Named tag attributes
426     */
427    public function add($attrib)
428    {
429        $this->fields_arr[] = $attrib;
430    }
431
432    /**
433     * Create HTML code for the hidden fields
434     *
435     * @return string Final HTML code
436     */
437    public function show()
438    {
439        $out = '';
440        foreach ($this->fields_arr as $attrib) {
441            $out .= self::tag($this->tagname, array('type' => $this->type) + $attrib);
442        }
443        return $out;
444    }
445}
446
447/**
448 * Class to create HTML radio buttons
449 *
450 * @package HTML
451 */
452class html_radiobutton extends html_inputfield
453{
454    protected $type = 'radio';
455
456    /**
457     * Get HTML code for this object
458     *
459     * @param string $value  Value of the checked field
460     * @param array  $attrib Additional attributes to override
461     * @return string HTML output
462     */
463    public function show($value = '', $attrib = null)
464    {
465        // overwrite object attributes
466        if (is_array($attrib)) {
467            $this->attrib = array_merge($this->attrib, $attrib);
468        }
469
470        // set value attribute
471        $this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
472
473        return parent::show();
474    }
475}
476
477/**
478 * Class to create HTML checkboxes
479 *
480 * @package HTML
481 */
482class html_checkbox extends html_inputfield
483{
484    protected $type = 'checkbox';
485
486    /**
487     * Get HTML code for this object
488     *
489     * @param string $value  Value of the checked field
490     * @param array  $attrib Additional attributes to override
491     * @return string HTML output
492     */
493    public function show($value = '', $attrib = null)
494    {
495        // overwrite object attributes
496        if (is_array($attrib)) {
497            $this->attrib = array_merge($this->attrib, $attrib);
498        }
499
500        // set value attribute
501        $this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
502
503        return parent::show();
504    }
505}
506
507/**
508 * Class to create an HTML textarea
509 *
510 * @package HTML
511 */
512class html_textarea extends html
513{
514    protected $tagname = 'textarea';
515    protected $allowed = array('name','rows','cols','wrap','tabindex',
516        'onchange','disabled','readonly','spellcheck');
517
518    /**
519     * Get HTML code for this object
520     *
521     * @param string $value  Textbox value
522     * @param array  $attrib Additional attributes to override
523     * @return string HTML output
524     */
525    public function show($value = '', $attrib = null)
526    {
527        // overwrite object attributes
528        if (is_array($attrib)) {
529            $this->attrib = array_merge($this->attrib, $attrib);
530        }
531
532        // take value attribute as content
533        if (empty($value) && !empty($this->attrib['value'])) {
534            $value = $this->attrib['value'];
535        }
536
537        // make shure we don't print the value attribute
538        if (isset($this->attrib['value'])) {
539            unset($this->attrib['value']);
540        }
541
542        if (!empty($value) && !preg_match('/mce_editor/', $this->attrib['class'])) {
543            $value = Q($value, 'strict', false);
544        }
545
546        return self::tag($this->tagname, $this->attrib, $value,
547            array_merge(self::$common_attrib, $this->allowed));
548    }
549}
550
551/**
552 * Builder for HTML drop-down menus
553 * Syntax:<pre>
554 * // create instance. arguments are used to set attributes of select-tag
555 * $select = new html_select(array('name' => 'fieldname'));
556 *
557 * // add one option
558 * $select->add('Switzerland', 'CH');
559 *
560 * // add multiple options
561 * $select->add(array('Switzerland','Germany'), array('CH','DE'));
562 *
563 * // generate pulldown with selection 'Switzerland'  and return html-code
564 * // as second argument the same attributes available to instanciate can be used
565 * print $select->show('CH');
566 * </pre>
567 *
568 * @package HTML
569 */
570class html_select extends html
571{
572    protected $tagname = 'select';
573    protected $options = array();
574    protected $allowed = array('name','size','tabindex','autocomplete',
575        'multiple','onchange','disabled','rel');
576   
577    /**
578     * Add a new option to this drop-down
579     *
580     * @param mixed $names  Option name or array with option names
581     * @param mixed $values Option value or array with option values
582     */
583    public function add($names, $values = null)
584    {
585        if (is_array($names)) {
586            foreach ($names as $i => $text) {
587                $this->options[] = array('text' => $text, 'value' => $values[$i]);
588            }
589        }
590        else {
591            $this->options[] = array('text' => $names, 'value' => $values);
592        }
593    }
594
595    /**
596     * Get HTML code for this object
597     *
598     * @param string $select Value of the selection option
599     * @param array  $attrib Additional attributes to override
600     * @return string HTML output
601     */
602    public function show($select = array(), $attrib = null)
603    {
604        // overwrite object attributes
605        if (is_array($attrib)) {
606            $this->attrib = array_merge($this->attrib, $attrib);
607        }
608
609        $this->content = "\n";
610        $select = (array)$select;
611        foreach ($this->options as $option) {
612            $attr = array(
613                'value' => $option['value'],
614                'selected' => (in_array($option['value'], $select, true) ||
615                  in_array($option['text'], $select, true)) ? 1 : null);
616
617            $this->content .= self::tag('option', $attr, Q($option['text']));
618        }
619        return parent::show();
620    }
621}
622
623
624/**
625 * Class to build an HTML table
626 *
627 * @package HTML
628 */
629class html_table extends html
630{
631    protected $tagname = 'table';
632    protected $allowed = array('id','class','style','width','summary',
633            'cellpadding','cellspacing','border');
634
635    private $header = array();
636    private $rows = array();
637    private $rowindex = 0;
638    private $colindex = 0;
639
640    /**
641     * Constructor
642     *
643     * @param array $attrib Named tag attributes
644     */
645    public function __construct($attrib = array())
646    {
647        $default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => 0) : array();
648        $this->attrib = array_merge($attrib, $default_attrib);
649    }
650
651    /**
652     * Add a table cell
653     *
654     * @param array  $attr Cell attributes
655     * @param string $cont Cell content
656     */
657    public function add($attr, $cont)
658    {
659        if (is_string($attr)) {
660            $attr = array('class' => $attr);
661        }
662
663        $cell = new stdClass;
664        $cell->attrib = $attr;
665        $cell->content = $cont;
666
667        $this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
668        $this->colindex++;
669
670        if ($this->attrib['cols'] && $this->colindex == $this->attrib['cols']) {
671            $this->add_row();
672        }
673    }
674
675    /**
676     * Add a table header cell
677     *
678     * @param array  $attr Cell attributes
679     * @param string $cont Cell content
680     */
681    public function add_header($attr, $cont)
682    {
683        if (is_string($attr))
684            $attr = array('class' => $attr);
685
686        $cell = new stdClass;
687        $cell->attrib = $attr;
688        $cell->content = $cont;
689        $this->header[] = $cell;
690    }
691
692     /**
693     * Remove a column from a table
694     * Useful for plugins making alterations
695     *
696     * @param string $class
697     */
698    public function remove_column($class)
699    {
700        // Remove the header
701        foreach ($this->header as $index=>$header){
702            if ($header->attrib['class'] == $class){
703                unset($this->header[$index]);
704                break;
705            }
706        }
707
708        // Remove cells from rows
709        foreach ($this->rows as $i=>$row){
710            foreach ($row->cells as $j=>$cell){
711                if ($cell->attrib['class'] == $class){
712                    unset($this->rows[$i]->cells[$j]);
713                    break;
714                }
715            }
716        }
717    }
718
719    /**
720     * Jump to next row
721     *
722     * @param array $attr Row attributes
723     */
724    public function add_row($attr = array())
725    {
726        $this->rowindex++;
727        $this->colindex = 0;
728        $this->rows[$this->rowindex] = new stdClass;
729        $this->rows[$this->rowindex]->attrib = $attr;
730        $this->rows[$this->rowindex]->cells = array();
731    }
732
733    /**
734     * Set row attributes
735     *
736     * @param array $attr  Row attributes
737     * @param int   $index Optional row index (default current row index)
738     */
739    public function set_row_attribs($attr = array(), $index = null)
740    {
741        if (is_string($attr))
742            $attr = array('class' => $attr);
743
744        if ($index === null)
745            $index = $this->rowindex;
746
747        $this->rows[$index]->attrib = $attr;
748    }
749
750    /**
751     * Get row attributes
752     *
753     * @param int $index Row index
754     *
755     * @return array Row attributes
756     */
757    public function get_row_attribs($index = null)
758    {
759        if ($index === null)
760            $index = $this->rowindex;
761
762        return $this->rows[$index] ? $this->rows[$index]->attrib : null;
763    }
764
765    /**
766     * Build HTML output of the table data
767     *
768     * @param array $attrib Table attributes
769     * @return string The final table HTML code
770     */
771    public function show($attrib = null)
772    {
773        if (is_array($attrib))
774            $this->attrib = array_merge($this->attrib, $attrib);
775
776        $thead = $tbody = "";
777
778        // include <thead>
779        if (!empty($this->header)) {
780            $rowcontent = '';
781            foreach ($this->header as $c => $col) {
782                $rowcontent .= self::tag('td', $col->attrib, $col->content);
783            }
784            $thead = self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib));
785        }
786
787        foreach ($this->rows as $r => $row) {
788            $rowcontent = '';
789            foreach ($row->cells as $c => $col) {
790                $rowcontent .= self::tag('td', $col->attrib, $col->content);
791            }
792
793            if ($r < $this->rowindex || count($row->cells)) {
794                $tbody .= self::tag('tr', $row->attrib, $rowcontent, parent::$common_attrib);
795            }
796        }
797
798        if ($this->attrib['rowsonly']) {
799            return $tbody;
800        }
801
802        // add <tbody>
803        $this->content = $thead . self::tag('tbody', null, $tbody);
804
805        unset($this->attrib['cols'], $this->attrib['rowsonly']);
806        return parent::show();
807    }
808
809    /**
810     * Count number of rows
811     *
812     * @return The number of rows
813     */
814    public function size()
815    {
816      return count($this->rows);
817    }
818
819    /**
820     * Remove table body (all rows)
821     */
822    public function remove_body()
823    {
824        $this->rows     = array();
825        $this->rowindex = 0;
826    }
827
828}
Note: See TracBrowser for help on using the repository browser.