<?php
/**
 * @package Horde_Mobile
 */
class Horde_Mobile_card extends Horde_Mobile_element {

    var $_name;
    var $_title;
    var $_form;
    var $_elements = array();
    var $_softkeys = array();
    var $_linksetAdded = false;

    /**
     * Constructor
     *
     * @param string $name   The name of this card. Can be used in anchor
     *                       links.
     * @param string $title  If a string is provided here, it will be displayed
     *                       in the HTML title bar, respectively somewhere on
     *                       the WAP display. Using a title you will normally
     *                       have to spend one of your few lines on your WAP
     *                       display. Consider that some WAP phones/SDK's and
     *                       handheld devices don't display the title at all.
     */
    function Horde_Mobile_card($name = null, $title = null)
    {
        $this->_name = $name;
        $this->_title = $title;
    }

    function &add(&$element)
    {
        if (!is_a($element, 'Horde_Mobile_element')) {
            $error = PEAR::raiseError('Invalid element.');
            return $error;
        } elseif (is_a($element, 'Horde_Mobile_text') ||
                  is_a($element, 'Horde_Mobile_image') ||
                  is_a($element, 'Horde_Mobile_link') ||
                  is_a($element, 'Horde_Mobile_phone') ||
                  is_a($element, 'Horde_Mobile_rule')) {
            $block = &new Horde_Mobile_block($element);
            $this->_elements[] = &$block;
        } elseif (is_a($element, 'Horde_Mobile_block')) {
            $this->_elements[] = &$element;
        } elseif (is_a($element, 'Horde_Mobile_form')) {
            if (!empty($this->_form)) {
                $error = PEAR::raiseError('Cards may only contain one Form element.');
                return $error;
            }
            $this->_elements[] = &$element;
            $this->_form = &$element;
        } elseif (is_a($element, 'Horde_Mobile_linkset')) {
            if ($this->_linksetAdded) {
                $error = PEAR::raiseError('Cards may only contain one Linkset element.');
                return $error;
            }

            $this->_elements[] = &$element;
            $this->_linksetAdded = true;
        } else {
            $error = PEAR::raiseError('This element must be inside an appropriate container element.');
            return $error;
        }

        return $element;
    }

    function softkey($url, $label)
    {
        $this->_softkeys[] = array('url' => $url,
                                   'label' => $label);
    }

}

/**
 * Horde_Mobile::
 *
 * Horde API for generating Mobile content. Includes numerous utility
 * functions, generalized element classes, and renderers for markup
 * languages including WML, HDML, and CHTML.
 *
 * This class is the top level class of all Horde_Mobile classes. Your
 * page should consist of exactly one Horde_Mobile object. Appropriate
 * markup - Imode, WML, HDML, etc. - is generated by the appropriate
 * renderer object
 *
 * Do not overstuff Horde_Mobile objects. Remember that a lot of WAP
 * clients cannot handle more than about 1400 bytes of compiled data.
 *
 * Examples:
 *
 * $myPage = new Horde_Mobile();
 * $myPage = new Horde_Mobile('My WAP page');
 * $myPage = new Horde_Mobile('', 'center');
 *
 * // More stuff
 *
 * $myPage->add($myText);
 *
 * // More items
 *
 * $myPage->render();
 *
 * $Horde: framework/Mobile/Mobile.php,v 1.32.10.14 2009-01-06 15:23:25 jan Exp $
 *
 * Copyright 2002-2009 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @since   Horde 3.0
 * @package Horde_Mobile
 */
class Horde_Mobile extends Horde_Mobile_card {

    var $_title;
    var $_elements = array();
    var $_cards = array();
    var $_debug = false;

    // Decide whether the simulator device is to be used. Only affects
    // HTML browser output.
    var $_simulator = false;

    /**
     * Constructor
     *
     * @param string $title  If a string is provided here, it will be displayed
     *                       in the HTML title bar, respectively somewhere on
     *                       the WAP display. Using a title you will normally
     *                       have to spend one of your few lines on your WAP
     *                       display. Consider that some WAP phones/SDK's and
     *                       handheld devices don't display the title at all.
     * @param string $agent  If specified, use instead of HTTP_USER_AGENT.
     */
    function Horde_Mobile($title = null, $agent = null)
    {
        if (!is_null($title)) {
            $this->_title = $title;
        }

        require_once 'Horde/Browser.php';
        $browser = &Browser::singleton($agent);

        if ($browser->hasFeature('html')) {
            $ml = 'html';
        } elseif ($browser->hasFeature('wml')) {
            $ml = 'wml';
        } else {
            $ml = 'html';
        }

        require_once dirname(__FILE__) . '/Mobile/Renderer.php';
        $this->_renderer = &Horde_Mobile_Renderer::singleton($ml, $browser);
    }

    function &add(&$element)
    {
        if (is_a($element, 'Horde_Mobile_card')) {
            if (count($this->_elements)) {
                $error = PEAR::raiseError('You cannot mix Horde_Mobile_cards and other elements at the deck level.');
                return $error;
            }

            $this->_usingCards = true;
            $this->_cards[] = &$element;

            return $element;
        } else {
            if (count($this->_cards)) {
                $error = PEAR::raiseError('You cannot mix Horde_Mobile_cards and other elements at the deck level.');
                return $error;
            }

            return parent::add($element);
        }
    }

    /**
     * Activates the built-in device simulator on bigscreen browsers.
     * The device simulator is only fully-functional in Internet
     * Explorer, because the layout requires a scrollable table
     * element. Other browsers will fail to show content on pages
     * longer than a single screen.
     */
    function useSimulator()
    {
        $this->_simulator = true;
    }

    /**
     * Creates the page in the appropriate markup language. Depending
     * on the renderer type, HTML (pure HTML, handheldfriendly AvantGo
     * HTML, i-mode cHTML, MML), WML or HDML code is created.
     */
    function display()
    {
        $this->_renderer->render($this);
    }

}

/**
 * @package Horde_Mobile
 */
class Horde_Mobile_element {

    /**
     * @TODO Remove in Horde 4.0
     */
    function Horde_Mobile_element()
    {
    }

    function get($attribute)
    {
        $attr = '_' . $attribute;
        if (isset($this->$attr)) {
            return $this->$attr;
        } else {
            return null;
        }
    }

    function set($attribute, $value)
    {
        $attr = '_' . $attribute;
        $this->$attr = $value;
    }

}

/**
 * @package Horde_Mobile
 */
class Horde_Mobile_formElement extends Horde_Mobile_element {

    var $_name;
    var $_value;
    var $_label;
    var $_size;
    var $_maxlength;
    var $_type;
    var $_format;
    var $_mode;

    /**
     * Set input mode/istyle for japanese MML/i-mode devices.
     *
     * @param string $mode  Input mode, one of:
     *                      'alpha' (default)
     *                      'katakana'
     *                      'hiragana'
     *                      'numeric'
     */
    function setMode($mode)
    {
        $this->_mode = $mode;

        // Map the mode into an appropriate format string, used for
        // WML and HDML. If a format string was provided earlier, it
        // will be overwritten.
        switch ($mode) {
        case 'hiragana':
        case 'katakana':
            $this->_format = '*M';
            break;

        case 'alpha':
            $this->_format = '*m';
            break;

        case 'numeric':
            $this->_format = '*N';
            break;
        }
    }

}

/**
 * This class defines a form with various possible input elements. The
 * input elements have to be defined as separate objects and are
 * linked to the form with a special "add" function. One Horde_Mobile
 * object can contain only one Horde_Mobile_form object.
 *
 * Examples:
 *
 * $myPage = new Horde_Mobile(...);
 *
 * $myForm = new Horde_Mobile_form("/mynextpage.wml");
 * $myText = new Horde_Mobile_text(...);
 * $myForm->add($myText);
 * $myInput = new Horde_Mobile_input(...);
 * $myForm->add($myInput);
 * $mySubmit = new Horde_Mobile_submit(...);
 * $myForm->add($mySubmit);
 *
 * $myPage->add($myForm);
 *
 * $myPage->render();
 *
 * @see Horde_Mobile_text
 * @see Horde_Mobile_image
 * @see Horde_Mobile_table
 * @see Horde_Mobile_dl
 * @see Horde_Mobile_input
 * @see Horde_Mobile_textarea
 * @see Horde_Mobile_select
 * @see Horde_Mobile_radio
 * @see Horde_Mobile_checkbox
 * @see Horde_Mobile_submit
 * @see Horde_Mobile_rule
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_form extends Horde_Mobile_element {

    var $_url;
    var $_method;
    var $_elements = array();

    /**
     * Constructor
     *
     * @param string $url       Address where the user input is sent to.
     * @param string $method    'post' (default) or 'get'.
     * @param boolean $session  Preserve the sesion id in the form? Defaults to true.
     */
    function Horde_Mobile_form($url, $method = 'post', $session = true)
    {
        $this->_url = $url;
        $this->_method = $method;

        if ($session && !array_key_exists(session_name(), $_COOKIE)) {
            $this->add(new Horde_Mobile_hidden(session_name(), session_id()));
        }
    }

    function &add(&$formElement)
    {
        if (is_a($formElement, 'Horde_Mobile_submit')) {
            $formElement->_form = &$this;
            $block = &new Horde_Mobile_block($formElement);
            $this->_elements[] = &$block;
        } elseif (is_a($formElement, 'Horde_Mobile_hidden') ||
                  is_a($formElement, 'Horde_Mobile_block')) {
            $this->_elements[] = &$formElement;
        } elseif (is_a($formElement, 'Horde_Mobile_formElement') ||
                  is_a($formElement, 'Horde_Mobile_text') ||
                  is_a($formElement, 'Horde_Mobile_rule') ||
                  is_a($formElement, 'Horde_Mobile_image')) {
            $block = &new Horde_Mobile_block($formElement);
            $this->_elements[] = &$block;
        } else {
            $error = PEAR::raiseError('Specified element cannot be inside a form.');
            return $error;
        }

        return $formElement;
    }

    function getDefaults()
    {
        $defaults = array();
        foreach ($this->_elements as $val) {
            switch (strtolower(get_class($val))) {
            case 'horde_mobile_hidden':
                $defaults[] = array('name' => $val->get('name'),
                                    'value' => $val->get('value'),
                                    'hidden' => true);
                break;

            case 'horde_mobile_block':
                foreach ($val->_elements as $bval) {
                    switch (strtolower(get_class($bval))) {
                    case 'horde_mobile_checkbox':
                        if ($bval->isChecked()) {
                            $defaults[] = array('name' => $bval->get('name'),
                                                'value' => $bval->get('value'));
                        }
                        break;

                    case 'horde_mobile_input':
                    case 'horde_mobile_textarea':
                    case 'horde_mobile_select':
                    case 'horde_mobile_radio':
                        $defaults[] = array('name' => $bval->get('name'),
                                            'value' => $bval->get('value'));
                        break;

                    case 'horde_mobile_hidden':
                        $defaults[] = array('name' => $bval->get('name'),
                                            'value' => $bval->get('value'),
                                            'hidden' => true);
                        break;

                    case 'horde_mobile_table':
                        foreach ($bval->_rows as $row) {
                            foreach ($row->_columns as $col) {
                                switch (strtolower(get_class($col))) {
                                case 'horde_mobile_checkbox':
                                    if ($col->isChecked()) {
                                        $defaults[] = array('name' => $bval->get('name'),
                                                            'value' => $bval->get('value'));
                                    }
                                    break;

                                case 'horde_mobile_input':
                                case 'horde_mobile_textarea':
                                case 'horde_mobile_select':
                                case 'horde_mobile_radio':
                                    $defaults[] = array('name' => $bval->get('name'),
                                                        'value' => $bval->get('value'));
                                    break;

                                case 'horde_mobile_hidden':
                                    $defaults[] = array('name' => $bval->get('name'),
                                                        'value' => $bval->get('value'),
                                                        'hidden' => true);
                                    break;
                                }
                            }
                        }
                        break;

                    case 'horde_mobile_dl':
                        foreach ($bval->_dts as $dt) {
                            foreach ($dt->_dds as $dd) {
                                switch (strtolower(get_class($dd))) {
                                case 'horde_mobile_checkbox':
                                    if ($dd->isChecked()) {
                                        $defaults[] = array('name'  => $bval->get('name'),
                                                            'value' => $bval->get('value'));
                                    }
                                    break;

                                case 'horde_mobile_input':
                                case 'horde_mobile_textarea':
                                case 'horde_mobile_select':
                                case 'horde_mobile_radio':
                                    $defaults[] = array('name'  => $bval->get('name'),
                                                        'value' => $bval->get('value'));
                                    break;

                                case 'horde_mobile_hidden':
                                    $defaults[] = array('name'  => $bval->get('name'),
                                                        'value' => $bval->get('value'));
                                    break;
                                }
                            }
                        }
                        break;
                    }
                }
                break;
            }
        }

        return $defaults;
    }

    function getGetVars()
    {
        // Determine all elements that have to be submitted.
        $getvars = array();
        foreach ($this->_elements as $val) {
            switch (strtolower(get_class($val))) {
            case 'horde_mobile_block':
                foreach ($val->_elements as $bval) {
                    switch (strtolower(get_class($bval))) {
                    case 'horde_mobile_input':
                    case 'horde_mobile_hidden':
                    case 'horde_mobile_textarea':
                    case 'horde_mobile_select':
                    case 'horde_mobile_checkbox':
                    case 'horde_mobile_radio':
                        $getvars[] = $bval->get('name');
                        break;

                    case 'horde_mobile_table':
                        foreach ($bval->_rows as $row) {
                            foreach ($row->_columns as $col) {
                                switch (strtolower(get_class($col))) {
                                case 'horde_mobile_input':
                                case 'horde_mobile_hidden':
                                case 'horde_mobile_textarea':
                                case 'horde_mobile_select':
                                case 'horde_mobile_checkbox':
                                case 'horde_mobile_radio':
                                    $getvars[] = $col->get('name');
                                    break;
                                }
                            }
                        }
                        break;

                    case 'horde_mobile_dl':
                        foreach ($bval->_dts as $dt) {
                            foreach ($dt->_dds as $dd) {
                                switch (strtolower(get_class($dd))) {
                                case 'horde_mobile_input':
                                case 'horde_mobile_hidden':
                                case 'horde_mobile_textarea':
                                case 'horde_mobile_select':
                                case 'horde_mobile_checkbox':
                                case 'horde_mobile_radio':
                                    $getvars[] = $dd->get('name');
                                    break;
                                }
                            }
                        }
                        break;
                    }
                }
                break;
            }
        }

        return $getvars;
    }

}

/**
 * This class holds text-level elements for use in Horde_Mobile or
 * Horde_Mobile_form objects.
 *
 * Examples:
 *
 * $block = new Horde_Mobile_block("Hello World");
 * $text = new Horde_Mobile_text("Welcome to Horde_Mobile", 'b');
 * $block->add($text);
 *
 * @see Horde_Mobile
 * @see Horde_Mobile_form
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_block extends Horde_Mobile_element {

    var $_elements = array();

    /**
     * Constructor.
     *
     * @param mixed $elements  Any elements (a single one or an array) to fill this block with.
     */
    function Horde_Mobile_block(&$elements)
    {
        if (!is_null($elements)) {
            if (!is_array($elements)) {
                $this->add($elements);
            } else {
                foreach ($elements as $element) {
                    if ($this->allows($element)) {
                        $this->_elements[] = $element;
                    }
                }
            }
        }
    }

    function &add(&$element)
    {
        if (!is_a($element, 'Horde_Mobile_element')) {
            $error = PEAR::raiseError('Invalid element.');
        } elseif ($this->allows($element)) {
            $this->_elements[] = &$element;
            return $element;
        } else {
            $error = PEAR::raiseError('The element is not allowed inside a block.');
        }
        return $error;
    }

    function allows($element)
    {
        return (is_a($element, 'Horde_Mobile_text') ||
                is_a($element, 'Horde_Mobile_table') ||
                is_a($element, 'Horde_Mobile_dl') ||
                is_a($element, 'Horde_Mobile_image') ||
                is_a($element, 'Horde_Mobile_formElement') ||
                is_a($element, 'Horde_Mobile_link') ||
                is_a($element, 'Horde_Mobile_linkset') ||
                is_a($element, 'Horde_Mobile_phone') ||
                is_a($element, 'Horde_Mobile_rule'));
    }

}

/**
 * This class inserts plain text into a Horde_Mobile_block or a
 * Horde_Mobile_row object.
 *
 * Examples:
 *
 * $myText1 = new Horde_Mobile_text("Hello World");
 * $myText2 = new Horde_Mobile_text("Welcome to Horde_Mobile", 'b');
 * $myText3 = new Horde_Mobile_text("Good Morning", array('b', 'big'));
 *
 * @see Horde_Mobile_block
 * @see Horde_Mobile_row
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_text extends Horde_Mobile_element {

    var $_text = '';
    var $_attributes = array();
    var $_linebreaks = false;

    /**
     * Constructor
     * @param string $text       The text content of the element.
     * @param array $attributes  Text attributes. Any of:
     *   'b'
     *   'u'
     *   'i'
     *   'big'
     *   'small'
     */
    function Horde_Mobile_text($text, $attributes = array())
    {
        $this->_text = $text;
        if (!is_array($attributes)) {
            $attributes = array($attributes);
        }
        $this->_attributes = $attributes;
    }

}

/**
 * This class allows to insert bitmap images into a Horde_Mobile_block,
 * Horde_Mobile_form or Horde_Mobile_row object.
 *
 * Examples:
 *
 * $image = new Horde_Mobile_image('/path/to/image.wbmp',
 *                                 array('height' => 100, 'width' => 100));
 *
 * @see Horde_Mobile_block
 * @see Horde_Mobile_form
 * @see Horde_Mobile_row
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_image extends Horde_Mobile_element {

    var $_src = '';
    var $_attributes = array();

    /**
     * Constructor
     * @param string $src        The source location of the image.
     * @param array $attributes  Image attributes. Any of:
     *   'align'
     *   'alt'
     *   'height'
     *   'hspace'
     *   'vspace'
     *   'width'
     *   'class'
     *   'id'
     */
    function Horde_Mobile_image($src, $attributes = array())
    {
        $this->_src = $src;
        $this->_attributes = $attributes;
    }

}

/**
 * This class allows to insert tables into a Horde_Mobile or
 * Horde_Mobile_form object.
 *
 * Examples:
 *
 * $myTable = new Horde_Mobile_table();
 *
 * $row1 = new Horde_Mobile_row();
 * $row1->add($image1);
 * $row1->add($text1);
 * $myTable->add($row1);
 *
 * $row2 = new Horde_Mobile_row();
 * $row2->add($image2);
 * $row2->add($text2);
 * $myTable->add($row2);
 *
 * $myDeck->add($myTable);
 *
 * @see Horde_Mobile
 * @see Horde_Mobile_form
 * @see Horde_Mobile_row
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_table extends Horde_Mobile_block {

    var $_rows = array();
    var $_border = null;
    var $_padding = null;
    var $_spacing = null;

    /**
     * Constructor.  Need because Horde_Mobile_block requires a reference
     * parameter and PHP 4 does not allow default arguments.
     *
     * @TODO Remove in Horde 4.0
     */
    function Horde_Mobile_table()
    {
    }

    /**
     * Adds a Horde_Mobile_row object to Horde_Mobile_table.
     *
     * @param Horde_Mobile_row $row  The row object to add.
     */
    function &add(&$row)
    {
        if (!is_a($row, 'Horde_Mobile_row')) {
            $error = PEAR::raiseError('Rows must be Horde_Mobile_row objects.');
            return $error;
        }

        $this->_rows[] = &$row;
        return $row;
    }

}

/**
 * This class defines the rows that a Horde_Mobile_table object
 * consists of.
 *
 * Examples:
 *
 * $image1 = new Horde_Mobile_image("my_image.wbmp", "my_image.png", ":-)");
 * $text1 = new Horde_Mobile_text("my text");
 * $row1 = new Horde_Mobile_row();
 * $row1->add($image1);
 * $row1->add();
 * $row1->add($text1);
 *
 * @see Horde_Mobile_table
 * @see Horde_Mobile_text
 * @see Horde_Mobile_image
 * @see Horde_Mobile_link
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_row extends Horde_Mobile_element {

    var $_columns = array();

    /**
     * Adds a column element to a Horde_Mobile_row object.
     *
     * @param Horde_Mobile_element $cellElement  Can be a Horde_Mobile_text
     *                                           object, a Horde_Mobile_image
     *                                           object, a Horde_Mobile_link
     *                                           object or null. The latter
     *                                           results in an empty cell.
     */
    function &add($cellElement = null)
    {
        if (is_object($cellElement)) {
            if (!is_a($cellElement, 'Horde_Mobile_text') &&
                !is_a($cellElement, 'Horde_Mobile_link') &&
                !is_a($cellElement, 'Horde_Mobile_image')) {
                $error = PEAR::raiseError('Table cells can only contain text, links, or images.');
                return $error;
            }
            $this->_columns[] = &$cellElement;
            return $cellElement;
        } elseif (!is_null($cellElement)) {
            $t = &new Horde_Mobile_text($cellElement);
            $this->_columns[] = &$t;
            return $t;
        } else {
            $this->_columns[] = &$cellElement;
            return $cellElement;
        }
    }

    function getColumnCount()
    {
        return count($this->_columns);
    }

}

/**
 * This class allows to insert definition lists into a Horde_Mobile or
 * Horde_Mobile_form object.
 *
 * Examples:
 *
 * $myDl = new Horde_Mobile_dl();
 *
 * $dt1 = new Horde_Mobile_dt();
 * $dt1->add($image1);
 * $dt1->add($text1);
 * $myDl->add($dt1);
 *
 * $dt2 = new Horde_Mobile_dt();
 * $dt2->add($image2);
 * $dt2->add($text2);
 * $myDl->add($dt2);
 *
 * $myDeck->add($myDl);
 *
 * @see Horde_Mobile
 * @see Horde_Mobile_form
 * @see Horde_Mobile_dt
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_dl extends Horde_Mobile_block {

    var $_dts = array();

    /**
     * Constructor.  Need because Horde_Mobile_block requires a reference
     * parameter and PHP 4 does not allow default arguments.
     *
     * @TODO Remove in Horde 4.0
     */
    function Horde_Mobile_dl()
    {
    }

    /**
     * Adds a Horde_Mobile_dt object to Horde_Mobile_dl.
     *
     * @param Horde_Mobile_dt $dt The dl object to add.
     */
    function &add(&$dt)
    {
        if (!is_a($dt, 'Horde_Mobile_dt')) {
            $error = PEAR::raiseError('Must be Horde_Mobile_dt objects.');
            return $error;
        }

        $this->_dts[] = &$dt;
        return $dt;
    }

}

/**
 * This class defines the terms of a Horde_Mobile_dl object.
 *
 * Examples:
 *
 * $image1 = new Horde_Mobile_image("my_image.wbmp", "my_image.png", ":-)");
 * $text1 = new Horde_Mobile_text("my text");
 * $dt1 = new Horde_Mobile_dt();
 * $dt1->add($image1);
 * $dt1->add();
 * $dt1->add($text1);
 *
 * @see Horde_Mobile_dl
 * @see Horde_Mobile_text
 * @see Horde_Mobile_image
 * @see Horde_Mobile_link
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_dt extends Horde_Mobile_element {

    var $_dds = array();

    /**
     * Adds a definition term element to a Horde_Mobile_dt object.
     *
     * @param Horde_Mobile_Element $ddElement  Can be a Horde_Mobile_text
     *                                         object, a Horde_Mobile_image
     *                                         object, a Horde_Mobile_link
     *                                         object or null. The latter
     *                                         results in an empty dd.
     *
     * @return Horde_Mobile_Element
     */
    function &add($ddElement = null)
    {
        if (is_object($ddElement)) {
            if (!is_a($ddElement, 'Horde_Mobile_text') &&
                !is_a($ddElement, 'Horde_Mobile_link') &&
                !is_a($ddElement, 'Horde_Mobile_image')) {
                $error = PEAR::raiseError('Description can only contain text, links or images.');
                return $error;
            }
            $this->_dds[]= &$ddElement;
            return $ddElement;
        } elseif (!is_null($ddElement)) {
            $t = &new Horde_Mobile_text($ddElement);
            $this->_dds[] = &$t;
            return $t;
        } else {
            $this->_dds[] = &$ddElement;
            return $ddElement;
        }
    }

    function getDdsCount()
    {
        return count($this->_dds);
    }

}

/**
 * This class provides a text input field in a Horde_Mobile_form object.
 *
 * Examples:
 *
 * $myInput1 = new Horde_Mobile_input('cid', '', 'Customer ID');
 *
 * $myInput2 = new Horde_Mobile_input('cid', '', 'Customer ID', '*N');
 * $myInput2->set_size(6);
 * $myInput2->set_maxlength(6);
 *
 * $myInput3 = new Horde_Mobile_input('pw', '', 'Password', '*N');
 * $myInput3->set_size(8);
 * $myInput3->set_maxlength(8);
 * $myInput3->set_type('password');
 *
 * @see Horde_Mobile_form
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_input extends Horde_Mobile_formElement {

    /**
     * Constructor
     *
     * @param string $name    Variable in which the input is sent to the
     *                        destination URL.
     * @param string $value   Initial value that will be presented in the
     *                        input field.
     * @param string $label   Describes your input field on the surfer's
     *                        screen/display.
     * @param string $format  Input format code according to the WAP standard.
     *                        Allows the WAP user client e.g. to input only
     *                        digits and no characters. On an HTML generated
     *                        page this format has no significance.
     */
    function Horde_Mobile_input($name, $value, $label = '', $format = '*M')
    {
        $this->_name = $name;
        $this->_value = $value;
        $this->_label = $label;
        $this->_format = $format;
        $this->_type = 'text';
        $this->_mode = 'alpha';
    }

}

/**
 * This class provides an input textarea in a Horde_Mobile_form object.
 *
 * Examples:
 *
 * $myArea1 = new Horde_Mobile_textarea('fb', '', 'Feedback');
 * $myArea2 = new Horde_Mobile_textarea('msg', 'Enter message here ...', 'Message', 40, 5);
 *
 * @see Horde_Mobile_form
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_textarea extends Horde_Mobile_formElement {

    var $_rows;
    var $_cols;

    /**
     * Constructor.
     *
     * @param string $name   Variable in which the input is sent to the
     *                       destination URL.
     * @param string $value  Initial value that will be presented in the
     *                       textarea.
     * @param string $label  Describes your textarea on the surfer's
     *                       screen/display.
     * @param integer $rows  Rows.
     * @param integer $cols  Columns.
     */
    function Horde_Mobile_textarea($name, $value, $label, $rows = 3, $cols = 16)
    {
        $this->_name = $name;
        $this->_value = $value;
        $this->_label = $label;
        $this->_rows = $rows;
        $this->_cols = $cols;
        $this->_mode = 'alpha';
    }

}

/**
 * This class provides a select element in a Horde_Mobile_form object.
 * It allows to create optimized WML for WAP devices which are capable
 * to interprete the Openwave GUI extensions for WML 1.3. All other
 * WML devices receive WML 1.1 compatible markup code, which is quite
 * similar to the markup code created by the Horde_Mobile_radio class.
 *
 * Examples:
 *
 * $mySelect = new Horde_Mobile_select('color');
 * $mySelect->add('Blue', 'b');
 * $mySelect->add('Red', 'r', true);
 * $mySelect->add('Yellow', 'y');
 *
 * @see Horde_Mobile_form
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_select extends Horde_Mobile_formElement {

    var $_type;
    var $_options = array();
    var $_htmlchars = true;

    /**
     * Constructor
     *
     * @param string $name  Variable in which the information about the
     *                      selected option is sent to the destination URL.
     * @param string $type  Type of select area:
     *                      'popup': popup the whole selection list
     *                      'spin':  rotate options on a WAP device screen (OW
     *                               1.3 GUI only).
     * @param string $label Describes your input field on the surfer's
     *                      screen/display.
     *
     * @param string $htmlchars Are the options already encoded for output?
     */
    function Horde_Mobile_select($name, $type = 'popup', $label = '', $htmlchars = false)
    {
        $this->_name = $name;
        $this->_type = $type;
        $this->_label = $label;
        $this->_value = null;
        $this->_htmlchars = $htmlchars;
    }

    /**
     * Adds one option to a Horde_Mobile_select object.
     *
     * @param string $label         Describes the option on the surfer's
     *                              screen/display.
     * @param string $value         Value sent in the "name" variable, if this
     *                              is the option selected.
     * @param boolean $is_selected  Allowed values are true or false.
     */
    function add($label, $value, $is_selected = false)
    {
        $this->_options[] = array('label' => $label,
                                  'value' => $value);

        if (is_null($this->_value) || $is_selected) {
            $this->_value = $value;
        }
    }

}

/**
 * This class provides a radio button element in a Horde_Mobile_form object.
 *
 * Examples:
 *
 * $myRadio = new Horde_Mobile_radio('country');
 * $myRadio->add('Finland', 'F');
 * $myRadio->add('Germany', 'G', true);
 * $myRadio->add('Sweden', 'S');
 *
 * @see Horde_Mobile_form
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_radio extends Horde_Mobile_formElement {

    var $_buttons = array();

    /**
     * Constructor
     *
     * @param string $name  Variable in which the information about the pressed button
     *                      is sent to the destination URL.
     */
    function Horde_Mobile_radio($name)
    {
        $this->_name = $name;
        $this->_value = null;
    }

    /**
     * Adds one radio button to a Horde_Mobile_radio object.
     *
     * @param string $label        Describes the radiobutton on the surfer's
     *                             screen/display.
     * @param string $value        Value sent in the "name" variable, if this
     *                             button is selected.
     * @param boolean $is_checked  Allowed values are true or false.
     */
    function add($label, $value, $is_checked = false)
    {
        $this->_buttons[] = array('label' => $label,
                                  'value' => $value);

        if (!$this->_value || ($is_checked)) {
            $this->_value = $value;
        }
    }

}

/**
 * This class provides a single checkbox element in a Horde_Mobile_form object.
 *
 * Examples:
 *
 * $myCheckbox = new Horde_Mobile_checkbox('agmt', 'yes', 'I agree');
 * $myCheckbox = new Horde_Mobile_checkbox('agmt', 'yes', 'I agree', false);
 * $myCheckbox = new Horde_Mobile_checkbox('agmt', 'yes', 'I agree', true);
 *
 * @note The first and second examples are identical.
 *
 * @see Horde_Mobile_form
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_checkbox extends Horde_Mobile_formElement {

    var $_checked;

    /**
     * Constructor
     *
     * @param string $name      Variable in which "value" is sent to the
     *                          destination URL, if the box is checked.
     * @param string $value     See name.
     * @param string $label     Describes the checkbox on the surfer's
     *                          screen/display.
     * @param boolean $checked  Allowed values are true or false.
     */
    function Horde_Mobile_checkbox($name, $value, $label, $checked = false)
    {
        $this->_name  = $name;
        $this->_value = $value;
        $this->_label = $label;
        $this->_checked = $checked;
    }

    function isChecked()
    {
        return $this->_checked;
    }

}

/**
 * This class provides hidden elements in Horde_Mobile_form objects.
 *
 * Examples:
 *
 * $hidden = new Horde_Mobile_hidden('internal_reference', '08154711');
 *
 * @see Horde_Mobile_form
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_hidden extends Horde_Mobile_formElement {

    /**
     * Constructor
     *
     * @param string $name   Variable in which $value is sent to the destination URL.
     * @param string $value  See name.
     */
    function Horde_Mobile_hidden($name, $value)
    {
        $this->_name = $name;
        $this->_value = $value;
    }

}

/**
 * This class provides a submit button for a Horde_Mobile_form object. One
 * Horde_Mobile_form object can contain only one Horde_Mobile_submit object.
 *
 * Examples:
 * $mySubmit = &new Horde_Mobile_submit('Submit');
 * $mySubmit = &new Horde_Mobile_submit('Submit', 'user_pressed');
 *
 * @see Horde_Mobile_form
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_submit extends Horde_Mobile_formElement {

    var $_form;

    /**
     * Constructor
     *
     * @param string $label  What's written on the button.
     * @param string $name   Variable in which "label" is sent to the
     *                       destination URL.
     */
    function Horde_Mobile_submit($label, $name = '')
    {
        $this->_label = $label;
        $this->_name = $name;
    }

}

/**
 * This class provides a link in a Horde_Mobile, Horde_Mobile_linkset or
 * Horde_Mobile_table object.
 *
 * Examples:
 *
 * $myPage = new Horde_Mobile(...);
 *
 * $myLink = new Horde_Mobile_link('Continue', '/mynextpage.wml');
 * $myPage->add($myLink);
 *
 * @see Horde_Mobile
 * @see Horde_Mobile_linkset
 * @see Horde_Mobile_table
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_link extends Horde_Mobile_element {

    var $_label;
    var $_url;
    var $_title;
    var $_accesskey;

    /**
     * Constructor
     *
     * @param string $label  Describes the link on the surfer's screen/display.
     * @param string $url    Next destination address. MUST be valid XML (&amp; instead of &, etc.).
     * @param string $title  If a string is provided here, it will be displayed
     *                       in the HTML browser status bar during
     *                       "MouseOver", respectively somewhere on the WAP
     *                       display. In order to work well with a broad range
     *                       of user agents, keep your title under 6
     *                       characters.
     */
    function Horde_Mobile_link($label, $url, $title = '')
    {
        $this->_label = $label;
        $this->_url = $url;
        $this->_title = $title;

        // No accesskey assigned by default; can be assigned later
        // from a Horde_Mobile_linkset object if required.
        $this->_accesskey = null;
    }

}

/**
 * This class provides a phone number in a Horde_Mobile object. If supported by
 * their mobile device, users can establish a voice connection to the
 * specified number.
 *
 * Examples:
 *
 * $myPhone = &new Horde_Mobile_phone('123-45678', 'CALL');
 * $myPage->add($myPhone);
 *
 * @see Horde_Mobile
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_phone extends Horde_Mobile_element {

    var $_label;
    var $_number;
    var $_title;

    /**
     * Constructor
     *
     * @param string $phone_number  Phone number to dial.
     * @param string $title         If a string is provided here, the call
     *                              button on a WAP/HDML device will be
     *                              enabled. In order to work well with
     *                              a broad range of user agents, keep your
     *                              title under 6 characters.
     */
    function Horde_Mobile_phone($phone_number, $title = '')
    {
        $this->_label = $phone_number;
        $this->_number = preg_replace('|\D|', '', $phone_number);
        $this->_title = $title;
    }

}

/**
 * This class defines a set of links. The links have to be defined as
 * separate Horde_Mobile_link objects and are attached to the linkset
 * with a special "add" function.  For WAP devices browser-dependent
 * WML code will be created. On all UP-browser-based WAP devices
 * linksets allow easier navigation through WML decks by using the
 * "onpick" WML option and therefore are improving the "usability" of
 * an application. Instead of painfully navigating through the links
 * "sports->football->results->today" the mobile user e.g. can press
 * "2431" on the keypad to enter his favorite deck. For all other WAP
 * devices normal <a> tags are created. One Horde_Mobile object can
 * contain only one linkset object.
 *
 * Examples:
 *
 * $myPage = new Horde_Mobile(...);
 *
 * $myLinkset = new Horde_Mobile_linkset();
 * $myLink1 = new Horde_Mobile_link("Phonebook", "/wap/phonebook.wml");
 * $myLinkset->add($myLink1);
 * $myLink2 = new Horde_Mobile_link("DateBook", "/wap/datebook.wml");
 * $myLinkset->add($myLink2);
 *
 * $myPage->add($myLinkset);
 *
 * $myPage->render();
 *
 * @see Horde_Mobile_link
 * @see Horde_Mobile
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_linkset extends Horde_Mobile_element {

    var $_elements;

    /**
     * Adds a Horde_Mobile_link object to Horde_Mobile_linkset.
     *
     * @param Horde_Mobile_link $link  The link object to add.
     *
     * @see Horde_Mobile_link
     */
    function &add(&$link)
    {
        if (!is_a($link, 'Horde_Mobile_link')) {
            $error = PEAR::raiseError('Links must be Horde_Mobile_link objects.');
            return $error;
        }

        $this->_elements[] = &$link;
        $link->set('accesskey', count($this->_elements));

        return $link;
    }

}

/**
 * This class will cause a horizontal rule to be drawn across the screen.  You
 * can use it to separate text paragraphs in Horde_Mobile or Horde_Mobile_form
 * objects.
 *
 * Examples:
 *
 * $myDefaultRule = new Horde_Mobile_rule();
 * $mySpecialRule = new Horde_Mobile_rule('60%', 4);
 *
 * $myPage->add($myDefaultRule);
 *
 * $myPage->add($mySpecialRule);
 *
 * @see Horde_Mobile
 * @see Horde_Mobile_form
 *
 * @package Horde_Mobile
 */
class Horde_Mobile_rule extends Horde_Mobile_element {

    var $_width;
    var $_size;

    /**
     * Constructor
     *
     * @param integer $width  Percentage of screen width or absolute value in
     *                        number of pixels (e.g. "50%", 100).
     * @param integer $size   Height of the line to be drawn in pixels.
     */
    function Horde_Mobile_rule($width = '', $size = '')
    {
        $this->_width = $width;
        $this->_size = $size;
    }

}
