/** connect this module to a node * * this makes the link between the node $node_id in area $area_id and this module. * In this case we simply link a data container to node $node_id in a 1-to-1 relation. * Note that we might decide lateron to keep versions of pages around, e.g. by inserting * new records every save rather than updating the existing. * * @param object &$output collects the html output (if any) * @param int $area_id the area in which $node_id resides * @param int $node_id the node to which we need to connect * @param array $module the module record straight from the database * @return bool TRUE on success, FALSE otherwise */ function htmlpage_connect(&$output, $area_id, $node_id, $module) { global $USER; $now = strftime('%Y-%m-%d %T'); $fields = array('node_id' => intval($node_id), 'version' => 1, 'page_data' => '', 'ctime' => $now, 'cuser_id' => $USER->user_id, 'mtime' => $now, 'muser_id' => $USER->user_id); $retval = db_insert_into('htmlpages', $fields); return $retval == 1 ? TRUE : FALSE; }
/** install the theme * * this routine performs the necessary actions to make this theme usable. * More specific, this routine adds a handful of default values into the * themes_properties table. Once a theme is actually used in an area, these * defaults are copied from the themes_properties table to the * themes_areas_properties table for the selected area. The user can subsequently * edit these properties in the Area Manager. * * @param array &$messages collects the (error) messages * @param int $theme_id the key for this theme in the themes table * @return bool TRUE on success + output via $messages, FALSE otherwise */ function sophia_install(&$messages, $theme_id) { $now = strftime('%Y-%m-%d %T'); $properties = array(array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'quicktop_section_id', 'type' => 'i', 'value' => '0', 'sort_order' => 10, 'extra' => 'minvalue=0', 'description' => 'indicates which section to use for the quicklinks at the top of the page')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'quickbottom_section_id', 'type' => 'i', 'value' => '0', 'sort_order' => 20, 'extra' => 'minvalue=0', 'description' => 'indicates which section to use for the quicklinks at the bottom of the page')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'logo_image', 'type' => 's', 'value' => 'program/graphics/waslogo-284x71.png', 'sort_order' => 30, 'extra' => 'maxlength=255', 'description' => 'the URL of the logo file or a path relative to the directory holding index.php')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'logo_width', 'type' => 'i', 'value' => '284', 'sort_order' => 40, 'extra' => 'minvalue=0;maxvalue=2048', 'description' => 'the width of the logo in pixels')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'logo_height', 'type' => 'i', 'value' => '71', 'sort_order' => 50, 'extra' => 'minvalue=0;maxvalue=1536', 'description' => 'the height of the logo in pixels')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'show_breadcrumb_trail', 'type' => 'b', 'value' => '1', 'sort_order' => 60, 'description' => 'this enables/disables the display of the breadcrumb trail in the navigation')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'style_usage_static', 'type' => 'b', 'value' => '1', 'sort_order' => 70, 'extra' => '', 'description' => 'if TRUE this includes the static stylesheet')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'stylesheet', 'type' => 's', 'value' => 'program/themes/sophia/style.css', 'sort_order' => 80, 'extra' => 'maxlength=255', 'description' => 'the URL of the stylesheet or a path relative to the directory holding index.php')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'style_usage_area', 'type' => 'b', 'value' => '1', 'sort_order' => 90, 'extra' => '', 'description' => 'if TRUE this includes the additional style information')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'style', 'type' => 's', 'value' => "", 'sort_order' => 100, 'extra' => 'maxlength=65535;rows=10;columns=70', 'description' => 'additional style information that will be processed AFTER the static stylesheet file')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'style_usage_node', 'type' => 'b', 'value' => '1', 'sort_order' => 110, 'extra' => '', 'description' => 'if TRUE this allows for addition style information from individual sections/pages')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'header_text', 'type' => 's', 'value' => '', 'sort_order' => 120, 'extra' => 'maxlength=255', 'description' => 'additional plain text added to the page header')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'left_top_html', 'type' => 's', 'value' => "", 'sort_order' => 130, 'extra' => 'maxlength=65535;rows=10;columns=70', 'description' => 'additional free form html above the menu')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'left_bottom_html', 'type' => 's', 'value' => "", 'sort_order' => 140, 'extra' => 'maxlength=65535;rows=10;columns=70', 'description' => 'additional free form html below the menu')), array('table' => 'themes_properties', 'fields' => array('theme_id' => $theme_id, 'name' => 'footer_text', 'type' => 's', 'value' => '', 'sort_order' => 150, 'extra' => 'maxlength=255', 'description' => 'additional plain text added to the page footer'))); $retval = TRUE; // assume success foreach ($properties as $property) { if (db_insert_into($property['table'], $property['fields']) === FALSE) { $messages[] = __FUNCTION__ . '(): ' . db_errormessage(); $retval = FALSE; } } return $retval; }
/** connect this module to a node * * this makes the link between the node $node_id in area $area_id and this module. * In this case we simply link a single record to node $node_id in a * 1-to-1 relation. * * Note that we set the parameters to more or less reasonable values. * It is up to the user to configure the aggregator with appropriate settings. * * @param object &$output collects the html output (if any) * @param int $area_id the area in which $node_id resides * @param int $node_id the node to which we need to connect * @param array $module the module record straight from the database * @return bool TRUE on success, FALSE otherwise */ function aggregator_connect(&$output, $area_id, $node_id, $module) { global $USER; $now = strftime('%Y-%m-%d %T'); $fields = array('node_id' => intval($node_id), 'header' => '', 'introduction' => '', 'node_list' => '', 'items' => 10, 'reverse_order' => FALSE, 'htmlpage_length' => 2, 'snapshots_width' => 512, 'snapshots_height' => 120, 'snapshots_visible' => 3, 'snapshots_showtime' => 5, 'ctime' => $now, 'cuser_id' => $USER->user_id, 'mtime' => $now, 'muser_id' => $USER->user_id); $retval = db_insert_into('aggregator', $fields); if ($retval !== 1) { logger(sprintf('%s(): cannot connect aggregator to node \'%d\': %s', __FUNCTION__, $node_id, db_errormessage())); $retval = FALSE; } else { $retval = TRUE; } return $retval; }
/** connect this module to a node * * this makes the link between the node $node_id in area $area_id and this module. * In this case we simply link a single 'scope' parameter to node $node_id in a * 1-to-1 relation. * * Note that we set the parameter 'scope' to 0. This implies a 'small' map. * It is up to the user to configure the node to use medium (scope=1) or large (scope=2) map. * * @param object &$output collects the html output (if any) * @param int $area_id the area in which $node_id resides * @param int $node_id the node to which we need to connect * @param array $module the module record straight from the database * @return bool TRUE on success, FALSE otherwise */ function sitemap_connect(&$output, $area_id, $node_id, $module) { global $USER; $now = strftime('%Y-%m-%d %T'); $fields = array('node_id' => intval($node_id), 'header' => '', 'introduction' => '', 'scope' => 0, 'ctime' => $now, 'cuser_id' => $USER->user_id, 'mtime' => $now, 'muser_id' => $USER->user_id); $retval = db_insert_into('sitemaps', $fields); if ($retval !== 1) { logger(sprintf('%s(): cannot connect sitemap to node \'%d\': %s', __FUNCTION__, $node_id, db_errormessage())); $retval = FALSE; } else { $retval = TRUE; } return $retval; }
/** install the module * * this routine installs the module. For this module there * are a few properties that need to be stored in the main * modules_properties table. The specific table for this module is * already created based on the tabledefs); see install/crew_tabledefs.php. * * Note that the record for this module is already created in the * modules table; the pkey is $module_id. * * @param array &$messages collects the (error) messages * @param int $module_id the key for this module in the modules table * @return bool TRUE on success + output via $messages, FALSE otherwise */ function crew_install(&$messages, $module_id) { $properties = array('origin' => array('type' => 's', 'value' => isset($_SERVER['HTTP_HOST']) ? 'http://' . $_SERVER['HTTP_HOST'] : '', 'extra' => 'maxlength=255', 'description' => 'this must match the origin as seen by the browser of the CREW-user'), 'location' => array('type' => 's', 'value' => '', 'extra' => 'maxlength=255', 'description' => 'this is the location (URL) of the websocket server'), 'secret' => array('type' => 's', 'value' => '', 'extra' => 'maxlength=255', 'description' => 'this shared secret is necessary to access the websocket server')); $retval = TRUE; // assume success $table = 'modules_properties'; $sort_order = 0; foreach ($properties as $name => $property) { $property['module_id'] = $module_id; $property['name'] = $name; $sort_order += 10; $property['sort_order'] = $sort_order; if (db_insert_into($table, $property) === FALSE) { $messages[] = __FUNCTION__ . '(): ' . db_errormessage(); $retval = FALSE; } } return $retval; }
/** connect this module to a node * * this makes the link between the node $node_id in area $area_id and this module. * In this case we simply link a single 'variant' parameter to node $node_id in a * 1-to-1 relation. * * Note that we set the parameter 'variant' to 1. This equates to the variant * where the visitor starts with the title, the optional introductory text and the * thumbnail overview. It is up to the user to configure the node to use other * variants, eg. start at the first picture full-size or display a slide show. * Also note that we start off with an (arbitrary) dimension for the full-size * snapshots. This is a per-node setting (as opposed to the systemwide setting * for thumbnail dimensions). * Finally, we do a little heuristics here by plugging in the current directory * from the filemanager. This is dirty, but we might assume that the user * uploaded the files to a directory just before adding this snapshots node. * In that case there is no need to change anything re: path. * If the user did NOT upload files, we plug in the name of the directory * which is associated with area $area_id. * * @param object &$output collects the html output (if any) * @param int $area_id the area in which $node_id resides * @param int $node_id the node to which we need to connect * @param array $module the module record straight from the database * @return bool TRUE on success, FALSE otherwise */ function snapshots_connect(&$output, $area_id, $node_id, $module) { global $USER; $now = strftime('%Y-%m-%d %T'); $areas = get_area_records(); $path = isset($_SESSION['current_directory']) ? $_SESSION['current_directory'] : (isset($areas[$area_id]) ? '/areas/' . $areas[$area_id]['path'] : ''); unset($areas); $fields = array('node_id' => intval($node_id), 'header' => '', 'introduction' => '', 'snapshots_path' => $path, 'variant' => 1, 'dimension' => 512, 'ctime' => $now, 'cuser_id' => $USER->user_id, 'mtime' => $now, 'muser_id' => $USER->user_id); $retval = db_insert_into('snapshots', $fields); if ($retval !== 1) { logger(sprintf('%s(): cannot connect snapshots to node \'%d\': %s', __FUNCTION__, $node_id, db_errormessage())); $retval = FALSE; } else { $retval = TRUE; } return $retval; }
/** save the changed roles in the dialog to the corresponding tables 'acls' * * this interprets the data from the current dialog and saves the changed roles * accordingly. Note that the information about tables and fields etc. is all * contained in the dialogdef so we can use this generic save_data() routine. * * @return bool FALSE on error, TRUE otherwise */ function save_data_permissions() { if (is_null($this->dialogdef)) { logger("save_data_permissions(): huh? dialogdef not set? cannot cope with that", WLOG_DEBUG); return FALSE; } if (!dialog_validate($this->dialogdef)) { // there were errors, show them to the user return error foreach ($this->dialogdef as $k => $item) { if (isset($item['errors']) && $item['errors'] > 0) { $this->output->add_message($item['error_messages']); } } return FALSE; } // At this point we have valid values in $this->dialogdef // Also, we have the old values in our hands so we can sense the difference $errors = 0; foreach ($this->dialogdef as $k => $item) { if (!isset($item['name']) || !isset($item['table_name']) || $item['type'] == F_SUBMIT || isset($item['hidden']) && $item['hidden'] || intval($item['old_value']) == intval($item['value'])) { // skip unchanged fields continue; } // At this point we DO have an associated data table // and we DO have a changed value. Now we need to // save the changes in the database. We first try to // update an existing record. $table = $item['table_name']; $field = $item['table_field']; $where = $item['table_where']; $value = intval($item['value']); $failed = FALSE; $rows = db_update($table, array($field => $value), $where); if ($rows === FALSE) { // oops, failed $failed = TRUE; } elseif ($rows == 0) { // apparently there was no record yet. Go add one. if ($table == 'acls') { // this should not happen logger(sprintf("aclmanager: weird! no record acl_id='%d' in 'acls'?", $where['acl_id'])); $failed = TRUE; } else { $fields = $where; $fields[$field] = $value; if (db_insert_into($table, $fields) === FALSE) { logger(sprintf("aclmanager: cannot insert record in %s: %s", $table, db_errormessage())); $failed = TRUE; } } } if ($failed) { $message = t('acl_error_saving_field', 'admin', array('{FIELD}' => isset($item['label']) ? str_replace('~', '', $item['label']) : $item['name'])); ++$errors; ++$this->dialogdef[$k]['errors']; $this->dialogdef[$k]['error_messages'][] = $message; $this->output->add_message($message); } } if ($errors == 0) { $this->output->add_message(t('success_saving_data', 'admin')); $retval = TRUE; } else { $this->output->add_message(t('errors_saving_data', 'admin', array('{ERRORS}' => $errors))); $retval = FALSE; } return $retval; }
/** fill tables in database via include()'ing a file with tabledata * * @param string $filename contains the table definitions * @return bool TRUE on success, FALSE otherwise + messages written to $this->messages * @uses $DB */ function insert_tabledata($filename) { global $DB; $retval = TRUE; // assume success $tabledata = array(); if (!file_exists($filename)) { $this->messages[] = $this->t('error_file_not_found', array('{FILENAME}' => $filename)); return FALSE; } else { include $filename; } foreach ($tabledata as $data) { if (db_insert_into($data['table'], $data['fields']) === FALSE) { $params = array('{TABLENAME}' => $DB->prefix . $data['table'], '{ERRNO}' => $DB->errno, '{ERROR}' => $DB->error); $this->messages[] = $this->t('error_insert_into_table', $params); $retval = FALSE; } } return $retval; }
/** create a new session in the session table, return the unique sessionkey * * this creates generates a new unique session key and stores it in a new record * in the sessions table. Additional information is recorded in the new record too: * the user_id and auxiliary information. This information makes that a session * can always be linked to a particular user (which is handy when dealing with * locked pages, etc.). This routine attempts to create a unique session key * a number of times. If it doesn't work out, the routine returns FALSE. * * the optional parameter $user_information can be used to store additional * information about this user, e.g. the IP-address. This is useful for generating * messages like 'Node xxx is currently locked by user YYYY logged in from ZZZZ'. * * Note that the generation of a unique session key is salted with both the main * url of this website and the special salt that was recorded once during installation * time. Also, pseudo-random data is added via rand(). Hopefully this will be * hard to guess, even though we use md5() to condense this (semi-)random information * into only 128 bits. * * @param int link to the users table, identifies the user that started the session * @param string (optional) auxiliary information about the user, e.g. the IP-address * @return bool|string FALSE on error, the unique session key ('token') on success * @uses $CFG * @uses $DB * @todo should we also record the IP-address of the user in the session record? * In a way this is a case of information leak, even though it is only between * authenticated users. Mmmm... */ function dbsession_create($user_id, $user_information = '') { global $CFG, $DB; $salt = $CFG->www; if (isset($CFG->salt)) { $salt .= $CFG->salt; } $current_time = strftime('%Y-%m-%d %T'); // current date/time as string yyyy-mm-dd hh:mm:ss for ($tries = 5; $tries > 0; --$tries) { $new_sessionkey = md5($salt . uniqid('', TRUE) . rand()); $retval = db_insert_into('sessions', array('session_key' => $new_sessionkey, 'session_data' => '', 'user_id' => $user_id, 'user_information' => $user_information, 'ctime' => $current_time, 'atime' => $current_time)); if ($retval !== FALSE) { return $new_sessionkey; } } return FALSE; }
/** save the new group/capacity for the selected user * * this adds a record to the users_groups_capacities table, indicating * the group membership and the corresponding capacity for the user. * * @return void output written to browser via $this->output * @uses $WAS_SCRIPT_NAME */ function user_groupsave() { global $WAS_SCRIPT_NAME; // // 0 -- sanity check // $user_id = get_parameter_int('user', NULL); if (is_null($user_id)) { logger("usermanager->user_groupsave(): unspecified parameter user"); $this->output->add_message(t('error_invalid_parameters', 'admin')); $this->users_overview(); return; } // // 1 -- bail out if the user pressed cancel button // if (isset($_POST['button_cancel'])) { $this->output->add_message(t('cancelled', 'admin')); $this->user_groups(); return; } // // 2 -- make sure the data is valid // $dialogdef = $this->get_dialogdef_add_usergroup($user_id); if (!dialog_validate($dialogdef)) { foreach ($dialogdef as $k => $item) { if (isset($item['errors']) && $item['errors'] > 0) { $this->output->add_message($item['error_messages']); } } $params = $this->get_user_names($user_id); $this->output->add_content('<h2>' . t('usermanager_user_groupadd_header', 'admin', $params) . '</h2>'); $this->output->add_content(t('usermanager_user_groupadd_explanation', 'admin', $params)); $href = href($WAS_SCRIPT_NAME, $this->a_params(TASK_USER_GROUPSAVE, $user_id)); $this->output->add_content(dialog_quickform($href, $dialogdef)); $this->show_menu_user($user_id, TASK_USER_GROUPS); $parameters = $this->a_params(TASK_USER_GROUPADD, $user_id); $anchor = t('usermanager_user_groups_add', 'admin'); $attributes = array('title' => t('usermanager_user_groups_add_title', 'admin')); $this->output->add_breadcrumb($WAS_SCRIPT_NAME, $parameters, $attributes, $anchor); return; } // // 3 -- save the selected group/capacity to this user account // $key = $dialogdef['user_group_capacity']['value']; list($group_id, $capacity_code) = explode(':', $key); $group_id = intval($group_id); $capacity_code = intval($capacity_code); if ($group_id == 0 || $capacity_code == 0) { // the key '0:0' is used to indicate 'no more groups'; pretend that the user cancelled add group membership $this->output->add_message(t('cancelled', 'admin')); $this->user_groups(); return; } $errors = 0; // assume all goes well, for now... $fields = array('user_id' => $user_id, 'group_id' => $group_id, 'capacity_code' => $capacity_code); $table = 'users_groups_capacities'; if (db_insert_into($table, $fields) === FALSE) { // Mmmm, weird. Perhaps there was already a record for $user_id,$group_id with another $capacity_code? logger("usermanager: weird: add membership for user '{$user_id}' and group '{$group_id}' failed: " . db_errormessage()); ++$errors; $where = array('user_id' => $user_id, 'group_id' => $group_id); if (db_delete($table, $where) === FALSE) { logger("usermanager: add membership double-fault, giving up: " . db_errormessage()); ++$errors; } elseif (db_insert_into($table, $fields) === FALSE) { logger("usermanager: add membership failed again, giving up: " . db_errormessage()); ++$errors; } else { logger("usermanager: add membership for user '{$user_id}' and group '{$group_id}' succeeded, finally"); $errors = 0; // OK. Forget the initial error. } } if ($errors == 0) { $this->output->add_message(t('success_saving_data', 'admin')); } else { $this->output->add_message(t('errors_saving_data', 'admin', array('{ERRORS}' => $errors))); } $this->user_groups(); }
/** create a few alerts * * * @param array &$messages used to return (error) messages to caller * @param array &$config pertinent information about the site * @param array &$tr translations of demodata texts * @return bool TRUE on success + data entered into database, FALSE on error */ function demodata_alerts(&$messages, &$config, &$tr) { $retval = TRUE; $now = strftime("%Y-%m-%d %T"); $email = $config['user_email']; $alerts = array('webmaster' => array('full_name' => $config['user_full_name'], 'email' => $email, 'cron_interval' => 1440, 'cron_next' => $now, 'messages' => 1, 'message_buffer' => $now . "\n" . $tr['alerts_initial_load'] . "\n" . $tr['alerts_every_1440_minutes'] . "\n" . $tr['alerts_all_areas'] . "\n" . $tr['alerts_email_address'] . "\n" . $email . " (" . $config['user_full_name'] . ")\n", 'is_active' => TRUE), 'acackl' => array('full_name' => 'Amelia Cackle', 'email' => $email, 'cron_interval' => 60, 'cron_next' => $now, 'messages' => 1, 'message_buffer' => $now . "\n" . $tr['alerts_initial_load'] . "\n" . $tr['alerts_every_60_minutes'] . "\n" . $tr['alerts_private_area'] . "\n" . $tr['alerts_email_address'] . "\n" . $email . " (Amelia Cackle)\n", 'is_active' => TRUE)); foreach ($alerts as $alert => $fields) { if (($alert_id = db_insert_into_and_get_id('alerts', $fields, 'alert_id')) === FALSE) { $messages[] = $tr['error'] . ' ' . db_errormessage(); $retval = FALSE; } $alerts[$alert]['alert_id'] = intval($alert_id); } $alerts_areas_nodes = array('webmaster' => array('alert_id' => $alerts['webmaster']['alert_id'], 'area_id' => 0, 'node_id' => 0, 'flag' => TRUE), 'acackl' => array('alert_id' => $alerts['acackl']['alert_id'], 'area_id' => $config['demo_areas']['private']['area_id'], 'node_id' => 0, 'flag' => TRUE)); foreach ($alerts_areas_nodes as $fields) { if (db_insert_into('alerts_areas_nodes', $fields) === FALSE) { $messages[] = $tr['error'] . ' ' . db_errormessage(); $retval = FALSE; } } return $retval; }
/** add a group/capacity and corresponding acl to the database * * @param int $group_id group of interest * @param int $capacity_code the capacity * @param int $sort_order * @return bool TRUE on success, FALSE otherwise */ function add_group_capacity($group_id, $capacity_code, $sort_order) { static $fields_acl = array('permissions_intranet' => ACL_ROLE_NONE, 'permissions_modules' => ACL_ROLE_NONE, 'permissions_jobs' => ACL_ROLE_NONE, 'permissions_nodes' => ACL_ROLE_NONE); // // 1 -- create an acl (with no permissions whatsoever) and remember the new acl_id // $new_acl_id = db_insert_into_and_get_id('acls', $fields_acl, 'acl_id'); if ($new_acl_id === FALSE) { logger(sprintf("%s.%s(): adding new acl for group/capacity '%d/%d' failed: %s", __CLASS__, __FUNCTION__, $group_id, $capacity_code, db_errormessage())); return FALSE; } // // 2 -- subsequently add a new group-capacity record pointing to this new acl // $fields = array('group_id' => intval($group_id), 'capacity_code' => intval($capacity_code), 'sort_order' => intval($sort_order), 'acl_id' => $new_acl_id); if (db_insert_into('groups_capacities', $fields) === FALSE) { logger(sprintf("%s.%s(): adding new record for group/capacity '%d/%d' failed: %s", __CLASS__, __FUNCTION__, $group_id, $capacity_code, db_errormessage())); $retval = db_delete('acls', array('acl_id' => $new_acl_id)); logger(sprintf("%s.%s(): removing freshly created acl '%d': %s", __CLASS__, __FUNCTION__, $new_acl_id, $retval !== FALSE ? 'success' : 'failure: ' . db_errormessage())); return FALSE; } logger(sprintf("%s.%s(): success adding group/capacity '%d/%d' with acl_id='%d'", __CLASS__, __FUNCTION__, $group_id, $capacity_code, $new_acl_id), WLOG_DEBUG); return TRUE; }
/** add demonstration data to the system * * this routine adds to the existing set of demonstration data as specified * in $config. Here we add a few nodes as follows: * * 'snapshots0': a section containing one page connected to the snapshots_module * 'snapshots1': a page in section 'snapshots0' acting as an example set of snapshots * * The section 'snapshots0' is added at the bottom of the demo-section 'news' * Maybe not the best place, but at least we don't add yet another top level * menu item. * * Note * If the module is installed via the Install Wizard, this routine is * called. However, if a module is installed as an additional module * after installation, the {$module}_demodata() routine is never called. * This is because the only time you know that demodata is installed is * when the Install Wizard runs. If we're called from admin.php, the * webmaster may have already deleted existing (core) demodata so you * never can be sure what to expect. To put it another way: it is hard * to re-construct $config when we're NOT the Instal Wizard. * * The array $config contains the following information. * * <code> * $config['language_key'] => install language code (eg. 'en') * $config['dir'] => path to CMS Root Directory (eg. /home/httpd/htdocs) * $config['www'] => URL of CMS Root Directory (eg. http://exemplum.eu) * $config['progdir'] => path to program directory (eg. /home/httpd/htdocs/program) * $config['progwww'] => URL of program directory (eg. http://exemplum.eu/program) * $config['datadir'] => path to data directory (eg. /home/httpd/wasdata/a1b2c3d4e5f6) * $config['user_username'] => userid of webmaster (eg. wblader) * $config['user_full_name'] => full name of webmaster (eg. Wilhelmina Bladergroen) * $config['user_email'] => email of webmaster (eg. w.bladergroen@exemplum.eu) * $config['user_id'] => numerical user_id (usually 1) * $config['demo_salt'] => password salt for all demodata accounts * $config['demo_password'] => password for all demodata accounts * $config['demo_areas'] => array with demo area data * $config['demo_groups'] => array with demo group data * $config['demo_users'] => array with demo user data * $config['demo_nodes'] => array with demo node data * $config['demo_string'] => array with demo strings from /program/install/languages/LL/demodata.php * $config['demo_replace'] => array with search/replace pairs to 'jazz up' the demo strings * </code> * * With this information, we can add a demonstration configuration for the public area, * which shows off the possibilities. Note that we add our own additions to the array * $config so other modules and themes can determine the correct status quo w.r.t. the * demodata nodes etc. * * @param array &$messages collects the (error) messages * @param int $module_id the key for this module in the modules table * @param array $configuration pertinent data for the new website + demodata foundation * @param array $manifest a copy of the manifest for this module * @return bool TRUE on success + output via $messages, FALSE otherwise */ function snapshots_demodata(&$messages, $module_id, $config, $manifest) { global $DB; $retval = TRUE; // assume success // 0 -- get hold of the module-specific translations in $string[] $string = array(); $language_key = $config['language_key']; $filename = dirname(__FILE__) . '/languages/' . $language_key . '/snapshots.php'; if (!file_exists($filename)) { $filename = dirname(__FILE__) . '/languages/en/snapshots.php'; } @(include $filename); if (empty($string)) { $messages[] = 'Internal error: no translations in ' . $filename; return FALSE; } // 1A -- prepare for addition of a few nodes $sort_order = 0; $parent_id = $config['demo_nodes']['news']['node_id']; foreach ($config['demo_nodes'] as $node => $fields) { if ($fields['parent_id'] == $parent_id && $fields['node_id'] != $parent_id) { $sort_order = max($sort_order, $fields['sort_order']); } } $sort_order += 10; // place the snapshots-section at the end of the parent section $nodes = array('snapshots0' => array('parent_id' => $parent_id, 'is_page' => FALSE, 'title' => strtr($string['snapshots0_title'], $config['demo_replace']), 'link_text' => strtr($string['snapshots0_link_text'], $config['demo_replace']), 'sort_order' => $sort_order), 'snapshots1' => array('parent_id' => 0, 'is_page' => TRUE, 'is_default' => TRUE, 'title' => strtr($string['snapshots1_title'], $config['demo_replace']), 'link_text' => strtr($string['snapshots1_link_text'], $config['demo_replace']), 'sort_order' => 10, 'module_id' => $module_id, 'style' => "div.thumbnail_image a:hover img { border: 5px solid #00FF00; }\n")); // 1B -- actually add the necessary nodes $now = strftime('%Y-%m-%d %T'); $user_id = $config['user_id']; $area_id = $config['demo_areas']['public']['area_id']; foreach ($nodes as $node => $fields) { $fields['area_id'] = $area_id; $fields['ctime'] = $now; $fields['mtime'] = $now; $fields['atime'] = $now; $fields['owner_id'] = $user_id; if ($fields['parent_id'] == 0) { $fields['parent_id'] = $config['demo_nodes']['snapshots0']['node_id']; // plug in real node_id of 'our' section } if (($node_id = db_insert_into_and_get_id('nodes', $fields, 'node_id')) === FALSE) { $messages[] = $config['demo_string']['error'] . db_errormessage(); $retval = FALSE; } $node_id = intval($node_id); $fields['node_id'] = $node_id; $config['demo_nodes'][$node] = $fields; } // 2 -- Simulate a series of snapshots in the exemplum area data storage // 2A -- copy from module demodata directory to exemplum path $pictures = array("allium.jpg", "calendula.jpg", "cynara.jpg", "lagos.jpg", "lavandula.jpg", "mentha.jpg", "nepeta.jpg", "ocimum.jpg", "origanum.jpg", "petroselinum.jpg", "salvia.jpg", "thymus.jpg"); $thumb_prefix = 'zz_thumb_'; $path_snapshots = '/areas/' . $config['demo_areas']['public']['path'] . '/snapshots'; // relative to $datadir $fullpath_source = dirname(__FILE__) . '/install/demodata'; $fullpath_target = $config['datadir'] . $path_snapshots; if (@mkdir($fullpath_target, 0700) === FALSE) { $messages[] = $config['demo_string']['error'] . "mkdir('{$fullpath_target}')"; $retval = FALSE; } else { @touch($fullpath_target . '/index.html'); // try to "protect" directory foreach ($pictures as $picture) { $filenames = array($picture, $thumb_prefix . $picture); foreach ($filenames as $filename) { if (!@copy($fullpath_source . '/' . $filename, $fullpath_target . '/' . $filename)) { $messages[] = $config['demo_string']['error'] . "copy('{$path_snapshots}/{$filename}')"; $retval = FALSE; } } } } $fields = array('node_id' => $config['demo_nodes']['snapshots1']['node_id'], 'header' => strtr($string['snapshots1_header'], $config['demo_replace']), 'introduction' => strtr($string['snapshots1_introduction'], $config['demo_replace']), 'snapshots_path' => $path_snapshots, 'variant' => 1, 'dimension' => 512, 'ctime' => $now, 'cuser_id' => $user_id, 'mtime' => $now, 'muser_id' => $user_id); if (db_insert_into('snapshots', $fields) === FALSE) { $messages[] = $config['demo_string']['error'] . db_errormessage(); $retval = FALSE; } return $retval; }
/** a simple function to log information to the database 'for future reference' * * This adds a message to the table log_messages, including a time, the remote address * and (of course) a message. See also the standard PHP-function syslog(). We use the * existing symbolic constants for priority. Default value is WLOG_INFO. * * Note that messages with a priority WLOG_DEBUG are only written to the log * if the global parameter $CFG->debug is TRUE. All other messages are simply * logged, no further questions asked. * * If the caller does not provide a user_id, this routine attempts to * read the user_id from the global $_SESSION array, i.e. we try to link * events to a particular user if possible. * * Note that with a field definition of varchar(150) there is room to store either * an IPv4 address (max 15 bytes) or a full-blown IPv6 address (39-47 bytes, see RFC3989) or * even twice a complete reverse DNS address (see {@link update_core_2011092100()}). * * See also {@link task_logview()} for a rant on the difference between LOG_DEBUG and LOG_INFO. * * @param string $message the message to write to the log * @param int $priority loglevel, see PHP-function syslog() for a list of predefined constants * @return bool FALSE on error, TRUE on success * @uses $CFG * @todo should we make this configurable and maybe log directly to syslog * (with automatic logrotate) or do we want to keep this 'self-contained' * (the webmaster can read the table, but not the machine's syslog)? */ function logger($message, $priority = WLOG_INFO, $user_id = '') { global $CFG; if ($priority == WLOG_DEBUG && !$CFG->debug) { return TRUE; } // Try to link this information to a particular user // if possible (only when a session exists) and if no // user_id was provided by the caller. if (empty($user_id)) { if (isset($_SESSION['user_id'])) { $user_id = $_SESSION['user_id']; } } $fields = array('datim' => strftime('%Y-%m-%d %T'), 'remote_addr' => (string) $_SERVER['REMOTE_ADDR'], 'priority' => intval($priority), 'user_id' => empty($user_id) ? NULL : intval($user_id), 'message' => (string) $message); $retval = FALSE === db_insert_into('log_messages', $fields) ? FALSE : TRUE; return $retval; }
/** save the modified content data of this module linked to node $node_id * * this validates and saves the data that was submitted by the user. * If validation fails, or storing the data doesn't work, the flag $edit_again * is set to TRUE and the return value is FALSE. * * If the user has cancelled the operation, the flag $edit_again is set to FALSE * and the return value is also FALSE. * * If the modified data is stored successfully, the return value is TRUE (and * the value of $edit_again is a don't care). * * Here is a summary of return values. * * - retval = TRUE ==> data saved successfully * - retval = FALSE && edit_again = TRUE ==> re-edit the data, show the edit dialog again * - retval = FALSE && edit_again = FALSE ==> cancelled, do nothing * * @param object &$output collects the html output (if any) * @param int $area_id the area in which $node_id resides * @param int $node_id the node to which the content is connected * @param array $module the module record straight from the database * @param bool $viewonly if TRUE, editing and hence saving is not allowed * @param bool &$edit_again set to TRUE if we need to edit the content again, FALSE otherwise * @return bool TRUE on success + output stored via $output, FALSE otherwise */ function crew_save(&$output, $area_id, $node_id, $module, $viewonly, &$edit_again) { global $USER; $retval = TRUE; // assume success $module_id = intval($module['module_id']); $node_id = intval($node_id); // 1 -- bail out if cancelled or viewonly if (isset($_POST['button_cancel']) || $viewonly) { $edit_again = FALSE; return FALSE; } // 2 -- redo if invalid data was submitted $dialogdef = crew_get_dialogdef($output, $viewonly, $module_id, $area_id, $node_id, $USER->user_id); if (!dialog_validate($dialogdef)) { // there were errors, show them to the user and ask caller to do it again foreach ($dialogdef as $k => $item) { if (isset($item['errors']) && $item['errors'] > 0) { $output->add_message($item['error_messages']); } } $edit_again = TRUE; return FALSE; } // 3 -- actually save the new (plain) settings (always) $now = strftime('%Y-%m-%d %T'); $table = 'workshops'; $fields = array('header' => $dialogdef['header']['value'], 'introduction' => $dialogdef['introduction']['value'], 'visibility' => $dialogdef['visibility']['value'], 'mtime' => $now, 'muser_id' => $USER->user_id); $where = array('node_id' => intval($node_id)); if (db_update($table, $fields, $where) === FALSE) { logger(sprintf('%s(): error saving config value: %s', __FUNCTION__, db_errormessage())); $edit_again = TRUE; $retval = FALSE; } // 4 -- save or delete the changed ACLs and maybe add new ones too $table = 'acls_modules_nodes'; $where = array('acl_id' => 0, 'node_id' => $node_id, 'module_id' => $module_id); $fields = $where; foreach ($dialogdef as $k => $item) { if (!isset($item['acl_id'])) { continue; } $acl_id = intval($item['acl_id']); $value = intval($item['value']); $old_value = intval($item['old_value']); $dbretval = TRUE; // assume success if ($value != 0) { if (is_null($item['old_value'])) { // need to add a new record $fields['permissions_modules'] = $value; $fields['acl_id'] = $acl_id; $dbretval = db_insert_into($table, $fields); } else { if ($value != $old_value) { // need to update existing record $where['acl_id'] = $acl_id; $dbretval = db_update($table, array('permissions_modules' => $value), $where); } } } else { if (!is_null($item['old_value'])) { // delete existing record because the value is now 0 $where['acl_id'] = $acl_id; $dbretval = db_delete($table, $where); } } if ($dbretval === FALSE) { $messages[] = __FUNCTION__ . '(): ' . db_errormessage(); $edit_again = TRUE; $retval = FALSE; } } return $retval; }
/** save the modified content data of this module linked to node $node_id * * this validates and saves the data that was submitted by the user. * * See also {@link mailpage_show_edit()} for the complications of having a single * routine to deal with two different dialogs. * If validation of dialog 1 fails, or storing the data doesn't work, * the flag $edit_again is set to TRUE and the return value is FALSE. * Validation and storage of data from dialog 2 _always_ returns $edit_again * TRUE because we want to return in dialog #1 after finishing dialog #2. * * If the user has cancelled the operation, the flag $edit_again is set to FALSE * and the return value is also FALSE. * * If the modified data is stored successfully, the return value is TRUE (and * the value of $edit_again is a don't care). Note that this also only applies * to the main dialoag (dialog #1). * * Here is a summary of return values. * * - retval = TRUE ==> data saved successfully * - retval = FALSE && edit_again = TRUE ==> re-edit the data, show the edit dialog again * - retval = FALSE && edit_again = FALSE ==> cancelled, do nothing * * @param object &$output collects the html output (if any) * @param int $area_id the area in which $node_id resides * @param int $node_id the node to which the content is connected * @param array $module the module record straight from the database * @param bool $viewonly if TRUE, editing and hence saving is not allowed * @param bool &$edit_again set to TRUE if we need to edit the content again, FALSE otherwise * @return bool TRUE on success + output stored via $output, FALSE otherwise */ function mailpage_save(&$output, $area_id, $node_id, $module, $viewonly, &$edit_again) { global $USER; $node_id = intval($node_id); $addresses = mailpage_get_addresses($node_id); $sort_order = 10 * (1 + sizeof($addresses)); // $addresses are always renumbered so this is the first largest sord_order $address_id = get_parameter_int('address', NULL); if (is_null($address_id)) { // main config needs to be saved $edit_again = FALSE; // assume we do NOT need to edit again // 1 -- bail out if cancelled or viewonly if (isset($_POST['button_cancel']) || $viewonly) { return FALSE; } // 2 -- redo if invalid data was submitted $dialogdef = mailpage_get_dialogdef_config($output, $viewonly, $node_id); if (!mailpage_dialog_validate($dialogdef, $node_id, $addresses)) { // there were errors, show them to the user and do it again foreach ($dialogdef as $k => $item) { if (isset($item['errors']) && $item['errors'] > 0) { $output->add_message($item['error_messages']); } } $edit_again = TRUE; return FALSE; } // 3 -- actually save the settings $retval = TRUE; // assume success $now = strftime('%Y-%m-%d %T'); $table = 'mailpages'; $fields = array('header' => trim($dialogdef['header']['value']), 'introduction' => trim($dialogdef['introduction']['value']), 'message' => trim($dialogdef['message']['value']), 'mtime' => $now, 'muser_id' => $USER->user_id); $where = array('node_id' => $node_id); if (db_update($table, $fields, $where) === FALSE) { logger(sprintf('%s(): error saving config values: %s', __FUNCTION__, db_errormessage())); $edit_again = TRUE; $retval = FALSE; $output->add_message(t('error_saving_data', 'm_mailpage')); } return $retval; } // // At this point we need to either save a new record, update an existing record, // delete an existing record or simply cancel and return to the main config dialog. // The logic depends on the submit button that was used and the value of $address_id. // $dialogdef = mailpage_get_dialogdef_address($output, $viewonly, $node_id, $address_id, $sort_order); if (!dialog_validate($dialogdef, $node_id, $addresses)) { // there were errors, show them to the user and do it again foreach ($dialogdef as $k => $item) { if (isset($item['errors']) && $item['errors'] > 0) { $output->add_message($item['error_messages']); } } $edit_again = TRUE; return FALSE; } $edit_again = TRUE; // we abuse this flag to return to the main config dialog instead of page mgr if (isset($_POST['button_cancel']) || $viewonly) { return FALSE; } $table = 'mailpages_addresses'; $fields = array('node_id' => $node_id, 'sort_order' => intval($dialogdef['sort_order']['value']), 'name' => trim($dialogdef['name']['value']), 'email' => trim($dialogdef['email']['value']), 'description' => trim($dialogdef['description']['value']), 'thankyou' => trim($dialogdef['thankyou']['value'])); if ($address_id <= 0) { // new record needs to be saved. if (db_insert_into($table, $fields) === FALSE) { logger(sprintf('%s(): error adding address: %s', __FUNCTION__, db_errormessage())); $output->add_message(t('error_saving_data', 'm_mailpage')); } } elseif (isset($addresses[$address_id])) { // OK, that is an existing record $where = array('mailpage_address_id' => $address_id); if (isset($_POST['button_save'])) { // Go save the record if (db_update($table, $fields, $where) === FALSE) { logger(sprintf('%s(): error updating address: %s', __FUNCTION__, db_errormessage())); $output->add_message(t('error_saving_data', 'm_mailpage')); } } elseif (isset($_POST['button_delete'])) { // Go delete this record if (db_delete($table, $where) === FALSE) { logger(sprintf('%s(): error deleting address: %s', __FUNCTION__, db_errormessage())); $output->add_message(t('error_deleting_data', 'm_mailpage')); } } } return FALSE; // Dirty trick to return to the main config dialog }
/** perform actual update to version 2011093000 * * this is yet another substantial change in the database: after we (finally) * standardised on UTF-8 the last time (see {@link update_core_2011051100()} * a number of problems occurred with new installations. * * This specifically occurs with MySQL (currently the only supported database). * In all their wisdom Oracle decided to change the default database engine from * MyISAM to InnoDB in MySQL version 5.5.5. Bad move to do that somewhere in a * sub-sub-release. Anyway. New installations with the default InnoDB engine * AND with the 4-byte utf8mb4 character set (available since sub-sub-release 5.5.3) * now generate serious trouble, because * * - there is a hard-coded limit of 767 bytes for a key (index) in InnoDB, and * - every utf8mb4 character counts as four bytes never mind the actual content. * * Note: the limit of 767 bytes stems from a utf8 (or utf8mb3 as it is now called) * string of max. 255 characters and 1 16-bit string length. 255 * 3 + 2 = 767 bytes. * I wonder why UTF-8 wasn't implemented correctly (ie. with 1 to 4 bytes) to begin with and * the key limit increased to 4 * 255 + 2 = 1022 bytes. The limited UTF-8 support * (only the BMP) now poses substantial problems. Yet another reason to start * looking for an alternative database solution. BTW: the key limit in MyISAM * is 1000 bytes. * * These two conditions (InnoDB and utf8mb4) limit the length of a key (index) to * 767 bytes / 4 bytes-per-char = 191 utf8mb4 characters. As it happens, some * tables in WebsiteAtSchool used keyfields like varchar(240) and even varchar(255). * These key sizes fail in InnoDB/utf8mb4 and the latter even fails with * MyISAM/utf8mb4 because 255 * 4 + 2 = 1022 bytes > 1000 bytes. What a mess... * * So there you have it: all keys MUST be shortened to 191 characters max. in order * to prevent stupid error messages about key too long. The alternative (forcing * another character set such as 'ascii' or 'latin1' for some fields) doesn't cut * it IMHO. * * *sigh* * * We still have a choice of exactly one database driver: MySQL. * Therefore the upgrade we do here can be more or less * MySQL-specific (so much for database-independency), as it has to be, * because the syntax of ALTER TABLE is -- unsuprisingly -- MySQL-specific. * * The good news is that we are still in beta, so a major change in the data * definition is less painful than with hundreds of production servers... * * Another issue is the use of foreign keys. We used to have a FK in the * nodes tabledef along the lines of this construct: * FOREIGN KEY parentnode (parent_id) REFERENCES nodes (node_id); * Upto now this could not possibly have worked with InnoDB because * adding a node would at the top level of an area would not satisfy this * constraint. Since MyISAM silently ignores any foreign key definition * it 'simply works' in that case. So, because this FK must be removed from * earlier installations we need to DROP the FOREIGN KEY. However, since * the whole program never installed using InnoDB, there is no need to drop * this foreign key that wasn't even recorded (in a MyISAM database) in the * first place. The same applies to a number of other FK's too: these are * now removed from the various tabledefs but do no need to be DROPped in * this update routine. * * What needs to be done here? * * For existing tables some fields must be shortened from varchar(255) or * varchar(240) to something like varchar(191) or even less. This MUST be * done for key (index) fields. However, while we are at it some more fields * SHOULD (or COULD) be shortened too. Here is what we do. * * <code> * for all affected table.fields do * if a record exists with current data length > proposed new length then * tell the user about it * endif * next * if there were data length errors then * tell the user about manually fixing it * bail out with result FALSE (= not upgraded) * endif * for all affected table.fields do * change field definition to new length * if errors * tell the user about it (but carry on) * endif * next * return results (TRUE on success, FALSE on 1 or more errors) * </code> * * Below is a discussion of all affected fields and the rationale for * picking the new lengths less than 191 characters. * * <code> * config.name: varchar(240) => varchar(80) * modules_properties.name: varchar(240) => varchar(80) * themes_properties.name: varchar(240) => varchar(80) * themes_areas_properties.name: varchar(240) => varchar(80) * users_properties.name: varchar(240) => varchar(80) * users_properties.section: varchar(240) => varchar(80) * </code> * * Currently the longest parameter name in use is 27 characters, so I * have to admit that the arbitrary size of 240 is a little bit too much. * I'll reduce these fields to a size of 80, which seems a little more * realistic. As an additional bonus, this allows for a compound key * using 'section' and 'name' in users_properties while staying within * the limit of 767 bytes or 191 characters. * * <code> * areas.path: varchar(240) => varchar(60) * groups.path: varchar(240) => varchar(60) * users.path: varchar(240) => varchar(60) * </code> * * and * * <code> * groups.groupname: varchar(255) => varchar(60) * users.username: varchar(255) => varchar(60) * </code> * * The length of username or groupname was arbitrary set to 255. * Different systems have different limits, e.g. 8, 14, 15, 16, * 20, 32, 64 or 128. Since W@S is a stand-alone system we are more * or less free to choose whatever we want (as long as it is less * than 191 of course). * * Since a username or groupname is only used to distinguish one user * from another but at the same time giving at least some readability, * a length of 255 is way too long. An arbitrary but hopefully more * realistic choice is 60 characters. * * The path for a user or group is derived from the corresponding * name so it makes sense to make both fields the same length. * * <code> * log_messages.remote_addr: varchar(255) => varchar(150) * login_failures.remote_addr: varchar(255) => varchar(150) * </code> * * A remote address of type IPv4 generally looks like this: 'ddd.ddd.ddd.ddd' => length 15 * It is not so easy to determine the length of an IPv6 address, because many valid variants exist. * 'xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx' => length 39 * '0000:0000:0000:0000:0000:0000:ddd.ddd.ddd.ddd' => length 45 * '[0000:0000:0000:0000:0000:0000:ddd.ddd.ddd.ddd'] => length 47 (RFC3989) * * Adding to the complexity and confusion are link-local addresses with * zone indices: a percent-sign followed by an interface number * (e.g. '%1') or interface name (e.g. '%eth0') appended to the raw * address. This adds 2 or 5 or even more characters to the address. * And then we of course have the reverse DNS-variant like * 'x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.x.ip6.arpa.' => length 73 * or the special Microsoft trick to shoehorn a literal address in a UNC path: * 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx.ipv6-literal.net' => length 56 or * 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxxs1.ipv6-literal.net' => length 58+ (with zone index) * * Of course there several 'simplifications' such as omitting leading * zeros in the hexquads and replacing the longest sequece of 0-hexquads * with '::' that add to the confusion. RFC5952 adds the definition of a 'canonical * representation' of IPv6 addresses to the party. Mmmm, see http://xkcd.com/927 * * My conclusion is: this whole IPv6-idea suffers from the Second System Syndrome * (see F. Brooks' Mythical Man Month) and unfortunately we have to deal with it. * * *sigh* * * I will reduce the length of these fields from 255 to 150 for no other * reason than that it is 10 times the length of a dotted-decimal IPv4 * address and sufficient to accomodate a reverse DNS address twice (2 x * 73 = 146). * * <code> * sessions.session_key: varchar(255) => varchar(172) * </code> * * This field stores a session key, currently constructed using md5() * which yields a string with 32 (lowercase) hexadecimal characters. In * the future a different digest could be used to provice a session_key, * e.g. SHA-1 (40 hexdigits) or SHA-512 (128 hexdigits). Another option * would be to use a UUID: 128 bits represented in 32 hexdigits in the * form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (string of 36 bytes). * Alternatively, the SHA-512 could be encoded in base64 yielding a * string of 512 / 6 = 86 bytes. In this context, a field of size 255 * seems a little over the top, not to mention problematic with 4-byte * UTF-8 characters combined with the infamous MySQL / InnoDB-limit of * 767 bytes for keyfields. I guess I will settle for a field size of * 172 characters which is not too much for InnoDB keys + utf8mb4 and * exactly enough to store a 1024 bit number in base64. * * @param object &$output collects the html output * @return bool TRUE on success, FALSE otherwise */ function update_core_2011093000(&$output) { global $CFG, $DB; // 0 -- get outta here when already upgraded $version = 2011093000; if ($CFG->version >= $version) { return TRUE; } // List of column definitions keyed by 'tablename:fieldname' copied from (new) tabledefs $alterdefs = array('areas:path' => array('name' => 'path', 'type' => 'varchar', 'length' => 60, 'notnull' => TRUE, 'comment' => 'the place to store user uploaded files etc., relative to CFG->datadir/areas'), 'config:name' => array('name' => 'name', 'type' => 'varchar', 'length' => 80, 'notnull' => TRUE, 'comment' => 'the name of the global configuration parameter'), 'groups:groupname' => array('name' => 'groupname', 'type' => 'varchar', 'length' => 60, 'notnull' => TRUE, 'comment' => 'the short groupname, must be unique too'), 'groups:path' => array('name' => 'path', 'type' => 'varchar', 'length' => 60, 'notnull' => TRUE, 'comment' => 'the place (subdirectory) to store files for this group, relative to CFG->datadir/groups'), 'log_messages:remote_addr' => array('name' => 'remote_addr', 'type' => 'varchar', 'length' => 150, 'notnull' => TRUE, 'comment' => 'IP-address of the visitor'), 'login_failures:remote_addr' => array('name' => 'remote_addr', 'type' => 'varchar', 'length' => 150, 'notnull' => TRUE, 'comment' => 'IP-address of the visitor that failed the login attempt/is blocked'), 'modules_properties:name' => array('name' => 'name', 'type' => 'varchar', 'length' => 80, 'notnull' => TRUE, 'comment' => 'the name of the configuration parameter'), 'sessions:session_key' => array('name' => 'session_key', 'type' => 'varchar', 'length' => 172, 'default' => '', 'comment' => 'contains the unique identifier (\'token\') which is stored in the user\'s cookie'), 'themes_areas_properties:name' => array('name' => 'name', 'type' => 'varchar', 'length' => 80, 'notnull' => TRUE, 'comment' => 'the name of the configuration parameter'), 'themes_properties:name' => array('name' => 'name', 'type' => 'varchar', 'length' => 80, 'notnull' => TRUE, 'comment' => 'the name of the configuration parameter'), 'users:path' => array('name' => 'path', 'type' => 'varchar', 'length' => 60, 'notnull' => TRUE, 'comment' => 'the place (subdirectory) to store files for this user, relative to CFG->datadir/users'), 'users:username' => array('name' => 'username', 'type' => 'varchar', 'length' => 60, 'notnull' => TRUE, 'comment' => 'the account name, must be unique too'), 'users_properties:name' => array('name' => 'name', 'type' => 'varchar', 'length' => 80, 'notnull' => TRUE, 'comment' => 'the name of the configuration parameter'), 'users_properties:section' => array('name' => 'section', 'type' => 'varchar', 'length' => 80, 'notnull' => TRUE, 'comment' => 'keeps related properties grouped together, e.g. in a separate tab'), 'nodes:module_id' => array('name' => 'module_id', 'type' => 'int', 'notnull' => FALSE, 'default' => NULL, 'comment' => 'this connects to the module generating actual node content; NULL for sections')); // // 1 -- check existing data for strings that are too long (only the varchar fields) // $errors = 0; foreach ($alterdefs as $table_field => $fielddef) { if ($fielddef['type'] != 'varchar') { continue; } list($table, $field) = explode(':', $table_field); $length = $fielddef['length']; $where = sprintf('CHAR_LENGTH(%s) > %d', $field, $length); if (($records = db_select_all_records($table, $field, $where)) === FALSE) { $msg = sprintf('%s(): cannot retrieve data from table \'%s\' field \'%s\': %s', __FUNCTION__, $table, $field, db_errormessage()); logger($msg); $output->add_message(htmlspecialchars($msg)); $output->add_message(t('update_core_error', 'admin', array('{VERSION}' => strval($version)))); return FALSE; } if (sizeof($records) <= 0) { continue; } $params = array('{TABLE}' => $table, '{FIELD}' => $field, '{LENGTH}' => $length, '{CONTENT}' => ''); foreach ($records as $record) { ++$errors; logger(sprintf('%s(): content of table \'%s\' field \'%s\' longer than %d: \'%s\'', __FUNCTION__, $table, $field, $length, $record[$field])); $params['{CONTENT}'] = $record[$field]; $output->add_message(t('update_field_value_too_long', 'admin', $params)); } } if ($errors > 0) { logger(sprintf('%s(): number of errors encoutered: %d; bailing out for manual correction', __FUNCTION__, $errors)); $output->add_message(t('update_please_correct_field_value_manually', 'admin', array('{ERRORS}' => $errors))); $msg = t('update_core_error', 'admin', array('{VERSION}' => strval($version))); $output->add_message($msg); $output->add_popup_bottom($msg); // attract some more attention return FALSE; } // // 2 -- actually change the table definitions (both varchar and int) // $overtime = max(intval(ini_get('max_execution_time')), 30); // additional processing time in seconds foreach ($alterdefs as $table_field => $fielddef) { list($table, $field) = explode(':', $table_field); $sql = sprintf('ALTER TABLE `%s%s` CHANGE %s %s', $DB->prefix, $table, $field, $DB->column_definition($fielddef)); if ($DB->exec($sql) === FALSE) { $msg = sprintf('%s(): cannot alter \'%s\' with \'%s\': %d/%s; bailing out', __FUNCTION__, $table, $sql, $DB->errno, $DB->error); logger($msg); $output->add_message(htmlspecialchars($msg)); $output->add_message(t('update_core_error', 'admin', array('{VERSION}' => strval($version)))); return FALSE; } else { if ($fielddef['type'] == 'varchar') { $msg = sprintf('changed type to varchar(%d)', $fielddef['length']); } else { $msg = 'changed \'notnull\' and \'default\' properties'; // there is only nodes.modules_id here... } logger(sprintf('%s(): alter table \'%s\' field \'%s\': %s', __FUNCTION__, $table, $field, $msg), WLOG_DEBUG); } @set_time_limit($overtime); // try to get additional processing time after every processed table } // // 3 -- adjust existing data for nodes.module_id // $table = 'nodes'; $fields = array('module_id' => NULL); $where = array('module_id' => 0); if (($retval = db_update($table, $fields, $where)) === FALSE) { $msg = sprintf('%s(): cannot update \'%s\': %d/%s; bailing out', __FUNCTION__, $table, $sql, $DB->errno, $DB->error); logger($msg); $output->add_message(htmlspecialchars($msg)); $output->add_message(t('update_core_error', 'admin', array('{VERSION}' => strval($version)))); return FALSE; } else { logger(sprintf('%s(): update field \'nodes.module_id\': %d rows affected', __FUNCTION__, $retval), WLOG_DEBUG); } // // 4 -- add new config option and attempt to fix existing data suffering from sort_order bug in page manager // // 4A -- add a new sort option to the CFG $retval = TRUE; // assume success if (!isset($CFG->pagemanager_at_end)) { $table = 'config'; $fields = array('name' => 'pagemanager_at_end', 'type' => 'b', 'value' => '0', 'sort_order' => 240, 'extra' => '', 'description' => 'sort order position within section for new nodes: TRUE is at the end - USER-defined'); if (db_insert_into($table, $fields) === FALSE) { $msg = sprintf("%s(): cannot add config option 'pagemanager_at_end': %s", __FUNCTION__, db_errormessage()); logger($msg); $output->add_message(htmlspecialchars($msg)); $output->add_message(t('update_core_error', 'admin', array('{VERSION}' => strval($version)))); return FALSE; } else { logger(sprintf("%s(): success adding option 'pagemanager_at_end' to configuration table", __FUNCTION__)); } } else { logger(sprintf("%s(): option 'pagemanager_at_end' already set in configuration table", __FUNCTION__)); } // 4B -- attempt to update sort_orders in nodes that are obviously wrong $table = 'nodes'; $fields = array('area_id', 'CASE WHEN node_id=parent_id THEN 0 ELSE parent_id END AS section', 'node_id', 'sort_order'); $where = ''; $order = array('area_id', 'CASE WHEN node_id = parent_id THEN 0 ELSE parent_id END', 'sort_order'); $keyfield = 'node_id'; if (($records = db_select_all_records($table, $fields, $where, $order, $keyfield)) === FALSE) { $msg = sprintf('%s(): cannot retrieve sort orders in nodes; skipping: %s', __FUNCTION__, db_errormessage()); logger($msg); $output->add_message(htmlspecialchars($msg)); $output->add_message(t('update_core_error', 'admin', array('{VERSION}' => strval($version)))); return FALSE; } $count = 0; $area_id = 0; $section = 0; foreach ($records as $node_id => $record) { if ($area_id != $record['area_id'] || $section != $record['section']) { $area_id = $record['area_id']; $section = $record['section']; $sort_order = $record['sort_order'] + 10; } else { if ($sort_order != $record['sort_order']) { $fields = array('sort_order' => intval($sort_order)); $where = array('node_id' => intval($node_id)); if (db_update($table, $fields, $where) === FALSE) { $msg = sprintf("%s(): sort order error in node '%d': %s", __FUNCTION__, $node_id, db_errormessage()); logger($msg); $output->add_message(htmlspecialchars($msg)); $output->add_message(t('update_core_error', 'admin', array('{VERSION}' => strval($version)))); return FALSE; } logger(sprintf('%s(): success updating sort_order from %d => %d in area %d, section %d, node %d', __FUNCTION__, $record['sort_order'], $sort_order, $area_id, $section, $node_id)); ++$count; } $sort_order += 10; } } logger(sprintf('%s(): success updating sort orders in nodes table; count = %d', __FUNCTION__, $count)); // // 5 -- all done: bump version in database // return update_core_version($output, $version); }
/** add 1 point to score for a particular IP-address and failed procedure, return the new score * * This records a login failure in a table and returns the the number * of failures for the specified procedure in the past T1 minutes. * * @param string $remote_addr the remote IP-address that is the origin of the failure * @param int $procedure indicates in which procedure the user failed * @param string $username extra information, could be useful for troubleshooting afterwards * @return int the current score */ function login_failure_increment($remote_addr, $procedure, $username = '') { global $CFG; // this array used to validate $procedure _and_ to make a human readable description with logger() static $procedure_names = array(LOGIN_PROCEDURE_NORMAL => 'normal login', LOGIN_PROCEDURE_CHANGE_PASSWORD => 'change password', LOGIN_PROCEDURE_SEND_LAISSEZ_PASSER => 'send laissez passer', LOGIN_PROCEDURE_SEND_BYPASS => 'send bypass'); $retval = 0; $procedure = intval($procedure); if (isset($procedure_names[$procedure])) { $now = strftime('%Y-%m-%d %T'); $retval = db_insert_into('login_failures', array('remote_addr' => $remote_addr, 'datim' => $now, 'failed_procedure' => $procedure, 'points' => 1, 'username' => $username)); if ($retval !== FALSE) { $minutes = intval($CFG->login_failures_interval); $interval_begin = strftime('%Y-%m-%d %T', time() - $minutes * 60); $where = 'remote_addr = ' . db_escape_and_quote($remote_addr) . ' AND failed_procedure = ' . $procedure . ' AND ' . db_escape_and_quote($interval_begin) . ' < datim'; $record = db_select_single_record('login_failures', 'SUM(points) AS score', $where); if ($record !== FALSE) { $retval = intval($record['score']); } else { logger('could not calculate failure score', WLOG_DEBUG); } } else { logger('could not increment failure count', WLOG_DEBUG); } logger('login: failed; procedure=' . $procedure_names[$procedure] . ', count=' . $retval . ', username=\'' . $username . '\''); } else { logger('internal error: unknown procedure', WLOG_DEBUG); } return $retval; }
/** upgrade the theme * * this routine performs an upgrade to the installed theme. * * Note that the initial version of this 'rosalina' theme does * not need any upgrade at all because there never was an earlier * version (well, duh). * * However, if there was to be a newer version of this theme, this * routine is THE place to bring the database up to date compared with * the existing version. For example, if an additional property 'foobar' * was to be added to the theme configuration, it could be added * to the themes_properties table with a suitable (default) value, * Any existing areas with this theme could have their configuration * updated with this additional foobar property, e.g. * INSERT INTO themes_properties: foobar * for all areas in themes_areas_properties with theme_id = $theme_id do * INSERT INTO themes_areas_properties: foobar * etcetera, * * The current version of the theme could be determined by consulting * the databse (db_select_single_record(themes,'*','theme_id = $theme_id') * etcetera. * * Note that it is the responbabilty of the caller to correctly store * the data from the manifest in the themes table. You should not do * this here, in this routine. * * Currently this is a quick and dirty routine to * - update changed sort_order in the existing settings, OR * - add fields that were not available in the current settings * * In the future we could make it more sophisticated by * updating the themes_areas_properties too. Oh well. KISS * * @param array &$messages collects the (error) messages * @param int $theme_id the key for this theme in the themes table * @return bool TRUE on success + output via $messages, FALSE otherwise * @uses rosalina_get_properties() * @todo maybe make this a little less quick and dirty? */ function rosalina_upgrade(&$messages, $theme_id) { $retval = TRUE; $theme_id = intval($theme_id); $table = 'themes_properties'; // 1 -- fetch current settings $where = array('theme_id' => $theme_id); $fields = array('name', 'sort_order'); $order = 'sort_order'; $keyfield = 'name'; if (($settings = db_select_all_records($table, $fields, $where, $order, $keyfield)) === FALSE) { $messages[] = sprintf('%s(): cannot get settings from %s: %s', __FUNCTION__, $table, db_errormessage()); $retval = FALSE; $settings = array(); // step 3 below expects an array } // 2 -- get new properties $properties = rosalina_get_properties(); // 3 -- selectively update settings or add as new $sort_order = 0; foreach ($properties as $name => $property) { $sort_order += 10; if (isset($settings[$name])) { // existing property, maybe update sort order if ($sort_order != $settings[$name]['sort_order']) { $fields = array('sort_order' => $sort_order); $where = array('theme_id' => $theme_id, 'name' => $name); if (db_update($table, $fields, $where) === FALSE) { $messages[] = __FUNCTION__ . '(): ' . db_errormessage(); $retval = FALSE; } } // else do_not_touch_existing_setting() } else { $property['theme_id'] = $theme_id; $property['name'] = $name; $property['sort_order'] = $sort_order; if (db_insert_into($table, $property) === FALSE) { $messages[] = __FUNCTION__ . '(): ' . db_errormessage(); $retval = FALSE; } } } return $retval; }
/** save the newly added language to the database * * This saves the essential information of a new language to the database, * using sensible defaults for the other fields. Also, a data directory * is created in $CFG->datadir * * If something goes wrong, the user can redo the dialog, otherwise we * return to the languages overview, with the newly added language in the * list, too. * * Apart from the standard checks the following checks are done: * - the language key should be an acceptable directory name * - the language key should be lowercase * - the language key should not exist already (in $this->languages) * - the directory should not yet exist * - the directory must be created here and now * * @return void results are returned as output in $this->output * @uses $WAS_SCRIPT_NAME * @uses $CFG * @uses $USER * @uses $LANGUAGE */ function language_savenew() { global $WAS_SCRIPT_NAME, $USER, $CFG, $LANGUAGE; // 1 -- bail out if user pressed cancel button if (isset($_POST['button_cancel'])) { $this->output->add_message(t('cancelled', 'admin')); $this->languages_overview(); return; } // 2 -- validate the data; check for generic errors (string too short, number too small, etc) $dialogdef = $this->get_dialogdef_language(); $invalid = dialog_validate($dialogdef) ? FALSE : TRUE; // 3 -- additional validation & massaging of the language key $fname = isset($dialogdef['language_key']['label']) ? $dialogdef['language_key']['label'] : 'language_key'; $params = array('{FIELD}' => str_replace('~', '', $fname)); // 3A -- additional check: the language key doubles as a directory name AND should be lowercase $path = $dialogdef['language_key']['value']; $languagedata_directory = strtolower(sanitise_filename($path)); if ($path != $languagedata_directory) { // User probably entered a few 'illegal' characters. This is no good $dialogdef['language_key']['value'] = $languagedata_directory; // 'Help' user with a better proposition? ++$dialogdef['language_key']['errors']; $params['{VALUE}'] = htmlspecialchars($path); $dialogdef['language_key']['error_messages'][] = t('validate_bad_filename', '', $params); $invalid = TRUE; } // 3B -- additional check: unique language key name entered if (isset($this->languages[$languagedata_directory])) { // Oops, already exists ++$dialogdef['language_key']['errors']; $params['{VALUE}'] = $languagedata_directory; $dialogdef['language_key']['error_messages'][] = t('validate_not_unique', '', $params); $invalid = TRUE; } // 3C -- additional check: can we create said directory? $languagedata_full_path = $CFG->datadir . '/languages/' . $languagedata_directory; $languagedata_directory_created = @mkdir($languagedata_full_path, 0700); if ($languagedata_directory_created) { @touch($languagedata_full_path . '/index.html'); // "protect" the newly created directory from prying eyes } else { // Mmmm, failed; probably already exists then. Oh well. Go flag error. ++$dialogdef['language_key']['errors']; $params['{VALUE}'] = '/languages/' . $languagedata_directory; $dialogdef['language_key']['error_messages'][] = t('validate_already_exists', '', $params); $invalid = TRUE; } // 3E -- if there were any errors go redo dialog while keeping data already entered if ($invalid) { if ($languagedata_directory_created) { // Only get rid of the directory _we_ created @unlink($languagedata_full_path . '/index.html'); @rmdir($languagedata_full_path); } // there were errors, show them to the user and do it again foreach ($dialogdef as $k => $item) { if (isset($item['errors']) && $item['errors'] > 0) { $this->output->add_message($item['error_messages']); } } $this->output->add_content('<h2>' . t('translatetool_add_language_header', 'admin') . '</h2>'); $this->output->add_content(t('translatetool_add_language_explanation', 'admin')); $href = href($WAS_SCRIPT_NAME, $this->a_param(TRANSLATETOOL_CHORE_LANGUAGE_SAVE_NEW)); $this->output->add_content(dialog_quickform($href, $dialogdef)); return; } // 4 -- go save the new language $language_key = $dialogdef['language_key']['value']; $language_name = $dialogdef['language_name']['value']; $parent_key = $dialogdef['parent_language_key']['value'] == '--' ? NULL : $dialogdef['parent_language_key']['value']; $is_active = $dialogdef['language_is_active']['value'] == '1' ? TRUE : FALSE; $fields = array('language_key' => $language_key, 'parent_language_key' => $parent_key, 'language_name' => $language_name, 'version' => 0, 'manifest' => '', 'is_core' => FALSE, 'is_active' => $is_active, 'dialect_in_database' => FALSE, 'dialect_in_file' => FALSE); if (db_insert_into('languages', $fields) === FALSE) { if ($languagedata_directory_created) { // Only get rid of the file/directory _we_ created @unlink($languagedata_full_path . '/index.html'); @rmdir($languagedata_full_path); } logger(sprintf('%s.%s(): saving new language \'%s\' failed: %s', __CLASS__, __FUNCTION__, htmlspecialchars($language_key), db_errormessage())); $this->output->add_message(t('translatetool_language_savenew_failure', 'admin')); } else { $params = array('{LANGUAGE_KEY}' => $language_key, '{LANGUAGE_NAME}' => $language_name); $this->output->add_message(t('translatetool_language_savenew_success', 'admin', $params)); logger(sprintf("%s.%s(): success saving new language '%s' (%s) with data directory /languages/%s", __CLASS__, __FUNCTION__, $language_name, $language_key, $languagedata_directory)); $this->languages = $LANGUAGE->retrieve_languages(TRUE); // TRUE means force reread from database after add } $this->languages_overview(); }