/** show a preview of the message to the visitor
 *
 * this shows a preview of the message to visitor.
 * Nothing is editable, it is view-only. The only option
 * is to either press the Send-button to actually send
 * the messate OR to press the Edit button to go back
 * to the editable form.
 *
 * Sending a message is a two-step procedure by design.
 *
 * @param object &$theme collects the (html) output
 * @param array mailpage configuration data in a (nested) array
 * @param array $dialogdef array that defines the input fields
 * @param string $ip_addr the originating IP-address
 * @return void output writted to $theme
 */
function mailpage_show_preview(&$theme, $config, $dialogdef, $ip_addr)
{
    $destinations = $forbidden = array(chr(10), chr(13), chr(34), '\\');
    $mailfrom = sprintf('"%s" <%s>', htmlspecialchars(str_replace($forbidden, '', trim($dialogdef['fullname']['value']))), htmlspecialchars(str_replace($forbidden, '', trim($dialogdef['email']['value']))));
    $subject = htmlspecialchars(trim($dialogdef['subject']['value']));
    $message = nl2br(htmlspecialchars(trim($dialogdef['message']['value'])));
    $remote_addr = htmlspecialchars($ip_addr);
    //
    // 2 -- actually output the preview
    //
    $theme->add_content(html_tag('h2', '', t('preview_header', 'm_mailpage')));
    $theme->add_content('<div>');
    $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('from', 'm_mailpage'), $mailfrom));
    if (1 < sizeof($config['addresses'])) {
        // only show destination if there is more than 1
        $index = isset($dialogdef['destination']) ? $dialogdef['destination']['value'] : 0;
        $destination = $config['addresses'][$index]['name'];
        $sendto = htmlspecialchars('"' . str_replace($forbidden, '', trim($destination)) . '"');
        $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('to', 'm_mailpage'), $sendto));
    }
    $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('subject', 'm_mailpage'), $subject));
    $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('date', 'm_mailpage'), date('r')));
    $theme->add_content(sprintf('<strong>%s</strong>: %s<br>', t('ip_addr', 'm_mailpage'), $remote_addr));
    $theme->add_content(sprintf("<strong>%s</strong>:<br>\n%s<br>", t('message', 'm_mailpage'), $message));
    $theme->add_content('</div>');
    //
    // 3 -- finish with navigation for the visitor
    //
    $previewdef = array('token' => $dialogdef['token'], 'button_edit' => dialog_buttondef(BUTTON_EDIT), 'button_send' => dialog_buttondef('button_send', t('button_send', 'm_mailpage')), 'button_cancel' => dialog_buttondef(BUTTON_CANCEL));
    $href = was_node_url($theme->node_record);
    $theme->add_content(dialog_quickform($href, $previewdef));
}
/** walk the tree and send to output in the form of nested unnumbered lists (uses recursion)
 *
 * @param object &$theme collects the output
 * @param int $subtree_id where to start walking the the tree
 * @param array &$tree the structure that holds the tree
 * @param string $m improves readability in output
 * @return void and output stored in $theme
 * @uses sitemap_tree_walk()
 */
function sitemap_tree_walk(&$theme, $subtree_id, &$tree, $m = '')
{
    static $level = 0;
    $class_level = 'level' . strval($level);
    $theme->add_content($m . '<ul>');
    for ($node_id = $subtree_id; $node_id != 0; $node_id = $tree[$node_id]['next_sibling_id']) {
        if ($tree[$node_id]['is_visible']) {
            // 1 -- show this node
            $is_page = $tree[$node_id]['is_page'];
            $class = ($is_page ? 'page ' : 'section ') . $class_level;
            $title = $tree[$node_id]['record']['title'];
            $attributes = array('title' => $title);
            $href = was_node_url($tree[$node_id]['record'], NULL, $title, $theme->preview_mode);
            $link_text = $tree[$node_id]['record']['link_text'];
            $anchor = html_a($href, NULL, $attributes, $link_text);
            $theme->add_content($m . '  ' . html_tag('li', array('class' => $class)) . $anchor);
            // 2 -- maybe descend to subsection
            if (!$is_page) {
                if (($subsubtree_id = $tree[$node_id]['first_child_id']) > 0) {
                    ++$level;
                    if ($level > MAXIMUM_ITERATIONS) {
                        logger(__FILE__ . '(' . __LINE__ . ') too many levels in node ' . $node_id);
                    } else {
                        sitemap_tree_walk($theme, $subsubtree_id, $tree, $m . '  ');
                    }
                    --$level;
                }
            }
            // current subsection
        }
        // visible
    }
    // for
    $theme->add_content($m . '</ul>');
}
/** show the (visually almost) empty page and load or continue with the JS popup window
 *
 * this routine is responsible for showing an 'empty' page and maybe for generating
 * a JS popup window (if $first==TRUE). The 'empty' page contains only a form with
 * single textarea. However, this textarea is not displayed (display:none) so the
 * casual user sees nothing (but obviously without CSS it is a different matter).
 * This textarea is used by the CREW code to store the edited document before
 * submitting the form. Since there are no buttons of any kind, it is completely
 * up to the JS code to generate the necessary DOM elements that are required to
 * successfully save the document.
 *
 * If $first is TRUE, we have to setup the popup window. This is quite complicated
 * because generate the necessary JS-code at runtime using JS. One of the reasons
 * is that I want to set the correct translations in the popup window. There may
 * be an easier way.
 *
 * The Websocket protocol is used to talk to the Websocket server which is configured
 * for this site. This setting can be manipulated using the Module Manager. In order
 * to authenticate ourselves against the websocket server we use the following mechanism.
 * There are a few important variables used in authenticating:
 *
 *  - $origin: this is the website's hostname as seen by the user's browser
 *  - $request_uri: a string that uniquely identifies the node within the origin
 *  - $full_name: the full name of the current user (ie. $USER->full_name) 
 *  - $username: the (short) name/userid of the curent user (ie. $USER->username)
 *  - $request_date: the current time (GMT) in the format "yyyy-mm-dd hh:mm:ss".
 *
 * and also
 *
 *  - $secret_key: a secret shared with the Websocket server
 *  - $location: the URL of the Websocket server
 *
 * The authentication works as follows. The variables $origin, $request_uri, $full_name,
 * $username and $request_date are concatenated in a $message. Then the $message and
 * the $secret_key are used to calculate a hashed message authentication code (HMAC)
 * according to RFC2104 (see function {@see hmac()} in waslib.php).
 *
 * When connecting to the Websocket server the parameters $request_uri, $full_name,
 * $username and $request_date are sent, together with the HMAC. The server then
 * calculates the HMAC too and if it matches the HMAC that was sent, access is
 * granted.
 *
 * Note that the variable $origin is only used here to calculate the HMAC; it is
 * not sent to the Websocket server like the other parameters. Instead we use the
 * Origin as seen by the user's web browser. Obviously the two should match or else
 * authentication fails. This way we check the browser's idea of where the web page
 * is located. Also note that we made the current date/time part of the HMAC. That
 * is done to prevent replay-attacks (the other variables are quasi-static between
 * CREW editing sessions). It is up to the Websocket server to determine if the
 * timestamp is (still) valid or not. This depends on a certain clock synchronisation
 * between the webserver and the Websocket server.
 *
 * Also note that the shared secret never leaves the webserver, only the hashed
 * message is sent from webserver to Websocket server. However, the secret has to
 * be the same on both ends.
 *
 * @param object &$theme collects the (html) output
 * @param int $module_id identifies the crew module (need that for getting module properties)
 * @param bool $first if TRUE we generate code to generate a popup
 * @return bool TRUE on success+output generated via $theme, FALSE otherwise
 */
function crew_view_show_edit(&$theme, $module_id, $first = FALSE)
{
    global $USER, $WAS_SCRIPT_NAME, $CFG;
    // 1A -- fetch the latest version of the document (we always need that)...
    $node_id = intval($theme->node_record['node_id']);
    if (($record = crew_view_get_workshop_data($node_id)) === FALSE) {
        $theme->add_message(t('error_retrieving_workshop_data', 'm_crew'));
        return FALSE;
    }
    // 1B -- and tell the user the date/time/user of latest update in content area
    $params = array('{USERNAME}' => is_null($record['username']) ? $record['muser_id'] : $record['username'], '{FULL_NAME}' => is_null($record['full_name']) ? $record['muser_id'] : $record['full_name'], '{DATIM}' => $record['mtime']);
    $attr = array('class' => 'crew_datim');
    $theme->add_content(html_tag('p', $attr, t('last_updated_by', 'm_crew', $params)));
    // 1C -- prepare a hidden textarea with the current document text
    /* <noscript>requires javascript</noscript>
     * <div>
     *   <form>
     *     <textarea>$document</textarea>
     *   </form>
     * </div>
     */
    $theme->add_content(html_tag('noscript', '', t('crew_requires_js_and_ws', 'm_crew')));
    $attr = array('id' => 'crew_start_edit', 'style' => 'display: none;');
    $theme->add_content(html_tag('div', $attr));
    $href = was_node_url($theme->node_record);
    $attr = array('id' => 'frmEdit');
    $theme->add_content(html_form($href, 'post', $attr));
    $attr = array('id' => 'txtText', 'rows' => 10, 'cols' => 80, 'name' => 'text');
    $theme->add_content(html_tag('textarea', $attr, htmlspecialchars($record['document'])));
    $theme->add_content(html_form_close());
    $theme->add_content(html_tag_close('div'));
    // At this point we're done IF this was a repeat call.
    // If it was the first call we need to do some more, like popping up the edit window
    if (!$first) {
        return TRUE;
    }
    // Still here, so this is the first time
    // 2 -- prepare all information for popup
    // 2A -- which skin?
    $dialogdef = crew_view_dialogdef();
    if (!dialog_validate($dialogdef)) {
        // somehow an error; default to first skin
        $value = '0';
    } else {
        $value = $dialogdef['skin']['value'];
    }
    $skin = $dialogdef['skin']['options'][$value]['css'];
    // 2B -- which location,origin,secret (from module_properties)
    $table = 'modules_properties';
    $fields = array('name', 'value');
    $where = array('module_id' => $module_id);
    $order = array('sort_order');
    $keyfield = 'name';
    if (($properties = db_select_all_records($table, $fields, $where, $order, $keyfield)) === FALSE) {
        logger(sprintf('%s(): module properties error: %s', __FUNCTION__, db_errormessage()));
        $theme->add_message(t('error_retrieving_workshop_data', 'm_crew'));
        return FALSE;
    }
    $org = $properties['origin']['value'];
    $loc = $properties['location']['value'];
    $secret = $properties['secret']['value'];
    // 2C -- prepare variables for and perform hmac calculation
    $workshop = trim($record['header']);
    if (empty($workshop)) {
        $workshop = trim($node_record['link_text']);
    }
    $uri = sprintf('%s/%d/%s', $WAS_SCRIPT_NAME, $node_id, friendly_bookmark($workshop));
    $name = $USER->full_name;
    $nick = $USER->username;
    $datim = gmstrftime('%Y-%m-%d %T');
    $hmac_key = $secret;
    $hmac_msg = $org . $uri . $name . $nick . $datim;
    $sig = hmac($hmac_key, $hmac_msg);
    $progcrew = $CFG->progwww_short . '/modules/crew';
    $css = $progcrew . '/' . $skin;
    if ($CFG->debug || !file_exists($CFG->progdir . '/modules/crew/crew.min.js')) {
        $js = $progcrew . '/crew.js';
    } else {
        $js = $progcrew . '/crew.min.js';
    }
    $theme->add_content(html_tag('script'));
    $theme->add_content(crew_screen($loc, $nick, $name, $uri, $workshop, $org, $datim, $sig, $css, $js, $progcrew));
    $theme->add_content(html_tag_close('script'));
    return TRUE;
}
/** construct a title, inline slideshow and readmore prompt for a snapshots page
 *
 * this routine uses the SnapshotViewerInline class to generate a
 * rotating inline slideshow. This leans very heavily on JavaScript.
 * If JavaScript is not enabled, that class has a fall-back showing
 * the first N images statically. Graceful degradation...
 *
 * @param int $counter is a sequential number identifying the aggregated nodes
 * @param object &$theme points to theme where the output goes
 * @param array &$node points to the node record of this htmlpage
 * @param array &$config points to the aggregator configuration
 * @param arrat &$modules points to list of records of supported modules
 * @return array ordered list of nodes to aggregate (could be empty)
 */
function aggregator_view_snapshots($counter, &$theme, &$node, &$config, &$modules)
{
    global $CFG;
    $id = strval($counter);
    // used to make all id's within this item unique
    // 1 -- outer div holds title+slideshow+readmore...
    $attributes = array('class' => 'aggregator_snapshots_outer', 'id' => 'aggregator_outer_' . $id);
    $theme->add_content(html_tag('div', $attributes));
    // 2A -- title
    $attributes = array('class' => 'aggregator_snapshots_header', 'id' => 'aggregator_header_' . $id);
    $theme->add_content('  ' . html_tag('h3', $attributes, $node['title']));
    // 2B -- slideshow (enclosed in inner div)
    $attributes = array('class' => 'aggregator_snapshots_inner', 'id' => 'aggregator_inner_' . $id);
    $theme->add_content('  ' . html_tag('div', $attributes));
    include_once $CFG->progdir . '/modules/snapshots/snapshots_view.php';
    $snap = new SnapshotViewerInline($theme, intval($node['area_id']), intval($node['node_id']), $modules[intval($node['module_id'])], $config['snapshots_width'], $config['snapshots_height'], $config['snapshots_visible']);
    $snap->default_showtime = $config['snapshots_showtime'];
    $snap->run();
    unset($snap);
    $theme->add_content('  </div>');
    // 2C -- readmore prompt
    $anchor = t('snapshots_more', 'm_aggregator');
    $title = t('snapshots_more_title', 'm_aggregator');
    $attr = array('title' => $title);
    $href = was_node_url($node, NULL, '', $theme->preview_mode);
    $attributes = array('class' => 'aggregator_snapshots_more', 'id' => 'aggregator_more_' . $id);
    $theme->add_content('  ' . html_tag('div', $attributes, html_a($href, NULL, $attr, $anchor)));
    $theme->add_content('  <div style="clear:both;"></div>');
    // 3 -- close outer div
    $theme->add_content('</div>');
}
 /** construct an anchor from a node record
  *
  * This constructs an array with key-value-pairs that can be used to
  * construct an HTML anchor tag. At least the following keys are created
  * in the resulting array: 'href', 'title' and 'anchor'. The latter is either
  * the text or a referenct to an image that is supposed to go between the
  * opening A-tag and closing A-tag. Furtermore an optional key is created: target.
  * The contents of the input array $attributes is merged into the result.
  *
  * If the parameter $textonly is TRUE the key 'anchor' is always text.
  * If $textonly is NOT TRUE, the 'anchor' may refer to an image.
  *
  * Note that the link text is always non-empty. If the node record has an
  * empty link_text, the word 'node' followed by the node_id is returned.
  * (Otherwise it will be hard to make an actual clickable link).
  *
  * Note that we attempt to create 'friendly' URLs, ie. URLs that look very
  * much like a plain path, e.g.
  * http://www.exemplum.eu/index.php/3/Information_about_the_school.html rather than
  * http://www.exemplum.eu/index.php?node=3
  * When bookmarking a page, the part 'Information_about_the_school.html' makes it
  * easier to recognise the bookmark than when it is just some number.
  * Choice for friendly URLs is made in the global (site) configuration.
  *
  * @param array $node_record the node record to convert
  * @param array $attributes optional attributes to add to the HTML A-tag
  * @param bool $textonly if TRUE, no clickable images will be returned
  * @return string an HTML A-tag that links to the node OR to the external link (if any)
  * @uses was_node_url()
  */
 function node2anchor($node_record, $attributes = NULL, $textonly = FALSE)
 {
     $node_id = intval($node_record['node_id']);
     $title = $node_record['title'];
     $link_text = $node_record['link_text'];
     if (empty($link_text)) {
         $link_text = "[ " . strval($node_id) . " ]";
     }
     $params = NULL;
     $href = was_node_url($node_record, $params, $title, $this->preview_mode);
     if (!is_array($attributes)) {
         $attributes = array();
     }
     $attributes['title'] = $title;
     if (!empty($node_record['link_target'])) {
         $attributes['target'] = $node_record['link_target'];
     }
     if ($textonly || empty($node_record['link_image'])) {
         $anchor = $link_text;
     } else {
         $img_attr = array('width' => intval($node_record['link_image_width']), 'height' => intval($node_record['link_image_height']), 'alt' => $link_text);
         $anchor = html_img(was_url($node_record['link_image']), $img_attr);
     }
     return html_a($href, $params, $attributes, $anchor);
 }
 /** construct a simple UL-based jump menu to select another area (when no Javascript is available)
  *
  * this constructs a list of clickable links to navigate to other areas.
  * This function is only used when the user has disabled Javascript (it is sandwiched between
  * noscript-tags, see {@link get_html()}). Note that there is no point in having a jump menu
  * when there is not at least another area. If there is only one, an empty string  is returned
  * 
  * @param string $m left margin for increased readability
  * @return string ready-to-use HTML or empty string if not at least 2 areas are available
  * @uses $WAS_SCRIPT_NAME
  */
 function get_menu_areas($m = '')
 {
     global $WAS_SCRIPT_NAME;
     if (sizeof($this->jumps) <= 1) {
         // do not add jump menu if there's just 1 area
         return '';
     }
     $jump_bar = $m . "<ul>\n";
     $href = $this->preview_mode ? "#" : $WAS_SCRIPT_NAME;
     foreach ($this->jumps as $area_id => $area_title) {
         $params = array('area' => $area_id);
         $href = was_node_url(NULL, $params, $area_title, $this->preview_mode);
         $attributes = $this->area_id == $area_id ? array('class' => 'current') : NULL;
         $jump_bar .= $m . '  <li>' . html_a($href, NULL, $attributes, $area_title) . "\n";
     }
     $jump_bar .= $m . "</ul>\n";
     return $jump_bar;
 }
 /** add a navigation bar / tool bar for a snapshot
  *
  * this bar contains the following elements:
  *  - double arrow left links to the first snapshot in the series
  *  - left arrow links to the previous snapshot in the series
  *  - up arrow links to the thumbnails overview
  *  - right arrow links to the next snapshot in the series
  *  - double right arrow links to the last snapshot in the series
  *  - position indicator (snapshot i from n snapshots) in the form: i/n
  *
  * @param int $snapshot_index indicates which element of $snapshots contains the current snapshot
  * @return void navigation bar added to output
  * @todo clean up this ugly code
  */
 function add_snapshot_navbar($snapshot_index, $m = '      ')
 {
     $images = array(0 => array('black' => 'llb.png', 'gray' => 'llg.png', 'title' => t('move_first_title', $this->domain), 'alt' => t('move_first_alt', $this->domain)), 1 => array('black' => 'lb.png', 'gray' => 'lg.png', 'title' => t('move_prev_title', $this->domain), 'alt' => t('move_prev_alt', $this->domain)), 2 => array('black' => 'ub.png', 'gray' => 'ug.png', 'title' => t('move_up_title', $this->domain), 'alt' => t('move_up_alt', $this->domain)), 3 => array('black' => 'rb.png', 'gray' => 'rg.png', 'title' => t('move_next_title', $this->domain), 'alt' => t('move_next_alt', $this->domain)), 4 => array('black' => 'rrb.png', 'gray' => 'rrg.png', 'title' => t('move_last_title', $this->domain), 'alt' => t('move_last_alt', $this->domain)));
     $current = $snapshot_index - 1;
     // array is 0-based, index is 1-based
     $last = sizeof($this->snapshots) - 1;
     $nav[0] = 0;
     $nav[1] = $current > 0 ? $current - 1 : ($current < 0 ? $last : $current);
     $nav[2] = NULL;
     $nav[3] = $current < $last ? $current + 1 : $current;
     $nav[4] = $last;
     $this->theme->add_content($m . html_tag('div', array('class' => 'snapshot_toolbar')));
     foreach ($nav as $id => $index) {
         $attributes = array('width' => 16, 'height' => 16, 'title' => $images[$id]['title'], 'alt' => $images[$id]['alt']);
         if ($current < 0) {
             if ($id == 1) {
                 $attributes['title'] = t('move_last_title', $this->domain);
                 $attributes['alt'] = t('move_last_alt', $this->domain);
             } elseif ($id == 3) {
                 $attributes['title'] = t('move_first_title', $this->domain);
                 $attributes['alt'] = t('move_first_alt', $this->domain);
             }
         }
         if (is_null($index)) {
             // special case: this yields the snapshots overview (thumbnails)
             if ($current < 0) {
                 // and thsi IS the snapshots overview (thumbnails): use gray icon
                 $img = html_img(was_url('program/modules/snapshots/' . $images[$id]['gray']), $attributes);
             } else {
                 $img = html_img(was_url('program/modules/snapshots/' . $images[$id]['black']), $attributes);
             }
             $params = array('variant' => '1');
             $caption = '';
         } elseif ($index === $current) {
             $params = array('snapshot' => $index + 1);
             $attributes['alt'] = t('move_current_alt', $this->domain);
             $attributes['title'] = t('move_current_title', $this->domain);
             $img = html_img(was_url('program/modules/snapshots/' . $images[$id]['gray']), $attributes);
             $caption = $this->snapshots[$index]['key'];
         } else {
             $params = array('snapshot' => $index + 1);
             $img = html_img(was_url('program/modules/snapshots/' . $images[$id]['black']), $attributes);
             $caption = $this->snapshots[$index]['key'];
         }
         $href = was_node_url($this->theme->node_record, $params, $caption, $this->theme->preview_mode);
         $this->theme->add_content($m . '  ' . html_a($href, NULL, NULL, $img));
     }
     // at this point we add another button with a link to the slideshow code
     $attributes = array('width' => 16, 'height' => 16, 'title' => t('slideshow_title', $this->domain), 'alt' => t('slideshow_alt', $this->domain));
     $img = html_img(was_url('program/modules/snapshots/sb.png'), $attributes);
     $href = sprintf('javascript:show_start(%d);', max(0, $current));
     $slideshow = str_replace('\'', '\\\'', html_a($href, NULL, NULL, $img));
     $this->theme->add_content($m . '  <script type="text/javascript"><!--');
     $this->theme->add_content($m . '    ' . sprintf('document.write(\'%s\');', $slideshow));
     $this->theme->add_content($m . '  //--></script>');
     if ($current >= 0) {
         // not in thumbnails overview
         $params = array('{SNAPSHOT}' => $current + 1, '{SNAPSHOTS}' => sizeof($this->snapshots), '{CAPTION}' => $this->snapshots[$current]['key']);
         $this->theme->add_content($m . '  ' . html_tag('span', array('class' => 'snapshot_status'), t('snapshot_status', $this->domain, $params)));
     }
     $this->theme->add_content($m . '</div>');
 }