/** 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 ready-to-use href which links to the node $node via index.php * * this routine creates a ready-to-use href that links to node $node, taking these * options into account: * - the href is replaced with a bare '#' if we area in preview mode * - the href is either fully qualified or abbreviated, depending on $qualified * - the node_id is conveyed either as a proxy-friendly url or a simple parameter ?node=$node_id * - if we use friendly url, $node_id always comes first in the path (without the word 'node') * - if we use friendly url, $bookmark is appended as the last item in the path * - the additional parameters (if any) are sandwiched between the node_id and the bookmark * * This routine mainly deals with constructing a friendly url taking parameters into account in the * form of path components. The node_id is conveyed as the first parameter and it has no associated * name, ie. the url is shortened from '/was/index.php/node/35' to '/was/index.php/35'. * All other parameters from the array $parameters (if any) are added as pairs: * '/key1/value1/key2/value2/key3/value3' etc. The last parameter added to this path * is based on the $bookmark or, if that is empty the node's title. The purpose of this parameter * is to create a URL that looks like a descriptive filename, which makes it easier for the visitor * to bookmark this page and still have a clue as to what the page is about. Otherwise this * parameter is not used at all; the 'real' navigation information is in the node_id and the * additional parameters. * Example: * <code> * was_node_url($tree[35]['record'],array('photo'=>'5'),'Picture of our field trip') * </code> * yields the following URL (when friendly urls are used): * <code> * /was/index.php/35/photo/5/Picture_of_our_field_trip.html * </code> * or (when friendly urls are not used): * <code> * /was/index.php?node=35&photo=5 * </code> * The interesting bits are the node_id (35) and the photo_id (5). The string alias filename * 'Picture_of_our_field_trip.html' is merely a suggestion to the browser and is not used by W@S. * * @param array $node record straight from the database (or $tree) * @param array|null $parameters additional parameters for the url (path components in friendly url mode) * @param string $bookmark the basis for a visual clue to identify the node (in friendly url mode only) * @param bool $preview if TRUE, the href is replaced with a bare '#' to obstruct navigation in preview mode * @param bool $qualified if TRUE use the scheme and authority, otherwise use the short(er) form without scheme/authority * @return string ready-to-use string holding the url * @uses $CFG * @uses friendly_bookmark() */ function was_node_url($node = NULL, $parameters = NULL, $bookmark = '', $preview = FALSE, $qualified = FALSE) { global $CFG; if ($preview) { $href = '#'; } else { $href = ($qualified ? $CFG->www : $CFG->www_short) . '/index.php'; if ($CFG->friendly_url) { // 1 -- maybe add the (unnamed) node parameter if (!empty($node)) { $node_id = intval($node['node_id']); $title = $node['title']; $href .= '/' . strval($node_id); } else { $title = ''; } // 2 -- maybe proceed with other, named parameters if (!empty($parameters)) { foreach ($parameters as $k => $v) { $k = strval($k); $v = strval($v); if ($k != '' && $v != '') { $href .= '/' . rawurlencode(strtr($k, '/?+%', '____')) . '/' . rawurlencode(strtr($v, '/?+%', '____')); } } } // 3 -- maybe finish with the superfluous bookmark-filename if (($bookmark_filename = friendly_bookmark(empty($bookmark) ? $title : $bookmark)) != '') { $href .= '/' . rawurlencode(strtr($bookmark_filename, '/?+%', '____')); } } else { if (!empty($node)) { $node_id = intval($node['node_id']); if (!is_array($parameters)) { $parameters = array(); } $parameters = array_merge(array('node' => strval($node_id)), $parameters); // node goes first } $href = href($href, $parameters); } } return $href; }