/** 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>'); }