source: github/program/include/html.php @ 011e805

HEADcourier-fixdev-browser-capabilitiespdorelease-0.8
Last change on this file since 011e805 was 011e805, checked in by thomascube <thomas@…>, 18 months ago

Get rid of sprintf() calls for html output generation

  • Property mode set to 100644
File size: 22.3 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     * @return string HTML code
230     * @see html::tag()
231     */
232    public static function script($attr, $cont = null)
233    {
234        if (is_string($attr)) {
235            $attr = array('src' => $attr);
236        }
237        if ($cont) {
238            if (self::$doctype == 'xhtml')
239                $cont = "\n/* <![CDATA[ */\n" . $cont . "\n/* ]]> */\n";
240            else
241                $cont = "\n" . $cont . "\n";
242        }
243
244        return self::tag('script', $attr + array('type' => 'text/javascript', 'nl' => true),
245            $cont, array_merge(self::$common_attrib, array('src','type','charset')));
246    }
247
248    /**
249     * Derrived method for line breaks
250     *
251     * @return string HTML code
252     * @see html::tag()
253     */
254    public static function br()
255    {
256        return self::tag('br');
257    }
258
259    /**
260     * Create string with attributes
261     *
262     * @param array $attrib Associative arry with tag attributes
263     * @param array $allowed List of allowed attributes
264     * @return string Valid attribute string
265     */
266    public static function attrib_string($attrib = array(), $allowed = null)
267    {
268        if (empty($attrib)) {
269            return '';
270        }
271
272        $allowed_f = array_flip((array)$allowed);
273        $attrib_arr = array();
274        foreach ($attrib as $key => $value) {
275            // skip size if not numeric
276            if (($key=='size' && !is_numeric($value))) {
277                continue;
278            }
279
280            // ignore "internal" or not allowed attributes
281            if ($key == 'nl' || ($allowed && !isset($allowed_f[$key])) || $value === null) {
282                continue;
283            }
284
285            // skip empty eventhandlers
286            if (preg_match('/^on[a-z]+/', $key) && !$value) {
287                continue;
288            }
289
290            // attributes with no value
291            if (in_array($key, array('checked', 'multiple', 'disabled', 'selected'))) {
292                if ($value) {
293                    $attrib_arr[] = $key . '="' . $key . '"';
294                }
295            }
296            else if ($key=='value') {
297                $attrib_arr[] = $key . '="' . Q($value, 'strict', false) . '"';
298            }
299            else {
300                $attrib_arr[] = $key . '="' . Q($value) . '"';
301            }
302        }
303        return count($attrib_arr) ? ' '.implode(' ', $attrib_arr) : '';
304    }
305}
306
307/**
308 * Class to create an HTML input field
309 *
310 * @package HTML
311 */
312class html_inputfield extends html
313{
314    protected $tagname = 'input';
315    protected $type = 'text';
316    protected $allowed = array('type','name','value','size','tabindex',
317        'autocomplete','checked','onchange','onclick','disabled','readonly',
318        'spellcheck','results','maxlength','src','multiple');
319
320    /**
321     * Object constructor
322     *
323     * @param array $attrib Associative array with tag attributes
324     */
325    public function __construct($attrib = array())
326    {
327        if (is_array($attrib)) {
328            $this->attrib = $attrib;
329        }
330
331        if ($attrib['type']) {
332            $this->type = $attrib['type'];
333        }
334
335        if ($attrib['newline']) {
336            $this->newline = true;
337        }
338    }
339
340    /**
341     * Compose input tag
342     *
343     * @param string $value Field value
344     * @param array  $attrib Additional attributes to override
345     * @return string HTML output
346     */
347    public function show($value = null, $attrib = null)
348    {
349        // overwrite object attributes
350        if (is_array($attrib)) {
351            $this->attrib = array_merge($this->attrib, $attrib);
352        }
353
354        // set value attribute
355        if ($value !== null) {
356            $this->attrib['value'] = $value;
357        }
358        // set type
359        $this->attrib['type'] = $this->type;
360        return parent::show();
361    }
362}
363
364/**
365 * Class to create an HTML password field
366 *
367 * @package HTML
368 */
369class html_passwordfield extends html_inputfield
370{
371    protected $type = 'password';
372}
373
374/**
375 * Class to create an hidden HTML input field
376 *
377 * @package HTML
378 */
379
380class html_hiddenfield extends html_inputfield
381{
382    protected $type = 'hidden';
383    protected $fields_arr = array();
384    protected $newline = true;
385
386    /**
387     * Constructor
388     *
389     * @param array $attrib Named tag attributes
390     */
391    public function __construct($attrib = null)
392    {
393        if (is_array($attrib)) {
394            $this->add($attrib);
395        }
396    }
397
398    /**
399     * Add a hidden field to this instance
400     *
401     * @param array $attrib Named tag attributes
402     */
403    public function add($attrib)
404    {
405        $this->fields_arr[] = $attrib;
406    }
407
408    /**
409     * Create HTML code for the hidden fields
410     *
411     * @return string Final HTML code
412     */
413    public function show()
414    {
415        $out = '';
416        foreach ($this->fields_arr as $attrib) {
417            $out .= self::tag($this->tagname, array('type' => $this->type) + $attrib);
418        }
419        return $out;
420    }
421}
422
423/**
424 * Class to create HTML radio buttons
425 *
426 * @package HTML
427 */
428class html_radiobutton extends html_inputfield
429{
430    protected $type = 'radio';
431
432    /**
433     * Get HTML code for this object
434     *
435     * @param string $value  Value of the checked field
436     * @param array  $attrib Additional attributes to override
437     * @return string HTML output
438     */
439    public function show($value = '', $attrib = null)
440    {
441        // overwrite object attributes
442        if (is_array($attrib)) {
443            $this->attrib = array_merge($this->attrib, $attrib);
444        }
445
446        // set value attribute
447        $this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
448
449        return parent::show();
450    }
451}
452
453/**
454 * Class to create HTML checkboxes
455 *
456 * @package HTML
457 */
458class html_checkbox extends html_inputfield
459{
460    protected $type = 'checkbox';
461
462    /**
463     * Get HTML code for this object
464     *
465     * @param string $value  Value of the checked field
466     * @param array  $attrib Additional attributes to override
467     * @return string HTML output
468     */
469    public function show($value = '', $attrib = null)
470    {
471        // overwrite object attributes
472        if (is_array($attrib)) {
473            $this->attrib = array_merge($this->attrib, $attrib);
474        }
475
476        // set value attribute
477        $this->attrib['checked'] = ((string)$value == (string)$this->attrib['value']);
478
479        return parent::show();
480    }
481}
482
483/**
484 * Class to create an HTML textarea
485 *
486 * @package HTML
487 */
488class html_textarea extends html
489{
490    protected $tagname = 'textarea';
491    protected $allowed = array('name','rows','cols','wrap','tabindex',
492        'onchange','disabled','readonly','spellcheck');
493
494    /**
495     * Get HTML code for this object
496     *
497     * @param string $value  Textbox value
498     * @param array  $attrib Additional attributes to override
499     * @return string HTML output
500     */
501    public function show($value = '', $attrib = null)
502    {
503        // overwrite object attributes
504        if (is_array($attrib)) {
505            $this->attrib = array_merge($this->attrib, $attrib);
506        }
507
508        // take value attribute as content
509        if (empty($value) && !empty($this->attrib['value'])) {
510            $value = $this->attrib['value'];
511        }
512
513        // make shure we don't print the value attribute
514        if (isset($this->attrib['value'])) {
515            unset($this->attrib['value']);
516        }
517
518        if (!empty($value) && !preg_match('/mce_editor/', $this->attrib['class'])) {
519            $value = Q($value, 'strict', false);
520        }
521
522        return self::tag($this->tagname, $this->attrib, $value,
523            array_merge(self::$common_attrib, $this->allowed));
524    }
525}
526
527/**
528 * Builder for HTML drop-down menus
529 * Syntax:<pre>
530 * // create instance. arguments are used to set attributes of select-tag
531 * $select = new html_select(array('name' => 'fieldname'));
532 *
533 * // add one option
534 * $select->add('Switzerland', 'CH');
535 *
536 * // add multiple options
537 * $select->add(array('Switzerland','Germany'), array('CH','DE'));
538 *
539 * // generate pulldown with selection 'Switzerland'  and return html-code
540 * // as second argument the same attributes available to instanciate can be used
541 * print $select->show('CH');
542 * </pre>
543 *
544 * @package HTML
545 */
546class html_select extends html
547{
548    protected $tagname = 'select';
549    protected $options = array();
550    protected $allowed = array('name','size','tabindex','autocomplete',
551        'multiple','onchange','disabled','rel');
552   
553    /**
554     * Add a new option to this drop-down
555     *
556     * @param mixed $names  Option name or array with option names
557     * @param mixed $values Option value or array with option values
558     */
559    public function add($names, $values = null)
560    {
561        if (is_array($names)) {
562            foreach ($names as $i => $text) {
563                $this->options[] = array('text' => $text, 'value' => $values[$i]);
564            }
565        }
566        else {
567            $this->options[] = array('text' => $names, 'value' => $values);
568        }
569    }
570
571    /**
572     * Get HTML code for this object
573     *
574     * @param string $select Value of the selection option
575     * @param array  $attrib Additional attributes to override
576     * @return string HTML output
577     */
578    public function show($select = array(), $attrib = null)
579    {
580        // overwrite object attributes
581        if (is_array($attrib)) {
582            $this->attrib = array_merge($this->attrib, $attrib);
583        }
584
585        $this->content = "\n";
586        $select = (array)$select;
587        foreach ($this->options as $option) {
588            $attr = array(
589                'value' => $option['value'],
590                'selected' => (in_array($option['value'], $select, true) ||
591                  in_array($option['text'], $select, true)) ? 1 : null);
592
593            $this->content .= self::tag('option', $attr, Q($option['text']));
594        }
595        return parent::show();
596    }
597}
598
599
600/**
601 * Class to build an HTML table
602 *
603 * @package HTML
604 */
605class html_table extends html
606{
607    protected $tagname = 'table';
608    protected $allowed = array('id','class','style','width','summary',
609            'cellpadding','cellspacing','border');
610
611    private $header = array();
612    private $rows = array();
613    private $rowindex = 0;
614    private $colindex = 0;
615
616    /**
617     * Constructor
618     *
619     * @param array $attrib Named tag attributes
620     */
621    public function __construct($attrib = array())
622    {
623        $default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => 0) : array();
624        $this->attrib = array_merge($attrib, $default_attrib);
625    }
626
627    /**
628     * Add a table cell
629     *
630     * @param array  $attr Cell attributes
631     * @param string $cont Cell content
632     */
633    public function add($attr, $cont)
634    {
635        if (is_string($attr)) {
636            $attr = array('class' => $attr);
637        }
638
639        $cell = new stdClass;
640        $cell->attrib = $attr;
641        $cell->content = $cont;
642
643        $this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
644        $this->colindex++;
645
646        if ($this->attrib['cols'] && $this->colindex == $this->attrib['cols']) {
647            $this->add_row();
648        }
649    }
650
651    /**
652     * Add a table header cell
653     *
654     * @param array  $attr Cell attributes
655     * @param string $cont Cell content
656     */
657    public function add_header($attr, $cont)
658    {
659        if (is_string($attr))
660            $attr = array('class' => $attr);
661
662        $cell = new stdClass;
663        $cell->attrib = $attr;
664        $cell->content = $cont;
665        $this->header[] = $cell;
666    }
667
668     /**
669     * Remove a column from a table
670     * Useful for plugins making alterations
671     *
672     * @param string $class
673     */
674    public function remove_column($class)
675    {
676        // Remove the header
677        foreach ($this->header as $index=>$header){
678            if ($header->attrib['class'] == $class){
679                unset($this->header[$index]);
680                break;
681            }
682        }
683
684        // Remove cells from rows
685        foreach ($this->rows as $i=>$row){
686            foreach ($row->cells as $j=>$cell){
687                if ($cell->attrib['class'] == $class){
688                    unset($this->rows[$i]->cells[$j]);
689                    break;
690                }
691            }
692        }
693    }
694
695    /**
696     * Jump to next row
697     *
698     * @param array $attr Row attributes
699     */
700    public function add_row($attr = array())
701    {
702        $this->rowindex++;
703        $this->colindex = 0;
704        $this->rows[$this->rowindex] = new stdClass;
705        $this->rows[$this->rowindex]->attrib = $attr;
706        $this->rows[$this->rowindex]->cells = array();
707    }
708
709    /**
710     * Set row attributes
711     *
712     * @param array $attr  Row attributes
713     * @param int   $index Optional row index (default current row index)
714     */
715    public function set_row_attribs($attr = array(), $index = null)
716    {
717        if (is_string($attr))
718            $attr = array('class' => $attr);
719
720        if ($index === null)
721            $index = $this->rowindex;
722
723        $this->rows[$index]->attrib = $attr;
724    }
725
726    /**
727     * Get row attributes
728     *
729     * @param int $index Row index
730     *
731     * @return array Row attributes
732     */
733    public function get_row_attribs($index = null)
734    {
735        if ($index === null)
736            $index = $this->rowindex;
737
738        return $this->rows[$index] ? $this->rows[$index]->attrib : null;
739    }
740
741    /**
742     * Build HTML output of the table data
743     *
744     * @param array $attrib Table attributes
745     * @return string The final table HTML code
746     */
747    public function show($attrib = null)
748    {
749        if (is_array($attrib))
750            $this->attrib = array_merge($this->attrib, $attrib);
751
752        $thead = $tbody = "";
753
754        // include <thead>
755        if (!empty($this->header)) {
756            $rowcontent = '';
757            foreach ($this->header as $c => $col) {
758                $rowcontent .= self::tag('td', $col->attrib, $col->content);
759            }
760            $thead = self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib));
761        }
762
763        foreach ($this->rows as $r => $row) {
764            $rowcontent = '';
765            foreach ($row->cells as $c => $col) {
766                $rowcontent .= self::tag('td', $col->attrib, $col->content);
767            }
768
769            if ($r < $this->rowindex || count($row->cells)) {
770                $tbody .= self::tag('tr', $row->attrib, $rowcontent, parent::$common_attrib);
771            }
772        }
773
774        if ($this->attrib['rowsonly']) {
775            return $tbody;
776        }
777
778        // add <tbody>
779        $this->content = $thead . self::tag('tbody', null, $tbody);
780
781        unset($this->attrib['cols'], $this->attrib['rowsonly']);
782        return parent::show();
783    }
784
785    /**
786     * Count number of rows
787     *
788     * @return The number of rows
789     */
790    public function size()
791    {
792      return count($this->rows);
793    }
794
795    /**
796     * Remove table body (all rows)
797     */
798    public function remove_body()
799    {
800        $this->rows     = array();
801        $this->rowindex = 0;
802    }
803
804}
805
Note: See TracBrowser for help on using the repository browser.