Aleksander Machniak
2016-03-28 46f7b7096450939fe03c95aa81ce06ae4bfca89d
program/lib/Roundcube/html.php
@@ -1,9 +1,9 @@
<?php
/*
/**
 +-----------------------------------------------------------------------+
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
 | Copyright (C) 2005-2013, The Roundcube Dev Team                       |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
@@ -16,24 +16,24 @@
 +-----------------------------------------------------------------------+
*/
/**
 * Class for HTML code creation
 *
 * @package    Framework
 * @subpackage HTML
 * @subpackage View
 */
class html
{
    protected $tagname;
    protected $attrib = array();
    protected $attrib  = array();
    protected $allowed = array();
    protected $content;
    public static $doctype = 'xhtml';
    public static $lc_tags = true;
    public static $common_attrib = array('id','class','style','title','align');
    public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script');
    public static $common_attrib = array('id','class','style','title','align','unselectable','tabindex','role');
    public static $containers = array('iframe','div','span','p','h1','h2','h3','ul','form','textarea','table','thead','tbody','tr','th','td','style','script');
    /**
     * Constructor
@@ -65,13 +65,15 @@
     * @param string $tagname Tag name
     * @param array  $attrib  Tag attributes as key/value pairs
     * @param string $content Optinal Tag content (creates a container tag)
     * @param array  $allowed_attrib List with allowed attributes, omit to allow all
     * @param array  $allowed List with allowed attributes, omit to allow all
     *
     * @return string The XHTML tag
     */
    public static function tag($tagname, $attrib = array(), $content = null, $allowed_attrib = null)
    public static function tag($tagname, $attrib = array(), $content = null, $allowed = null)
    {
        if (is_string($attrib))
        if (is_string($attrib)) {
            $attrib = array('class' => $attrib);
        }
        $inline_tags = array('a','span','img');
        $suffix = $attrib['nl'] || ($content && $attrib['nl'] !== false && !in_array($tagname, $inline_tags)) ? "\n" : '';
@@ -80,15 +82,17 @@
        if (isset($content) || in_array($tagname, self::$containers)) {
            $suffix = $attrib['noclose'] ? $suffix : '</' . $tagname . '>' . $suffix;
            unset($attrib['noclose'], $attrib['nl']);
            return '<' . $tagname  . self::attrib_string($attrib, $allowed_attrib) . '>' . $content . $suffix;
            return '<' . $tagname  . self::attrib_string($attrib, $allowed) . '>' . $content . $suffix;
        }
        else {
            return '<' . $tagname  . self::attrib_string($attrib, $allowed_attrib) . '>' . $suffix;
            return '<' . $tagname  . self::attrib_string($attrib, $allowed) . '>' . $suffix;
        }
    }
    /**
     * Return DOCTYPE tag of specified type
     *
     * @param string $type Document type (html5, xhtml, 'xhtml-trans, xhtml-strict)
     */
    public static function doctype($type)
    {
@@ -112,6 +116,7 @@
     *
     * @param mixed  $attr Hash array with tag attributes or string with class name
     * @param string $cont Div content
     *
     * @return string HTML code
     * @see html::tag()
     */
@@ -120,6 +125,7 @@
        if (is_string($attr)) {
            $attr = array('class' => $attr);
        }
        return self::tag('div', $attr, $cont, array_merge(self::$common_attrib, array('onclick')));
    }
@@ -128,6 +134,7 @@
     *
     * @param mixed  $attr Hash array with tag attributes or string with class name
     * @param string $cont Paragraph content
     *
     * @return string HTML code
     * @see html::tag()
     */
@@ -136,6 +143,7 @@
        if (is_string($attr)) {
            $attr = array('class' => $attr);
        }
        return self::tag('p', $attr, $cont, self::$common_attrib);
    }
@@ -143,6 +151,7 @@
     * Derrived method to create <img />
     *
     * @param mixed $attr Hash array with tag attributes or string with image source (src)
     *
     * @return string HTML code
     * @see html::tag()
     */
@@ -151,8 +160,9 @@
        if (is_string($attr)) {
            $attr = array('src' => $attr);
        }
        return self::tag('img', $attr + array('alt' => ''), null, array_merge(self::$common_attrib,
            array('src','alt','width','height','border','usemap','onclick')));
            array('src','alt','width','height','border','usemap','onclick','onerror')));
    }
    /**
@@ -160,6 +170,7 @@
     *
     * @param mixed  $attr Hash array with tag attributes or string with link location (href)
     * @param string $cont Link content
     *
     * @return string HTML code
     * @see html::tag()
     */
@@ -168,8 +179,9 @@
        if (is_string($attr)) {
            $attr = array('href' => $attr);
        }
        return self::tag('a', $attr, $cont, array_merge(self::$common_attrib,
        array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
            array('href','target','name','rel','onclick','onmouseover','onmouseout','onmousedown','onmouseup')));
    }
    /**
@@ -177,6 +189,7 @@
     *
     * @param mixed  $attr Hash array with tag attributes or string with class name
     * @param string $cont Tag content
     *
     * @return string HTML code
     * @see html::tag()
     */
@@ -185,6 +198,7 @@
        if (is_string($attr)) {
            $attr = array('class' => $attr);
        }
        return self::tag('span', $attr, $cont, self::$common_attrib);
    }
@@ -193,6 +207,7 @@
     *
     * @param mixed  $attr Hash array with tag attributes or string with 'for' attrib
     * @param string $cont Tag content
     *
     * @return string HTML code
     * @see html::tag()
     */
@@ -201,6 +216,7 @@
        if (is_string($attr)) {
            $attr = array('for' => $attr);
        }
        return self::tag('label', $attr, $cont, array_merge(self::$common_attrib, array('for')));
    }
@@ -208,6 +224,7 @@
     * Derrived method to create <iframe></iframe>
     *
     * @param mixed $attr Hash array with tag attributes or string with frame source (src)
     *
     * @return string HTML code
     * @see html::tag()
     */
@@ -216,15 +233,17 @@
        if (is_string($attr)) {
            $attr = array('src' => $attr);
        }
        return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib,
            array('src','name','width','height','border','frameborder')));
            array('src','name','width','height','border','frameborder','onload','allowfullscreen')));
    }
    /**
     * Derrived method to create <script> tags
     *
     * @param mixed $attr Hash array with tag attributes or string with script source (src)
     * @param mixed  $attr Hash array with tag attributes or string with script source (src)
     * @param string $cont Javascript code to be placed as tag content
     *
     * @return string HTML code
     * @see html::tag()
     */
@@ -247,6 +266,8 @@
    /**
     * Derrived method for line breaks
     *
     * @param array $attrib  Associative arry with tag attributes
     *
     * @return string HTML code
     * @see html::tag()
     */
@@ -258,8 +279,9 @@
    /**
     * Create string with attributes
     *
     * @param array $attrib Associative arry with tag attributes
     * @param array $attrib  Associative arry with tag attributes
     * @param array $allowed List of allowed attributes
     *
     * @return string Valid attribute string
     */
    public static function attrib_string($attrib = array(), $allowed = null)
@@ -268,17 +290,27 @@
            return '';
        }
        $allowed_f = array_flip((array)$allowed);
        $allowed_f  = array_flip((array)$allowed);
        $attrib_arr = array();
        foreach ($attrib as $key => $value) {
            // skip size if not numeric
            if ($key == 'size' && !is_numeric($value)) {
                continue;
            }
            // ignore "internal" or not allowed attributes
            if ($key == 'nl' || ($allowed && !isset($allowed_f[$key])) || $value === null) {
            // ignore "internal" or empty attributes
            if ($key == 'nl' || $value === null) {
                continue;
            }
            // ignore not allowed attributes, except aria-* and data-*
            if (!empty($allowed)) {
                $is_data_attr = @substr_compare($key, 'data-', 0, 5) === 0;
                $is_aria_attr = @substr_compare($key, 'aria-', 0, 5) === 0;
                if (!$is_aria_attr && !$is_data_attr && !isset($allowed_f[$key])) {
                    continue;
                }
            }
            // skip empty eventhandlers
@@ -287,7 +319,7 @@
            }
            // attributes with no value
            if (in_array($key, array('checked', 'multiple', 'disabled', 'selected'))) {
            if (in_array($key, array('checked', 'multiple', 'disabled', 'selected', 'autofocus'))) {
                if ($value) {
                    $attrib_arr[] = $key . '="' . $key . '"';
                }
@@ -304,6 +336,7 @@
     * Convert a HTML attribute string attributes to an associative array (name => value)
     *
     * @param string Input string
     *
     * @return array Key-value pairs of parsed attributes
     */
    public static function parse_attrib_string($str)
@@ -332,7 +365,16 @@
     */
    public static function quote($str)
    {
        return @htmlspecialchars($str, ENT_COMPAT, RCUBE_CHARSET);
        static $flags;
        if (!$flags) {
            $flags = ENT_COMPAT;
            if (defined('ENT_SUBSTITUTE')) {
                $flags |= ENT_SUBSTITUTE;
            }
        }
        return @htmlspecialchars($str, $flags, RCUBE_CHARSET);
    }
}
@@ -340,16 +382,18 @@
/**
 * Class to create an HTML input field
 *
 * @package HTML
 * @package    Framework
 * @subpackage View
 */
class html_inputfield extends html
{
    protected $tagname = 'input';
    protected $type = 'text';
    protected $type    = 'text';
    protected $allowed = array(
        'type','name','value','size','tabindex','autocapitalize',
        'type','name','value','size','tabindex','autocapitalize','required',
        'autocomplete','checked','onchange','onclick','disabled','readonly',
        'spellcheck','results','maxlength','src','multiple','placeholder',
        'spellcheck','results','maxlength','src','multiple','accept',
        'placeholder','autofocus',
    );
    /**
@@ -371,8 +415,9 @@
    /**
     * Compose input tag
     *
     * @param string $value Field value
     * @param string $value  Field value
     * @param array  $attrib Additional attributes to override
     *
     * @return string HTML output
     */
    public function show($value = null, $attrib = null)
@@ -388,6 +433,7 @@
        }
        // set type
        $this->attrib['type'] = $this->type;
        return parent::show();
    }
}
@@ -395,7 +441,8 @@
/**
 * Class to create an HTML password field
 *
 * @package HTML
 * @package    Framework
 * @subpackage View
 */
class html_passwordfield extends html_inputfield
{
@@ -405,15 +452,15 @@
/**
 * Class to create an hidden HTML input field
 *
 * @package HTML
 * @package    Framework
 * @subpackage View
 */
class html_hiddenfield extends html
{
    protected $tagname = 'input';
    protected $type = 'hidden';
    protected $fields_arr = array();
    protected $type    = 'hidden';
    protected $allowed = array('type','name','value','onchange','disabled','readonly');
    protected $fields_arr = array();
    /**
     * Constructor
@@ -448,6 +495,7 @@
        foreach ($this->fields_arr as $attrib) {
            $out .= self::tag($this->tagname, array('type' => $this->type) + $attrib);
        }
        return $out;
    }
}
@@ -455,7 +503,8 @@
/**
 * Class to create HTML radio buttons
 *
 * @package HTML
 * @package    Framework
 * @subpackage View
 */
class html_radiobutton extends html_inputfield
{
@@ -466,6 +515,7 @@
     *
     * @param string $value  Value of the checked field
     * @param array  $attrib Additional attributes to override
     *
     * @return string HTML output
     */
    public function show($value = '', $attrib = null)
@@ -485,7 +535,8 @@
/**
 * Class to create HTML checkboxes
 *
 * @package HTML
 * @package    Framework
 * @subpackage View
 */
class html_checkbox extends html_inputfield
{
@@ -496,6 +547,7 @@
     *
     * @param string $value  Value of the checked field
     * @param array  $attrib Additional attributes to override
     *
     * @return string HTML output
     */
    public function show($value = '', $attrib = null)
@@ -515,7 +567,8 @@
/**
 * Class to create an HTML textarea
 *
 * @package HTML
 * @package    Framework
 * @subpackage View
 */
class html_textarea extends html
{
@@ -528,6 +581,7 @@
     *
     * @param string $value  Textbox value
     * @param array  $attrib Additional attributes to override
     *
     * @return string HTML output
     */
    public function show($value = '', $attrib = null)
@@ -573,7 +627,8 @@
 * print $select->show('CH');
 * </pre>
 *
 * @package HTML
 * @package    Framework
 * @subpackage View
 */
class html_select extends html
{
@@ -587,16 +642,17 @@
     *
     * @param mixed $names  Option name or array with option names
     * @param mixed $values Option value or array with option values
     * @param array $attrib Additional attributes for the option entry
     */
    public function add($names, $values = null)
    public function add($names, $values = null, $attrib = array())
    {
        if (is_array($names)) {
            foreach ($names as $i => $text) {
                $this->options[] = array('text' => $text, 'value' => $values[$i]);
                $this->options[] = array('text' => $text, 'value' => $values[$i]) + $attrib;
            }
        }
        else {
            $this->options[] = array('text' => $names, 'value' => $values);
            $this->options[] = array('text' => $names, 'value' => $values) + $attrib;
        }
    }
@@ -605,6 +661,7 @@
     *
     * @param string $select Value of the selection option
     * @param array  $attrib Additional attributes to override
     *
     * @return string HTML output
     */
    public function show($select = array(), $attrib = null)
@@ -627,7 +684,7 @@
                $option_content = self::quote($option_content);
            }
            $this->content .= self::tag('option', $attr, $option_content);
            $this->content .= self::tag('option', $attr + $option, $option_content, array('value','label','class','style','title','disabled','selected'));
        }
        return parent::show();
@@ -638,7 +695,8 @@
/**
 * Class to build an HTML table
 *
 * @package HTML
 * @package    Framework
 * @subpackage View
 */
class html_table extends html
{
@@ -646,8 +704,8 @@
    protected $allowed = array('id','class','style','width','summary',
        'cellpadding','cellspacing','border');
    private $header = array();
    private $rows = array();
    private $header   = array();
    private $rows     = array();
    private $rowindex = 0;
    private $colindex = 0;
@@ -658,8 +716,13 @@
     */
    public function __construct($attrib = array())
    {
        $default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => 0) : array();
        $this->attrib = array_merge($attrib, $default_attrib);
        $default_attrib = self::$doctype == 'xhtml' ? array('summary' => '', 'border' => '0') : array();
        $this->attrib   = array_merge($attrib, $default_attrib);
        if (!empty($attrib['tagname']) && $attrib['tagname'] != 'table') {
          $this->tagname = $attrib['tagname'];
          $this->allowed = self::$common_attrib;
        }
    }
    /**
@@ -675,7 +738,7 @@
        }
        $cell = new stdClass;
        $cell->attrib = $attr;
        $cell->attrib  = $attr;
        $cell->content = $cont;
        $this->rows[$this->rowindex]->cells[$this->colindex] = $cell;
@@ -699,16 +762,16 @@
        }
        $cell = new stdClass;
        $cell->attrib = $attr;
        $cell->content = $cont;
        $cell->attrib   = $attr;
        $cell->content  = $cont;
        $this->header[] = $cell;
    }
     /**
    /**
     * Remove a column from a table
     * Useful for plugins making alterations
     *
     * @param string $class
     *
     * @param string $class
     */
    public function remove_column($class)
    {
@@ -742,7 +805,7 @@
        $this->colindex = 0;
        $this->rows[$this->rowindex] = new stdClass;
        $this->rows[$this->rowindex]->attrib = $attr;
        $this->rows[$this->rowindex]->cells = array();
        $this->rows[$this->rowindex]->cells  = array();
    }
    /**
@@ -759,6 +822,11 @@
        if ($index === null) {
            $index = $this->rowindex;
        }
        // make sure row object exists (#1489094)
        if (!$this->rows[$index]) {
            $this->rows[$index] = new stdClass;
        }
        $this->rows[$index]->attrib = $attr;
@@ -784,12 +852,14 @@
     * Build HTML output of the table data
     *
     * @param array $attrib Table attributes
     *
     * @return string The final table HTML code
     */
    public function show($attrib = null)
    {
        if (is_array($attrib))
        if (is_array($attrib)) {
            $this->attrib = array_merge($this->attrib, $attrib);
        }
        $thead = $tbody = "";
@@ -797,19 +867,20 @@
        if (!empty($this->header)) {
            $rowcontent = '';
            foreach ($this->header as $c => $col) {
                $rowcontent .= self::tag('td', $col->attrib, $col->content);
                $rowcontent .= self::tag($this->_head_tagname(), $col->attrib, $col->content);
            }
            $thead = self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib));
            $thead = $this->tagname == 'table' ? self::tag('thead', null, self::tag('tr', null, $rowcontent, parent::$common_attrib)) :
                self::tag($this->_row_tagname(), array('class' => 'thead'), $rowcontent, parent::$common_attrib);
        }
        foreach ($this->rows as $r => $row) {
            $rowcontent = '';
            foreach ($row->cells as $c => $col) {
                $rowcontent .= self::tag('td', $col->attrib, $col->content);
                $rowcontent .= self::tag($this->_col_tagname(), $col->attrib, $col->content);
            }
            if ($r < $this->rowindex || count($row->cells)) {
                $tbody .= self::tag('tr', $row->attrib, $rowcontent, parent::$common_attrib);
                $tbody .= self::tag($this->_row_tagname(), $row->attrib, $rowcontent, parent::$common_attrib);
            }
        }
@@ -818,7 +889,7 @@
        }
        // add <tbody>
        $this->content = $thead . self::tag('tbody', null, $tbody);
        $this->content = $thead . ($this->tagname == 'table' ? self::tag('tbody', null, $tbody) : $tbody);
        unset($this->attrib['cols'], $this->attrib['rowsonly']);
        return parent::show();
@@ -831,7 +902,7 @@
     */
    public function size()
    {
      return count($this->rows);
        return count($this->rows);
    }
    /**
@@ -843,4 +914,30 @@
        $this->rowindex = 0;
    }
    /**
     * Getter for the corresponding tag name for table row elements
     */
    private function _row_tagname()
    {
        static $row_tagnames = array('table' => 'tr', 'ul' => 'li', '*' => 'div');
        return $row_tagnames[$this->tagname] ?: $row_tagnames['*'];
    }
    /**
     * Getter for the corresponding tag name for table row elements
     */
    private function _head_tagname()
    {
        static $head_tagnames = array('table' => 'th', '*' => 'span');
        return $head_tagnames[$this->tagname] ?: $head_tagnames['*'];
    }
    /**
     * Getter for the corresponding tag name for table cell elements
     */
    private function _col_tagname()
    {
        static $col_tagnames = array('table' => 'td', '*' => 'span');
        return $col_tagnames[$this->tagname] ?: $col_tagnames['*'];
    }
}