/** compute URL for a background image based on current time and index * * this routine returns a URL to be used as a background image. The * URL is different depending on the time of day and the $index parameter. * The latter is thought to be the position of the current item in the * main navigation (starting at 1) or 0 in case no item in the main * navigation is current. * * Configuration is done via two parameters: * the path in $this->config['header_banners_directory'] and * the interval in $this->config['header_banners_interval']. * * The interval is expressed in minutes. It means that a particular page * has the same background image for the duration of the interval and * another one in the next interval. The banners directory is supposed * to contain banner files of the correct dimensions, e.g. 980x170. * * If the specified directory starts with a '/' it is assumed to start in * the data root, e.g. something like /areas/exemplum/banners. This will * eventually lead to a URL of the form * /file.php/areas/exemplum/banners/mentha-banner.jpg using * was_file_url() * * If the specified directory does NOT start with a '/' it is assumed to * be a directory relative to the directory where index.php and friends * live, unless it starts with program/ in which case it is a static dir * somewhere in the program hierarchy. This is done via was_url(). * * @param int $index indicates current main navigation item or 0 for none * @param bool $fully_qualified if TRUE forces the URL to contain a scheme, authority etc. * @return string ready to use URL * @uses was_file_url() * @uses was_url() */ function cornelia_get_background_url($index, $fully_qualified = FALSE) { global $CFG; // // 0 -- sanity checks // $path = trim($this->config['header_banners_directory']); $interval = intval($this->config['header_banners_interval']); if (empty($path) || $interval <= 0) { return FALSE; } if (!utf8_validate($path) || strpos('/' . $path . '/', '/../') !== FALSE) { logger(sprintf("%s.%s(): invalid path '%s'; bailing out", __CLASS__, __FUNCTION__, $path)); return FALSE; } // // 1 -- where to find the files? // if (substr($path, 0, 1) == '/') { $full_path = $CFG->datadir . $path; } elseif (substr($path, 0, 8) == 'program/') { $full_path = $CFG->progdir . substr($path, 7); } else { $full_path = $CFG->dir . '/' . $path; } // get rid of trailing slash if any if (substr($full_path, -1) == '/') { $full_path = substr($full_path, 0, -1); } if (($handle = @opendir($full_path)) === FALSE) { logger(sprintf("%s.%s(): cannot open directory '%s'", __CLASS__, __FUNCTION__, $path)); return FALSE; } // // 2 -- scan the directory for graphics files (skip the thumbnails) // $prefix_len = strlen(THUMBNAIL_PREFIX); $extensions = explode(',', str_replace(array('.', ' '), '', $CFG->filemanager_images)); $files = array(); while (($entryname = readdir($handle)) !== FALSE) { $full_entryname = $full_path . '/' . $entryname; if ($entryname == '.' || $entryname == '..' || $entryname == 'index.html' || is_link($full_entryname) || !is_file($full_entryname)) { continue; } // we are now fairly sure $entryname is a genuine file. check extension (if any) if (strpos($entryname, '.') === FALSE) { $ext = ''; } else { $components = explode('.', $entryname); $ext = utf8_strtolower(array_pop($components)); unset($components); } if (array_search($ext, $extensions) === FALSE) { // not an image file, next please continue; } if (substr($entryname, 0, $prefix_len) == THUMBNAIL_PREFIX) { // thumbnail, next please continue; } if (($image_info = @getimagesize($full_entryname)) === FALSE) { // not an image, next please continue; } $files[] = $entryname; } closedir($handle); // // 3 -- is there any image at all? // if (($n = sizeof($files)) <= 0) { return FALSE; } if ($n == 1) { $entryname = $files[0]; // Looks like Ford T: choice of 1 } else { // use a different image every $interval minutes $entryname = $files[($index + time() / (60 * $interval)) % $n]; } if (substr($path, 0, 1) == '/') { return was_file_url($path . '/' . $entryname, $fully_qualified); } else { return was_url($path . '/' . $entryname, $fully_qualified); } }
/** validate and check values that were submitted via a user dialog * * this steps through the definition of a dialog and retrieves the values submitted by * the user via $_POST[]. The values are checked against the constraints (e.g. minimum string length, * date range, etc.). If the submitted value is considered valid, it is stored in the corresponding * value of the dialogdef element, maybe properly reformatted (in case of dates/times/datetimes). * If there were errors, these are recorded in the dialog definition element, in the form of one or * more readable error messages. Also the error count (per element) is incremented. This makes it * easy to * - inform the user about what was wrong with the input data * - determine whether there was an error at all (if $dialogdef[$k]['errors'] > 0). * * Note that this routine has the side effect of filling the dialog array with the data that * was submitted by the user via $_POST. If the validation is successful, the data is ready * to be saved into the database. If it is not, the data entered is still available in the * dialogdef which makes it easy to return to the user and let the user correct the errors without * losing all the data input because of a silly mistake in some input field. * * Update 2009-03-17: We no longer validate the view-only fields because these fields are not POST'ed * by the browser and hence cannot be validated. This also means that there is no value set from $_POST * for those fields. * * Update 2011-09-29: added UTF-8 validation, replace with U+FFFD (Unicode replacement character) on fail * * @param array &$dialogdef the complete dialog definition; contains detailed errors and/or reformatted values * @return bool TRUE if all submitted values are considered valid, FALSE otherwise * @todo add an error message to */ function dialog_validate(&$dialogdef) { $total_errors = 0; foreach ($dialogdef as $k => $item) { if (isset($item['name'])) { if (isset($item['viewonly']) && $item['viewonly']) { continue; } $name = $item['name']; $fname = isset($item['label']) ? str_replace('~', '', $item['label']) : $name; $dialogdef[$k]['errors'] = 0; $dialogdef[$k]['error_messages'] = array(); $f_type = isset($item['type']) ? $item['type'] : ''; $value = isset($item['value']) ? $item['value'] : ''; if (isset($_POST[$name])) { if (utf8_validate($_POST[$name])) { $posted_value = magic_unquote($_POST[$name]); } else { $posted_value = "�"; // UTF-8 encoded substitution character U+FFFD ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_invalid', '', array('{FIELD}' => $fname)); } } else { $posted_value = ''; // should be NULL but empty string is more convenient here } switch ($f_type) { case F_DATE: case F_TIME: case F_DATETIME: case F_ALPHANUMERIC: case F_INTEGER: case F_REAL: case F_PASSWORD: case F_RICHTEXT: if ($f_type == F_DATE || $f_type == F_TIME || $f_type == F_DATETIME) { $datetime_value = ''; $is_valid_datetime = valid_datetime($f_type, $posted_value, $datetime_value); if (!$is_valid_datetime) { ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_invalid_datetime', '', array('{FIELD}' => $fname)); } // else we have a valid date/time, and a properly reformatted copy for comparisons too } if (isset($item['minlength'])) { $minlength = intval($item['minlength']); if (strlen($posted_value) < $minlength) { ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_too_short', '', array('{FIELD}' => $fname, '{MIN}' => strval($minlength))); } } if (isset($item['maxlength'])) { $maxlength = intval($item['maxlength']); if ($maxlength < strlen($posted_value)) { ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_too_long', '', array('{FIELD}' => $fname, '{MAX}' => strval($maxlength))); } } if (isset($item['minvalue'])) { switch ($f_type) { case F_INTEGER: if (intval($posted_value) < intval($item['minvalue'])) { ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_too_small', '', array('{FIELD}' => $fname, '{MIN}' => $item['minvalue'])); } break; case F_REAL: if (floatval($posted_value) < floatval($item['minvalue'])) { ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_too_small', '', array('{FIELD}' => $fname, '{MIN}' => $item['minvalue'])); } break; case F_DATE: case F_TIME: case F_DATETIME: if ($is_valid_datetime) { // there's no point in checking a value if the value itself is invalid if ($datetime_value < $item['minvalue']) { ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_too_small', '', array('{FIELD}' => $fname, '{MIN}' => $item['minvalue'])); } } break; } } if (isset($item['maxvalue'])) { switch ($f_type) { case F_INTEGER: if (intval($item['maxvalue']) < intval($posted_value)) { ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_too_large', '', array('{FIELD}' => $fname, '{MAX}' => $item['maxvalue'])); } break; case F_REAL: if (floatval($item['maxvalue']) < floatval($posted_value)) { ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_too_large', '', array('{FIELD}' => $fname, '{MAX}' => $item['maxvalue'])); } break; case F_DATE: case F_TIME: case F_DATETIME: if ($is_valid_datetime) { // there's no point in checking a value if the value itself is invalid if ($item['maxvalue'] < $datetime_value) { ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_too_large', '', array('{FIELD}' => $fname, '{MAX}' => $item['maxvalue'])); } } break; } } // finally format the data switch ($f_type) { case F_INTEGER: $dialogdef[$k]['value'] = strval(intval($posted_value)); break; case F_REAL: $decimals = isset($item['decimals']) ? abs(intval($item['decimals'])) : 2; $dialogdef[$k]['value'] = sprintf("%1." . $decimals . "f", floatval($posted_value)); break; case F_DATE: case F_TIME: case F_DATETIME: $dialogdef[$k]['value'] = $is_valid_datetime ? $datetime_value : $posted_value; break; default: $dialogdef[$k]['value'] = $posted_value; break; } break; case F_CHECKBOX: // there are two options: // either $posted_value equals the value in the options list // OR it does't. (well duh). However, it it doesn't match AND // it is not an empty string (see above, should be NULL), it // is an error nonetheless. OK. Here we go. if (!empty($posted_value)) { if (!isset($item['options'][$posted_value])) { // oops, something rottenin the state of Denmark... ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_invalid', '', array('{FIELD}' => $fname)); $dialogdef[$k]['value'] = ''; } else { $dialogdef[$k]['value'] = $posted_value; } } else { $dialogdef[$k]['value'] = ''; } break; case F_RADIO: case F_LISTBOX: // the value should exist in the options array if (!isset($item['options'][$posted_value])) { // oops, something rotten in the state of Denmark... ++$total_errors; ++$dialogdef[$k]['errors']; $dialogdef[$k]['error_messages'][] = t('validate_invalid', '', array('{FIELD}' => $fname)); $dialogdef[$k]['value'] = ''; } else { $dialogdef[$k]['value'] = $posted_value; } break; case F_FILE: // any value is OK, because // 1. checking is done separately in the filemanager (including virusscan etc.) // 2. if there is an error in 1 file, the other uploaded files could be perfectly // OK. Sending the user back to the upload dialog would be counter-productive // because the 'good' files would have to be uploaded again and she would have // to type in/browse the files again (and again and again...) break; case F_SUBMIT: // any value is OK, no check needed break; default: ++$total_errors; $dialogdef[$k]['errors'] = 1; $dialogdef[$k]['error_messages'] = $item['name'] . ' - INTERNAL ERROR: unknown type'; break; } } } return $total_errors == 0; }
/** display the content of the mailpage linked to node $node_id * * @param object &$theme collects the (html) output * @param int $area_id identifies the area where $node_id lives * @param int $node_id the node to which this module is connected * @param array $module the module record straight from the database * @return bool TRUE on success + output via $theme, FALSE otherwise */ function mailpage_view(&$theme, $area_id, $node_id, $module) { // // 0 -- basic sanity checks // if (($config = mailpage_view_get_config($node_id)) === FALSE) { $theme->add_message(t('error_retrieving_config', 'm_mailpage')); return FALSE; } elseif (sizeof($config['addresses']) <= 0) { logger(sprintf('%s(): no addresses at node %d: is mailpage unconfigured?', __FUNCTION__, $node_id)); $msg = t('error_retrieving_addresses', 'm_mailpage', array('{NODE}' => strval($node_id))); $theme->add_message($msg); $theme->add_content($msg); return FALSE; } // // 1 -- do we have a token already? // $t0 = $t1 = 0; $ip_addr = ''; $data = FALSE; $token_id = FALSE; if (isset($_POST['token'])) { // lookup valid UTF8 key (or fail with substitute U+FFFD instead) $token_key = utf8_validate($_POST['token']) ? magic_unquote($_POST['token']) : "�"; $token_id = token_lookup(MAILPAGE_REFERENCE, $token_key, $t0, $t1, $ip_addr, $data); } // // 2 -- handle cases of expired tokens and Cancel first // $now = time(); if ($token_id !== FALSE && isset($_POST['button_cancel'])) { // visitor pressed [Cancel] $theme->add_message(t('cancelled', 'admin')); token_destroy($token_id); $token_id = FALSE; } if ($token_id !== FALSE && $t1 < $now) { // token expired $theme->add_message(t('error_token_expired', 'm_mailpage')); token_destroy($token_id); $token_id = FALSE; } // // 3 -- handle the three remaining buttons from the two dialogs // if ($token_id !== FALSE) { if (isset($_POST['button_preview'])) { // // 3A -- Preview button // $dialogdef = mailpage_view_get_dialogdef($config, $token_key); if (!mailpage_view_dialog_validate($dialogdef)) { foreach ($dialogdef as $k => $item) { if (isset($item['errors']) && $item['errors'] > 0) { $theme->add_message($item['error_messages']); } } mailpage_show_form($theme, $config, $dialogdef); } else { if (!token_store($token_id, $dialogdef)) { $theme->add_message(t('error_storing_data', 'm_mailpage')); logger(sprintf('%s(): token store error in page %d: %s', __FUNCTION__, $node_id, db_errormessage())); return FALSE; } mailpage_show_preview($theme, $config, $dialogdef, $ip_addr); } } elseif (isset($_POST['button_edit'])) { // // 3B -- Edit button // if ($data === FALSE) { $theme->add_message(t('error_retrieving_data', 'm_mailpage')); logger(sprintf('%s(): no data after token_lookup()? (page=%d)', __FUNCTION__, $node_id)); $data = mailpage_view_get_dialogdef($config, $token_key); } mailpage_show_form($theme, $config, $data); } elseif (isset($_POST['button_send'])) { // // 3C -- Send button // if ($data === FALSE) { $theme->add_message(t('error_retrieving_data', 'm_mailpage')); logger(sprintf('%s(): no data after token_lookup()? (page=%d)', __FUNCTION__, $node_id)); $data = mailpage_view_get_dialogdef($config, $token_key); } if ($now < $t0) { // the window of opportunity is still closed; go back to form a la Edit $msg = t('error_too_fast', 'm_mailpage'); $theme->add_message($msg); $theme->add_popup_top($msg); mailpage_show_form($theme, $config, $data); logger(sprintf('%s(): reply too fast (%ds) from %s', __FUNCTION__, $t0 - $now, $ip_addr)); } elseif (!mailpage_send_message($config, $data, $ip_addr, $now - $t0)) { $theme->add_message(t('error_sending_message')); mailpage_show_form($theme, $config, $data); } else { token_destroy($token_id); mailpage_show_thankyou($theme, $config, $data, $ip_addr); } } else { // // 3D -- catch all: initiate a new round (shouldn't happen) // token_destroy($token_id); $token_id = FALSE; } } // // 4 -- Start with a clean slate // if ($token_id === FALSE) { $token_key = ''; if (($token_id = token_create(MAILPAGE_REFERENCE, $token_key, 20)) === FALSE) { // 20s delay $msg = t('error_creating_token', 'm_mailpage', array('{NODE}' => strval($node_id))); $theme->add_message($msg); $theme->add_content($msg); return FALSE; } $dialogdef = mailpage_view_get_dialogdef($config, $token_key); mailpage_show_form($theme, $config, $dialogdef); } return TRUE; }
/** return a valid (unquoted) UTF-8 string parameter typically from $_POST, or default value if none * * this retrieves a parameter string, typically from $_POST, and checks basic properties. * 1. the string should not contain more than $maximum_length characters * 2. the string should be valid UTF-8 * * If it is not a valid UTF-8 string or the string is too long, $error_value * (default: empty string) is returned. If no string by the name $name is * present in $gpc, the default value $default_value is returned. If there WAS * a valid UTF-8 string, it is automagically UNquoted in the process. * * Note that we check the stringlength in two steps. First we check the worst-case UTF-8 string * with 4-byte sequences for a length of 4 * $maximum_length. Then, after establishing that * the string is valid UTF-8 we make a more precise check of the length expressed in chars rather than bytes. * * @param array &$gpc a reference to the array to search (usually $_POST, $_GET or $_COOKIE) * @param string $name the name of the variable to retrieve * @param mixed $default_value the value to return if parameter is not in $gpc * @param mixed $error_value the value to return if parameter is not valid UTF-8 * @param mixed $maximum_length * @return mixed the value of the parameter or the default value if not specified */ function get_utf8_parameter_string(&$gpc, $name, $default_value = '', $error_value = '', $maximum_length = 255) { $value = $default_value; if (isset($gpc[$name])) { $value = $this->magic_unquote($gpc[$name]); if (strlen($value) > $maximum_length << 2 || !utf8_validate($value) || utf8_strlen($value) > $maximum_length) { // extra test to check length more precise $value = $error_value; } } return $value; }
/** return the reconstructed URL in a single (indented) line * * This constructs the exact URL (including the GET-parameters) * of the current script. This URL is returned as HTML so it * can be displyed. It is NOT meant to be a clickable link, but * as a documentation of the actual URL that was used. Note that * this URL can be suppressed by an appropriate 'display:none' * in the stylesheet, making it an item that only appears on * a hardcopy (media="print") and not on screen. * * If somehow the input is invalid UTF-8, we replace the offending * strings witht the unicode substitution character U+FFFD in UTF-8 * encode form (ie. the three character string 0xEF 0xBF 0xBD). * * @param string $m left margin for increased readability * @return string reconstructed URL as text */ function get_address($m = '') { global $CFG; $url = $CFG->www . '/index.php'; if ($CFG->friendly_url && isset($_SERVER['PATH_INFO'])) { $path_info = $_SERVER['PATH_INFO']; $url .= utf8_validate($path_info) ? htmlspecialchars($path_info) : "/�"; } if (!empty($_GET)) { $item_count = 0; foreach ($_GET as $k => $v) { $url .= sprintf('%s%s=%s', $item_count++ == 0 ? '?' : '&', utf8_validate($k) ? htmlspecialchars($k) : "�", utf8_validate($v) ? htmlspecialchars($v) : "�"); } } return $m . 'URL:' . $url . "\n"; }
/** retrieve a named parameter from the friendly URL * * This routine attempts to parse the PATH_INFO server variable * and extract the parameters and values stored in the path components. * (see also {@link was_node_url()}). * * Example: the URL * <code> * /was/index.php/35/photo/5/Picture_of_our_field_trip.html * </code> * is broken down as follows: * - /35 is the first non-empty parameter and also is completely numeric and hence * interpreted as a node_id; * - /photo/5 is considered a key-value-pair with key=photo and value=5; * - /Picture_of_our_field_trip.html is the last component and is discarded * The static array which caches the results of the parsing will contain this: * $parameters = array('node' => 35, 'photo' => 5); * * Note that all parameters are checked for valid UTF-8. * If either key or value is NOT UTF-8, the pair is silently discarded. * This prevents tricks with overlong sequences and other UTF-8 black magic. * * Once the parsed friendly path is cached the parameter $name is looked up. * If found, the corresponding value is returned. If it is not found, $default_value * is returned. * * The cache is rebuilt if $force is TRUE (should never be necessary) * * Note: the parameter 'node' is a special case: if it is specified it is the first * parameter. This parameter otherwise is unnamed. * * @param string $name the parameter we need to look for * @param mixed $default_value is returned if the parameter was not found * @param bool $force if TRUE forces the parsing to be redone * @return mixed either the $default_value or the value of the named parameter * */ function get_friendly_parameter($name, $default_value = NULL, $force = FALSE) { global $CFG; static $parameters = NULL; if (is_null($parameters) || $force) { $parameters = array(); if ($CFG->friendly_url && isset($_SERVER['PATH_INFO'])) { $raw_params = explode('/', $_SERVER['PATH_INFO']); $n = sizeof($raw_params); $index = 0; while ($index < $n && empty($raw_params[$index])) { ++$index; } if ($index < $n && utf8_validate($raw_params[$index]) && is_numeric($raw_params[$index])) { $parameters['node'] = intval($raw_params[$index]); ++$index; } while ($index < $n - 1) { $key = utf8_validate($raw_params[$index]) ? strval($raw_params[$index]) : ''; ++$index; $val = utf8_validate($raw_params[$index]) ? strval($raw_params[$index]) : ''; ++$index; if ($key != '') { $parameters[$key] = $val; } } // at this point there may be one last component ($raw_params[$n-1]); we'll ignore that unused parameter } } return isset($parameters[$name]) ? $parameters[$name] : $default_value; }
/** retrieve configuration data for this set of snapshots * * this routine fetches the configuration from the snapshots table and stores * the sanitised information from the various fields in the object variables. * * @param int $node_id this key identifies the snapshots series * @return void and information stored in object variables * @todo check for information leaks (private path) here? */ function get_snapshots_configuration($node_id) { // // 1 -- retrieve the relevant data for this series of snapshots from database // $table = 'snapshots'; $fields = array('header', 'introduction', 'snapshots_path', 'variant', 'dimension'); $where = array('node_id' => intval($node_id)); $record = db_select_single_record($table, $fields, $where); if ($record === FALSE) { logger(sprintf('%s.%s(): error retrieving configuration: %s', __CLASS__, __FUNCTION__, db_errormessage())); $record = array('header' => '', 'introduction' => '', 'snapshots_path' => '', 'variant' => 1, 'dimension' => 512); } $this->header = trim($record['header']); $this->introduction = trim($record['introduction']); $this->variant = intval($record['variant']); $this->dimension = intval($record['dimension']); // // 2 -- sanity checks (just in case); massage pathname // $path = trim($record['snapshots_path']); if (!utf8_validate($path) || strpos('/' . $path . '/', '/../') !== FALSE) { logger(sprintf("%s.%s(): invalid path '%s'; using root path", __CLASS__, __FUNCTION__, $path)); $path = '/'; // shouldn't happen } if (substr($path, 0, 1) != '/') { $path = '/' . $path; } if (substr($path, -1) == '/') { $path = substr($path, 0, -1); } $this->snapshots_path = $path; // FIXME: check permissions here to prevent leaking a private area path to anonymous visitors? return; }