Ejemplo n.º 1
0
/** 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;
}
Ejemplo n.º 2
0
/** 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;
}