/** manufacture a theme object * * This loads (includes) a specific theme based on the parameter * $theme_id. Relevant data is read from the database. * * @param int $theme_id denotes which theme to retrieve from database via primary key * @param int $area_id the area we're working in * @param int $node_id the node that is to be displayed * @return bool|object FALSE on error, or an instance of the specified theme class * @todo should we massage the directory and file names of the included theme? * @todo what if the theme is not found? Currently no alternative is loaded but FALSE is returned. * @uses $CFG */ function theme_factory($theme_id, $area_id, $node_id) { global $CFG; $o = FALSE; // assume failure $theme_record = db_select_single_record('themes', '*', array('theme_id' => intval($theme_id), 'is_active' => TRUE)); if ($theme_record !== FALSE) { /* We have an existing and active theme. We know that we can find the * theme's files in $CFG->progdir.'/themes/'.$theme_name. * The file to include is called $theme_filename and the class * is $theme_class. We will now try to include the relevant file * and instantiate the theme. */ $theme_name = $theme_record['name']; // e.g. 'frugal' $theme_class = $theme_record['class']; // e.g. 'ThemeFrugal' $theme_filename = $theme_record['class_file']; // e.g. 'frugal.class.php' $theme_directory = $CFG->progdir . '/themes/' . $theme_name; if (is_file($theme_directory . '/' . $theme_filename)) { include_once $theme_directory . '/' . $theme_filename; $o = new $theme_class($theme_record, $area_id, $node_id); } } return $o; }
/** display the content of the htmlpage 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 htmlpage_view(&$theme, $area_id, $node_id, $module) { $record = db_select_single_record('htmlpages', 'page_data', array('node_id' => intval($node_id))); if ($record === FALSE) { $msg = "Oops. Something went wrong with the htmlpage linked to node '{$node_id}'"; $theme->add_message($msg); $theme->add_popup_top($msg); $retval = FALSE; } else { // if ((10 <= $node_id) && ($node_id <= 11)) { // $msg = 'STUB to demonstrate the message area and popup-function (only pages 10 and 11): <b>'. // __FUNCTION__."</b>(\$theme,$area_id,$node_id,{$module['module_id']})"; // $theme->add_message($msg); // $theme->add_popup_bottom(strip_tags($msg)); // } $theme->add_content($record['page_data']); $retval = TRUE; } return $retval; }
/** construct the (possibly translated) name of the last directory in the path * * This examines $path and returns a string with the last directory component. * There are a few special cases: * * - the empty string indicates the root directory * - /users/<userpath> maps to a (translated) string t('filemanager_personal') * - /areas maps to a (translated) string t('filemanager_areas') * - /groups maps to a (translated) string t('filemanager_groups') * - /users maps to a (translated) string t('filemanager_users') * * All other variations yield the last component in the list of components. * * @param string $path the path to examine * @return string the (possibly translated) name of the last directory component */ function vname($path) { global $USER; $vname = ''; // assume nothing $path_components = $this->explode_path($path); $n = count($path_components); if ($n <= 1) { switch ($path_components[0]) { case 'areas': $vname = t('filemanager_areas', 'admin'); break; case 'users': $vname = t('filemanager_users', 'admin'); break; case 'groups': $vname = t('filemanager_groups', 'admin'); break; case '': $vname = t('filemanager_root', 'admin'); break; } } elseif ($n == 2) { $name = strval($path_components[1]); switch ($path_components[0]) { case 'areas': $vname = $name; // fall back foreach ($this->areas as $area_id => $area) { if ($area['path'] == $name) { $vname = $area['title']; break; } } break; case 'users': if ($name == $USER->path) { $vname = t('filemanager_personal', 'admin'); } else { $table = 'users'; $fields = array('full_name'); $where = array('path' => $name); if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { $vname = $name; // fall back } else { $vname = $record['full_name']; } } break; case 'groups': $table = 'groups'; $fields = array('full_name'); $where = array('path' => $name); if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { $vname = $name; // fall back } else { $vname = $record['full_name']; } break; } } else { $vname = $path_components[$n - 1]; // last component of the path } return $vname; }
/** construct a title, summary and readmore prompt for an htmlpage page * * this routine uses a heuristic approach to snip N paragraphs from * the actual text in the html-page. Because we cannot be sure that * stripos() is available we resort to first changing any '<P' to * '<p ' and subsequently searching the string until the N+1'th '<p '. * The offset we calculate this way should contain exactly N * paragraphs. Obviously this assumes (dangerous...) that the htmlpage * page_data actually contains paragraphs. However, if not enough '<p * strings are found, the complete page is used. Heuristics... * * @param int $counter is a sequential number identifying the aggregated nodes * @param object &$theme points to theme where the output goes * @param array &$node points to the node record of this htmlpage * @param array &$config points to the aggregator configuration * @return array ordered list of nodes to aggregate (could be empty) */ function aggregator_view_htmlpage($counter, &$theme, &$node, &$config) { $id = strval($counter); // used to make all id's within this item unique // 1 -- outer div holds title+blurb+readmore... $attributes = array('class' => 'aggregator_htmlpage_outer', 'id' => 'aggregator_outer_' . $id); $theme->add_content(html_tag('div', $attributes)); // 2A -- title $attributes = array('class' => 'aggregator_htmlpage_header', 'id' => 'aggregator_header_' . $id); $theme->add_content(' ' . html_tag('h3', $attributes, $node['title'])); // 2B -- blurb (enclosed in inner div) $attributes = array('class' => 'aggregator_htmlpage_inner', 'id' => 'aggregator_inner_' . $id); $theme->add_content(' ' . html_tag('div', $attributes)); // fetch actual content from database (requires knowledge of internals of htmlpage module/table) $table = 'htmlpages'; $node_id = intval($node['node_id']); $where = array('node_id' => $node_id); $fields = array('page_data'); if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf('%s: no pagedata (node=%d): %s', __FUNCTION__, $node_id, db_errormessage())); $pagedata = ''; } else { // make SURE we only have lowercase <p followed by a space in the text (easy strpos'ing) $pattern = '/(<[pP])([^a-zA-Z0-9])/'; $replace = '<p $2'; $pagedata = preg_replace($pattern, $replace, $record['page_data']); } $offset = -1; $limit = $config['htmlpage_length']; for ($i = 0; $i <= $limit; ++$i) { if (($offset = strpos($pagedata, '<p ', $offset + 1)) === FALSE) { break; } } if ($offset === FALSE) { // not enough '<p ' seen => show everything $theme->add_content($pagedata . "\n"); } else { $theme->add_content(substr($pagedata, 0, $offset) . "\n"); } $theme->add_content(' </div>'); // 2C -- readmore prompt $anchor = t('htmlpage_more', 'm_aggregator'); $title = t('htmlpage_more_title', 'm_aggregator'); $attr = array('title' => $title); $href = was_node_url($node, NULL, '', $theme->preview_mode); $attributes = array('class' => 'aggregator_htmlpage_more', 'id' => 'aggregator_more_' . $id); $theme->add_content(' ' . html_tag('div', $attributes, html_a($href, NULL, $attr, $anchor))); $theme->add_content(' <div style="clear:both;"></div>'); // 3 -- close outer div $theme->add_content('</div>'); }
/** load the visitor/view interface of a module in core * * this includes the 'view'-part of a module via 'require_once()'. This routine * first figures out if the view-script file actually exists before the * file is included. Also, we look at a very specific location, namely: * /program/modules/<modulename>/<module_view_script> where <modulename> is retrieved * from the modules table in the database. * * Note that if modulename would somehow be something like "../../../../../../etc/passwd\x00", * we could be in trouble... * * @param int $module_id indicates which module to load * @return bool|array FALSE on error or an array with the module record from the modules table * @todo should we sanitise the modulename here? It is not user input, but it comes from the modules * table in the database. However, if a module name would contain sequences of "../" we might * be in trouble */ function module_load_view($module_id) { global $CFG; $module_record = db_select_single_record('modules', '*', array('module_id' => intval($module_id), 'is_active' => TRUE)); if ($module_record === FALSE) { logger("module_load_view(): weird: module '{$module_id}' is not there. Is it de-activated?"); return FALSE; } $module_view_script = $CFG->progdir . '/modules/' . $module_record['name'] . '/' . $module_record['view_script']; if (!file_exists($module_view_script)) { logger("module_load_view(): weird: file '{$module_view_script}' does not exist. Huh?"); return FALSE; } require_once $module_view_script; return $module_record; }
/** call the theme-specific upgrade routine * * this routine tries to execute the correct upgrade script/function for * theme $theme_id. If all goes well, a success message is written to $output * (and the update is performed), otherwise an error message is written to $output * Either way the event is logged via logger(). * * Note that we take care not to load spurious files and execute non-existing functions. * However, at some point we do have to have some trust in the file system... * * @param object &$output collects the html output * @param string $theme_key unique secondary key for theme record in themes table in database * @return void results are returned as output in $output */ function update_theme(&$output, $theme_key) { global $CFG; $theme_key = strval($theme_key); $params = array('{THEME}' => $theme_key); $progdir_themes = $CFG->progdir . '/themes'; $manifests = get_manifests($progdir_themes); // 1 -- validate input: theme must exist in available manifests if (!isset($manifests[$theme_key])) { logger(sprintf('%s(): manifest for theme \'%s\' not found; nothing updated', __FUNCTION__, $theme_key)); $output->add_message(t('update_subsystem_theme_error', 'admin', $params)); return; } $manifest = $manifests[$theme_key]; // 2 -- get the primary key of the installed theme (required for {$theme}_update() function) $table = 'themes'; $fields = array('theme_id', 'name'); $where = array('name' => $theme_key); $record = db_select_single_record($table, $fields, $where); if ($record === FALSE) { logger(sprintf('%s(): error retrieving theme_id for \'%s\': %s', __FUNCTION__, $theme_key, db_errormessage())); $output->add_message(t('update_subsystem_theme_error', 'admin', $params)); return; } $name = $record['name']; $theme_id = intval($record['theme_id']); // 3 -- let the theme update code do its thing $retval = FALSE; // assume failure $messages = array(); // collects error messages from call-back routine if (isset($manifest['install_script']) && !empty($manifest['install_script'])) { $filename = sprintf('%s/%s/%s', $progdir_themes, $name, $manifest['install_script']); if (file_exists($filename)) { @(include_once $filename); $theme_upgrade = $name . '_upgrade'; if (function_exists($theme_upgrade)) { if ($theme_upgrade($messages, $theme_id)) { logger(sprintf('%s(): %s(): success upgrading theme \'%s\' (new version is %d)', __FUNCTION__, $theme_upgrade, $name, $manifest['version'])); $retval = TRUE; // so far so good; remember this success } else { logger(sprintf('%s(): %s() returned an error', __FUNCTION__, $theme_upgrade)); } foreach ($messages as $message) { // remember messages, either good or bad logger($message); } } else { logger(sprintf('%s(): function %s() does not exist?', __FUNCTION__, $theme_upgrade)); } } else { logger(sprintf('%s(): file %s does not exist?', __FUNCTION__, $filename)); } } else { logger(sprintf('%s(): no install script specified in manifest for theme \'%s\'', __FUNCTION__, $name)); } // 4 -- on success update the relevant record in the database (and make it active) if ($retval) { $table = 'themes'; $fields = array('version' => intval($manifest['version']), 'manifest' => $manifest['manifest'], 'is_active' => TRUE, 'class' => isset($manifest['class']) ? $manifest['class'] : NULL, 'class_file' => isset($manifest['class_file']) ? $manifest['class_file'] : NULL); $where = array('theme_id' => $theme_id); if (db_update($table, $fields, $where) === FALSE) { logger(sprintf('%s(): cannot update theme data for \'%s\': %s', __FUNCTION__, $theme_key, db_errormessage())); $retval = FALSE; } } // 5 -- inform the user about the final outcome if ($retval) { $output->add_message(t('update_subsystem_theme_success', 'admin', $params)); } else { $output->add_message(t('update_subsystem_theme_error', 'admin', $params)); } }
/** validate and massage the user-supplied data path * * this checks the directory path the user entered, * returns TRUE if the tests are passed. * * There three places from which snapshots can be retrieved: * - /areas/aaa * - /users/uuu * - /groups/ggg * * That is: the path should at least contain 2 levels (and possibly more). * In other words: a bare '/' is not enough and neither are bare '/areas', * '/users' or '/groups'. And of course the directory should already exist * in the file systen under $CFG->datadir. * * Various tests are done: * - the selected area directory must be active * - if the selected area is private, * $USER must have intranet access for this area, OR * the selected area must be the same as the area in which $node_id resides * - the selected user directory must be the $USER's, OR * the $USER has access to the account manager (able to manipulate ALL users' directories) * - the selected group directory must be from a group the $USER is a member of, OR * the $USER has access to the account manager (able to manipulate ALL groups' directories) * * If all tests succeed, we may want to warn the user in the case that the * file location is in a different (and public) area than the node holding the snapshots module. * However, this is a warning only. * * Finally, we reconstruct the path in such a way that it starts with a slash * and does NOT end with a slash. This is done by changing the content of the $item parameter. * * @param array &$item holds the field definition from the $dialogdef for the snapshots_path * @param int $area_id the area in which we are editing a snapshot module configuration * @param int $node_id the node to which the snapshot module is connected (unused) * @return bool TRUE if valid path, otherwise FALSE + messages in dialogdef * @todo should the user / group really be active here? If not, the images will fail in file.php * but that may leak information about inactive users. Hmmm... * @todo we should use a different error message as soon as it is available in was.php, * eg. 'validate_bad_directory' (much like 'validate_bad_filename'). */ function snapshots_check_path(&$item, $area_id, $node_id) { global $USER, $CFG; $warning = ''; $invalid = FALSE; $path_components = explode('/', trim(strtr($item['value'], '\\', '/'), '/')); if (sizeof($path_components) < 2 || in_array('..', $path_components)) { $invalid = TRUE; } else { switch ($path_components[0]) { case 'areas': $fields = array('area_id', 'is_private', 'title'); $where = array('is_active' => TRUE, 'path' => $path_components[1]); $table = 'areas'; if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { // area doesn't exist or is inactive $invalid = TRUE; } elseif (db_bool_is(TRUE, $record['is_private'])) { // specified area is private if (intval($record['area_id']) != $area_id || !$USER->has_intranet_permissions(ACL_ROLE_INTRANET_ACCESS, $record['area_id'])) { // this private area is NOT the one where $node_id resides OR this user is denied access $invalid = TRUE; } } else { // specified area is public if (intval($record['area_id']) != $area_id) { // but it is not the same as the one where $node_id resides: go warn user eventually! $params = array('{AREANAME}' => htmlspecialchars($record['title'])); $warning = t('warning_different_area', 'm_snapshots', $params); } } break; case 'users': if (!$USER->has_job_permissions(JOB_PERMISSION_ACCOUNTMANAGER) && $path_components[1] != $USER->path) { $invalid = TRUE; } if ($path_components[1] == $USER->path) { $warning = t('warning_personal_directory', 'm_snapshots'); } break; case 'groups': if (!$USER->has_job_permissions(JOB_PERMISSION_ACCOUNTMANAGER)) { $usergroups = get_user_groups($USER->user_id); $is_member = FALSE; foreach ($usergroups as $group_id => $usergroup) { if ($usergroup['path'] == $path_components[1]) { $is_member = TRUE; break; } } if (!$is_member) { $invalid = TRUE; } } break; default: $invalid = TRUE; break; } } if (!$invalid) { $path = '/' . implode('/', $path_components); if (!is_dir($CFG->datadir . $path)) { $invalid = TRUE; } } if ($invalid) { $fname = str_replace('~', '', $item['label']); $params = array('{PATH}' => htmlspecialchars($item['value'])); $error_message = sprintf('%s: %s', $fname, t('invalid_path', 'admin', $params)); ++$item['errors']; $item['error_messages'][] = $error_message; return FALSE; } if ($warning != '') { $item['warnings'] = 0; ++$item['warnings']; $item['warning_messages'] = $warning; } $item['value'] = $path; return TRUE; }
/** retrieve all configuration data for this mailpage * * this retrieves all configuration data for this mailpage, * i.e. both the general parameters (header/intro/etc.) and * the full list of configured destination addresses. * Here is a quick reminder of the structure in $config. * <pre> * 'node_id' * 'header' * 'introduction' * 'message' * 'addresses' = ['mailpage_address_id','node_id','sort_order','name','email','description','thankyou'],... * </pre> * * @param int $node_id identifies the page * @return bool|array configuration data in a (nested) array or FALSE on error */ function mailpage_view_get_config($node_id) { // 1 -- generic configuration $table = 'mailpages'; $fields = array('node_id', 'header', 'introduction', 'message'); $where = array('node_id' => intval($node_id)); if (($config = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf('%s(): error retrieving configuration: %s', __FUNCTION__, db_errormessage())); return FALSE; } // 2 -- fetch all configured destinations $table = 'mailpages_addresses'; $fields = '*'; $where = array('node_id' => intval($node_id)); $order = array('sort_order', 'mailpage_address_id'); if (($records = db_select_all_records($table, $fields, $where, $order)) === FALSE) { logger(sprintf('%s(): error retrieving addresses: %s', __FUNCTION__, db_errormessage())); return FALSE; } $config['addresses'] = $records; // simple 0-based array, not keyed by mailpage_address_id return $config; }
/** construct a dialog definition for a mailpage address configuration * * @param object &$output collects the html output (if any) * @param int $viewonly if TRUE the Save button is not displayed and config values cannot be changed * @param int $node_id identifies the current mailpage * @param int $address_id identifies the address, 0 for a new one * @param int $sort_order is the next available sort order for new records * @return array dialog definition */ function mailpage_get_dialogdef_address(&$output, $viewonly, $node_id, $address_id, $sort_order = 10) { // // 1 -- maybe get existing data from table // $values = array('name' => '', 'sort_order' => $sort_order, 'email' => '', 'description' => '', 'thankyou' => '', 'message' => ''); if ($address_id > 0) { $table = 'mailpages_addresses'; $keyfield = 'mailpage_address_id'; $fields = '*'; $where = array('node_id' => intval($node_id), $keyfield => intval($address_id)); if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf('%s(): error retrieving configuration: %s', __FUNCTION__, db_errormessage())); $output->add_message(t('error_retrieving_data', 'admin')); } else { $values = $record; } } // // 2 -- define the dialog // $dialogdef = array('name' => array('type' => F_ALPHANUMERIC, 'name' => 'name', 'minlength' => 0, 'maxlength' => 80, 'columns' => 40, 'label' => t('address_name_label', 'm_mailpage'), 'title' => t('address_name_title', 'm_mailpage'), 'viewonly' => $viewonly, 'value' => $values['name'], 'old_value' => $values['name']), 'email' => array('type' => F_ALPHANUMERIC, 'name' => 'email', 'minlength' => 0, 'maxlength' => 255, 'columns' => 40, 'label' => t('address_email_label', 'm_mailpage'), 'title' => t('address_email_title', 'm_mailpage'), 'viewonly' => $viewonly, 'value' => $values['email'], 'old_value' => $values['email']), 'description' => array('type' => F_ALPHANUMERIC, 'name' => 'description', 'minlength' => 0, 'maxlength' => 240, 'columns' => 40, 'label' => t('address_description_label', 'm_mailpage'), 'title' => t('address_description_title', 'm_mailpage'), 'viewonly' => $viewonly, 'value' => $values['description'], 'old_value' => $values['description']), 'thankyou' => array('type' => F_ALPHANUMERIC, 'name' => 'thankyou', 'minlength' => 0, 'maxlength' => 240, 'columns' => 40, 'label' => t('address_thankyou_label', 'm_mailpage'), 'title' => t('address_thankyou_title', 'm_mailpage'), 'viewonly' => $viewonly, 'value' => $values['thankyou'], 'old_value' => $values['thankyou']), 'sort_order' => array('type' => F_INTEGER, 'name' => 'sort_order', 'minlength' => 0, 'maxlength' => 10, 'columns' => 7, 'minvalue' => 0, 'label' => t('address_sort_order_label', 'm_mailpage'), 'title' => t('address_sort_order_title', 'm_mailpage'), 'viewonly' => $viewonly, 'value' => $values['sort_order'], 'old_value' => $values['sort_order'])); // // 3 -- add buttons: save (mabye), cancel and delete (maybe) // if (!$viewonly) { $dialogdef['button_save'] = dialog_buttondef(BUTTON_SAVE); } $dialogdef['button_cancel'] = dialog_buttondef(BUTTON_CANCEL); if (!$viewonly && $address_id != 0) { $dialogdef['button_delete'] = dialog_buttondef(BUTTON_DELETE); } return $dialogdef; }
/** create three areas + themes * * @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_areas(&$messages, &$config, &$tr) { global $wizard; // This is a kludge to get to the sanitise_filename() code. There must be a better way... global $DB; static $seq = 0; // circumvent file/directory name clashes by appending a 'unique' sequence number $retval = TRUE; // assume success // 0 -- setup essential information if (($record = db_select_single_record('themes', 'theme_id', array('name' => 'frugal'))) === FALSE) { $messages[] = $tr['error'] . ' ' . db_errormessage(); $retval = FALSE; $theme_id = 1; // lucky guess } else { $theme_id = intval($record['theme_id']); } $user_id = $config['user_id']; $metadata = "<meta name=\"keywords\" content=\"school website, websiteatschool, primary education, " . "secondary education, freire, freinet, habermas, learing tool, it learning tool, " . "ict learing tool, ict, bazaar style sheet, bss, screen reader, braille reader, " . "braille terminal, learning html, learning css, free software, exemplum, site@school, " . "siteatschool, websiteatschool.eu\">\n" . "<meta name=\"description\" content=\"Website@School is a website content management system " . "(CMS) for schools\">\n"; $now = strftime('%Y-%m-%d %T'); // 1 -- construct area records $areas = array('public' => array('title' => $tr['public_area_title'], 'is_private' => FALSE, 'is_active' => TRUE, 'is_default' => TRUE, 'path' => utf8_strtolower($wizard->sanitise_filename($tr['public_area_path'])), 'metadata' => $metadata, 'sort_order' => 10, 'theme_id' => $theme_id, 'ctime' => $now, 'cuser_id' => $user_id, 'mtime' => $now, 'muser_id' => $user_id), 'private' => array('title' => $tr['private_area_title'], 'is_private' => TRUE, 'is_active' => TRUE, 'is_default' => FALSE, 'path' => utf8_strtolower($wizard->sanitise_filename($tr['private_area_path'])), 'sort_order' => 20, 'theme_id' => $theme_id, 'ctime' => $now, 'cuser_id' => $user_id, 'mtime' => $now, 'muser_id' => $user_id), 'extra' => array('title' => $tr['extra_area_title'], 'is_private' => FALSE, 'is_active' => FALSE, 'is_default' => FALSE, 'path' => utf8_strtolower($wizard->sanitise_filename($tr['extra_area_path'])), 'sort_order' => 30, 'theme_id' => $theme_id, 'ctime' => $now, 'cuser_id' => $user_id, 'mtime' => $now, 'muser_id' => $user_id)); // 1A -- make sure the area directories so not exist yet; maybe change name $datadir_areas = $config['datadir'] . '/areas/'; foreach ($areas as $area => $fields) { $path = $fields['path']; if ($path == '_') { $path .= strval(++$seq); } $ext = ''; while (is_dir($datadir_areas . $path . $ext)) { $ext = strval(++$seq); } $areas[$area]['path'] = $path . $ext; } // 1B -- actually make the directories and store the name and other data in table foreach ($areas as $area => $fields) { $fullpath = $datadir_areas . $fields['path']; if (@mkdir($fullpath, 0700)) { @touch($fullpath . '/index.html'); // try to "protect" directory } else { $messages[] = $tr['error'] . " mkdir('{$fullpath}')"; $retval = FALSE; } if (($area_id = db_insert_into_and_get_id('areas', $fields, 'area_id')) === FALSE) { $messages[] = $tr['error'] . ' ' . db_errormessage(); $retval = FALSE; } $area_id = intval($area_id); // remember the area_id $areas[$area]['area_id'] = intval($area_id); // copy the theme's default setting to this area $sql = sprintf('INSERT INTO %s%s(area_id,theme_id,name,type,value,extra,sort_order,description) ' . 'SELECT %d AS area_id,theme_id,name,type,value,extra,sort_order,description ' . 'FROM %s%s ' . 'WHERE theme_id = %d', $DB->prefix, 'themes_areas_properties', intval($area_id), $DB->prefix, 'themes_properties', intval($theme_id)); if ($DB->exec($sql) === FALSE) { $messages[] = $tr['error'] . ' ' . db_errormessage(); $retval = FALSE; } } $config['demo_areas'] = $areas; return $retval; }
/** present the user with a dialog to modify the aggregator that are connected to node $node_id * * this prepares a dialog for the user filled with existing data (if any), possibly allowing * the user to modify the content. If the flag $viewonly is TRUE, this routine should only * display the content rather than let the user edit it. If the flag $edit_again is TRUE, * the routine should use the data available in the $_POST array, otherwise it should read * the data from the database (or wherever the data comes from). The parameter $href is the * place where the form should be POST'ed. * * The dialog should be added to the $output object. Useful routines are: * <code> * $output->add_content($content): add $content to the content area * $output->add_message($message): add $message to the message area (feedback to the user) * $output->add_popup_bottom($message): make $message popup in the browser after loading the page (uses javascript) * $output->add_popup_top($message): make $message popup in the browser before loading the page (uses javascript) * </code> * * @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 this module is connected * @param array $module the module record straight from the database * @param bool $viewonly if TRUE, editing is not allowed (but simply showing the content is allowed) * @param bool $edit_again if TRUE start with data from $_POST, else use data from database * @param string $href the action property of the HTML-form, the place where data will be POST'ed * @return bool TRUE on success + output stored via $output, FALSE otherwise */ function aggregator_show_edit(&$output, $area_id, $node_id, $module, $viewonly, $edit_again, $href) { $dialogdef = aggregator_get_dialogdef($viewonly); if ($edit_again) { // retrieve and validate the POSTed values dialog_validate($dialogdef); // no need to show messages; we did that alread in aggregator_save() aggregator_check_node_list($dialogdef['node_list'], $area_id, $node_id); } else { // make a fresh start with data from the database $where = array('node_id' => intval($node_id)); if (($record = db_select_single_record('aggregator', '*', $where)) === FALSE) { logger(sprintf('%s(): error retrieving aggregator configuration: %s', __FUNCTION__, db_errormessage())); } else { foreach ($dialogdef as $name => $item) { switch ($name) { case 'header': case 'introduction': case 'node_list': case 'items': case 'reverse_order': case 'htmlpage_length': case 'snapshots_width': case 'snapshots_height': case 'snapshots_visible': case 'snapshots_showtime': $dialogdef[$name]['value'] = strval($record[$name]); break; } } } } $output->add_content('<h2>' . t('aggregator_content_header', 'm_aggregator') . '</h2>'); $output->add_content(t('aggregator_content_explanation', 'm_aggregator')); $output->add_content(dialog_quickform($href, $dialogdef)); return TRUE; }
/** retrieve a single group's record possibly from the cache * * @param int $group_id identifies the group record * @param bool $forced if TRUE unconditionally fetch the record from the database * @return bool|array FALSE if there were errors, the group record otherwise */ function get_group_record($group_id, $forced = FALSE) { $group_id = intval($group_id); if (!isset($this->groups[$group_id]) || $forced) { $table = 'groups'; $fields = '*'; $where = array('group_id' => $group_id); if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf("%s.%s(): cannot retrieve record for group '%d': %s", __CLASS__, __FUNCTION__, $group_id, db_errormessage())); $this->output->add_message(t('error_retrieving_data', 'admin')); return FALSE; } else { $this->groups[$group_id] = $record; } } return isset($this->groups[$group_id]) ? $this->groups[$group_id] : FALSE; }
/** put a (co-operative) lock on a record * * this tries to set the co-operative) lock on the record with serial (pkey) $id * in table $tablename by setting the $locked_by field to our own session_id. This * is the companion routine of {@link lock_release()}. * * The mechanism of co-operative locking works as follows. Some tables (such as * the 'nodes' table) have an int field, e.g. 'locked_by_session_id'. This field * can either be NULL (indicating that the record is not locked) or hold the primary * key of a session (indicating that the record is locked and also by which session). * * Obtaining a lock boils down to updating the table and setting that field to the * session_id. As long as the underlying database system guarantees that execution * of an UPDATE statement is not interrupted, we can use UPDATE as a * 'Test-And-Set'-function. According to the docentation MySQL does this. * * The procedure is as follows. * * 1. we try to set the locked_by-field to our session_id on the condition that * the previous value of that field is NULL. If this succeeds, we have effectively * locked the record. * * 2. If this fails, we retrieve the current value of the field to see which session has * locked it. If this happens to be us, we had already locked the record before and * we're done. * * 3. If another session_id holds the lock, we check for that session's existence. If it * still exists, we're out of luck: we can't obtain the lock. * * 4. If that other session does no longer exist, we try to replace that other session's * session_id with our own session_id, once again using a single UPDATE (avoiding another * race condition). If that succeeds we're done and we have the lock; if it failes * we're also done but without lock. * * If locking the record fails because the record is already locked by another session, * this routine returns information about that other session in $lockinfo. It is up to * the caller to use this information or not. * * Note. * A record can stay locked if the webbrowser of the locking session has crashed. Eventually * this will be resolved if the crashed session is removed from the sessions table. However, * the user may have restarted her browser while the record was locked. From the new session * it appears that the record is still locked. This may take a while. Mmmmm... * The other option is to lock on a per-user basis rather than per-session basis. Mmmm... * Should we ask the user to override the session if it happens to be the same user? * Mmm. put it on the todo list. (A small improvement might be to call the garbage collection * between step 2 and 3. Oh well). * * @todo perhaps we can save 1 trip to the database by checking for something like * UPDATE SET locked_by = $session_id WHERE (id = $id) AND ((locked_by IS NULL) OR (locked_by = $session_id)) * but I don't know how many affected rows that would yield if we already had the lock and * effectively nothing changes in the record. (Perhaps always update atime to force 1 affected row?) * @todo do we need a 'force lock' option to forcefully take over spurious locks? * @todo we need to resolve the problem of crashing browsers and locked records * @param int $id the primary key of the record to lock * @param array &$lockinfo returns information about the session that already locked this record * @param string $tablename the name of the table * @param string $pkey name of the field holding the serial (pkey) * @param string $locked_by name of the field to hold our session_id indicating we locked the record * @param string $locked_since name of the field holding the datetime when the lock was obtained * @return bool TRUE if locked succesfully, FALSE on error or already locked ; extra info returned in $lockinfo */ function lock_record($id, &$lockinfo, $tablename, $pkey, $locked_by, $locked_since) { global $DB; $now = strftime("%Y-%m-%d %T"); $lockinfo = array(); if (!isset($_SESSION['session_id'])) { // weird, we SHOULD have a session id logger('weird: no session_id in lock_record()', WLOG_DEBUG); return FALSE; } else { $session_id = $_SESSION['session_id']; if (!is_int($session_id)) { logger('weird: session_id in lock_record() is not an int', WLOG_DEBUG); return FALSE; } } // // 1 -- try to get a lock from scratch (ie: record is currently not locked by anyone) // $fields = array($locked_by => $session_id, $locked_since => $now); $where = array($pkey => intval($id), $locked_by => NULL); $retval = db_update($tablename, $fields, $where); if ($retval === FALSE) { // error return FALSE; } elseif ($retval == 1) { // exactly 1 row was updated, hurray, success! return TRUE; } // else record was probably locked by another session or by our session; go check it out // // 2 -- 1 didn't work, find out who holds the lock // $retval = db_select_single_record($tablename, $locked_by, array($pkey => $id)); if ($retval === FALSE) { return FALSE; // error } elseif ($retval[$locked_by] == $session_id) { return TRUE; // we had the lock ourselves all along } // else record was definately locked by session $retval[$locked_by] // // 3 -- whoever holds the lock, it is not us. check out if 'their' session is still active // $locked_by_session_id = $retval[$locked_by]; $prefixed_tablename = $DB->prefix . $tablename; $sessions_table = $DB->prefix . 'sessions'; $users_table = $DB->prefix . 'users'; $sql = "SELECT s.user_id, u.username, u.full_name, s.user_information, s.ctime, s.atime, x.{$locked_since} AS ltime " . "FROM {$prefixed_tablename} AS x INNER JOIN {$sessions_table} AS s ON x.{$locked_by} = s.session_id " . "LEFT JOIN {$users_table} AS u ON s.user_id = u.user_id " . "WHERE s.session_id = {$locked_by_session_id}"; $DBResult = $DB->query($sql, 1); if ($DBResult === FALSE) { // error return FALSE; } elseif ($DBResult->num_rows == 1) { // alas, already locked by another $lockinfo = $DBResult->fetch_row_assoc(); $DBResult->close(); return FALSE; } else { $DBResult->close(); } // // 4 -- We still have a chance to obtain the lock because the current 'lock' appears // to belong to a session that no longer exists // $fields = array($locked_by => $session_id, $locked_since => $now); $where = array($pkey => intval($id), $locked_by => $locked_by_session_id); $retval = db_update($tablename, $fields, $where); if ($retval === FALSE) { // error return FALSE; } elseif ($retval == 1) { // exactly 1 row was updated, hurray, success! return TRUE; } else { return FALSE; // no joy, give up } }
/** 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 htmlpage_save(&$output, $area_id, $node_id, $module, $viewonly, &$edit_again) { global $USER; if (isset($_POST['button_cancel']) || $viewonly) { $edit_again = FALSE; $retval = FALSE; } else { $edit_again = TRUE; if (!$viewonly) { $where = array('node_id' => intval($node_id)); $order = array('version DESC'); $record = db_select_single_record('htmlpages', '*', $where, $order); if ($record !== FALSE) { $now = strftime('%Y-%m-%d %T'); $fields = array('version' => intval($record['version']) + 1, 'page_data' => magic_unquote($_POST['htmlpage_content']), 'mtime' => $now, 'muser_id' => $USER->user_id); $where = array('htmlpage_id' => $record['htmlpage_id']); $retval = db_update('htmlpages', $fields, $where); } else { $retval = FALSE; } } else { $retval = FALSE; } } return $retval; }
/** display the content of the workshop 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 * @todo FixMe: we need to take parent node permissions into account * as soon as we can assign crew permissions to sections. * We now specificly look at permissions in table acls_modules_nodes * OR at global (guru) permissions for modules in table acls. (June 2013). */ function crew_view(&$theme, $area_id, $node_id, $module) { global $USER; // // 1 -- initialise: check out the visibility of this page // $table = 'workshops'; $fields = array('visibility'); $where = array('node_id' => intval($node_id)); $record = db_select_single_record($table, $fields, $where); if ($record === FALSE) { logger(sprintf('%s(): error retrieving configuration: %s', __FUNCTION__, db_errormessage())); $visibility = 0; } else { $visibility = intval($record['visibility']); } // // 2 -- compute permissions for the current user: 1=READ, 2=WRITE // $module_id = intval($module['module_id']); if (!($visibility == 2 || $visibility == 1 && $USER->is_logged_in || $visibility == 0 && $USER->has_module_node_permissions(1, $module_id, $area_id, $node_id))) { $theme->add_content(t('crew_view_access_denied', 'm_crew')); return TRUE; } // OK. Reading is permitted. How about writing? $writer = $USER->has_module_node_permissions(2, $module_id, $area_id, $node_id); // // 3 -- what do we need to do here? // // If we are still here, we have at least read access. // That means that we always can show the current version of the document // via crew_view_show_view(). Anything else requires the $writer permissions. // If, for some reason, someone attempts to save the document without // having write permissions, we simply can/will discard the POST'ed // document and pretend that the visitor just wants to read the current // version of the document. // // Note that we have a total of 5 variations here. // 1. Initial visit: a simple GET for the current version of the page (for starters) // 2. The user pressed [Edit] from 1. and now sees the popup JS Workshop // 3. The user submitted via [Save] which saves the document and shows 1. again // 4. The user submitted via [SaveEdit] which saves the document and shows 2. again without popup // 5. The user submitted via [Cancel] and now sees 1. again. // Here we go. if ($writer) { if (isset($_POST['button_edit'])) { $retval = crew_view_show_edit($theme, $module_id, TRUE); // TRUE means 1st time; generate pop-up } elseif (isset($_POST['button_saveedit'])) { crew_view_save_document($theme, $node_id); $retval = crew_view_show_edit($theme, $module_id, FALSE); // FALSE means do not generate pop-up } else { if (isset($_POST['button_save'])) { crew_view_save_document($theme, $node_id); } elseif (isset($_POST['button_cancel'])) { $theme->add_message(t('cancelled', 'admin')); } $retval = crew_view_show_view($theme, $writer, $node_id); } } else { $retval = crew_view_show_view($theme, $writer, $node_id); } return $retval; }
/** 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; }
/** determine the number of existing properties for a theme in an area * * @param int $area_id * @param int $theme_id * @return int number of existing properties */ function count_existing_theme_properties($area_id, $theme_id) { $where = array('area_id' => intval($area_id), 'theme_id' => intval($theme_id)); $record = db_select_single_record('themes_areas_properties', 'count(*) as properties', $where); if ($record === FALSE) { logger("areamanager: error counting properties in theme '{$theme_id} and area '{$area_id}': " . db_errormessage()); $num = 0; } else { $num = intval($record['properties']); } return $num; }
/** get pertinent user information in core * * Note: * We used to have a bool named 'high_visibility' in both the users table * and this class. That changed with version 0.90.4 (April 2012) and we now * have a field and variable 'skin' which is a varchar(20). The values were * mapped as follows: high_availability=FALSE -> skin='base' and * high_availability=TRUE -> skin='textonly'. The extra test for the * existence of $record['skin'] was necessary for the case where the user * wanted to upgrade from 0.90.3 to 0.90.4 where 'skin' replaced 'high_visibility'. * * @param int $user_id identifies data from which user to load, 0 means no user/a passerby * @return void */ function Useraccount($user_id = 0) { $user_id = intval($user_id); $this->user_id = $user_id; if ($this->user_id == 0) { // just a passerby gets no privileges return FALSE; } // Now try to fetch data for this user from database $fields = '*'; $record = db_select_single_record('users', $fields, array('user_id' => $user_id, 'is_active' => TRUE)); if ($record === FALSE) { logger('useraccount: cannot find record for user_id \'' . $user_id . '\'', WLOG_INFO, $user_id); return FALSE; } $this->username = $record['username']; $this->full_name = $record['full_name']; $this->email = $record['email']; $this->language_key = $record['language_key']; $this->path = $record['path']; $this->editor = $record['editor']; $this->skin = isset($record['skin']) ? $record['skin'] : 'base'; // see note above // Prepare for retrieval of acls/permissions $this->acl_id = intval($record['acl_id']); $this->related_acls = calc_user_related_acls($user_id); // Always fetch the site-wide permissions from acls table (others are cached on demand) $fields = array_keys($this->acls); $where = $this->where_acl_id(); $records = db_select_all_records('acls', $fields, $where); if ($records === FALSE) { logger('useraccount: cannot find acls records for user_id \'' . $user_id . '\'', WLOG_INFO, $user_id); } else { foreach ($records as $record) { foreach ($fields as $field) { $this->acls[$field] |= $record[$field]; } } } // get all properties for this user in 2D array (sections, entries) $tablename = 'users_properties'; $fields = array('section', 'name', 'type', 'value'); $where = array('user_id' => $user_id); $order = array('section', 'name'); $records = db_select_all_records($tablename, $fields, $where, $order); if ($records !== FALSE) { $properties = array(); foreach ($records as $rec) { $properties[$rec['section']][$rec['name']] = convert_to_type($rec['type'], $rec['value']); } $this->properties = $properties; } return TRUE; }
/** retrieve page data for htmlpage module on a node * * this retrieves data from the page $node_id (requires knowledge of * internals of htmlpage module/table) in order to show that in a box * in the 3rd column. * The content is wrapped in a div which has a unique ID of the form * sidebar_blockN where N counts from 1 upward and a common class * sidebar_htmlpage. This allows for connecting style information to * a particular sidebar block in a flexible way. * * @param in $index block number (1-based) * @param int $node_id identifies the pagedata to retrieve * @param string $m increased readbility * @return string ready-to-use HTML-code */ function cornelia_get_sidebar_htmlpage($index, $node_id, $m = '') { $attributes = array('id' => 'sidebar_block' . strval($index), 'class' => 'sidebar_htmlpage'); $s = $m . html_tag('div', $attributes) . "\n"; // fetch actual content from database (requires knowledge of internals of htmlpage module/table) $table = 'htmlpages'; $node_id = intval($node_id); $where = array('node_id' => $node_id); $fields = array('page_data'); if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf('%s: no pagedata (node=%d): %s', __FUNCTION__, $node_id, db_errormessage())); } else { $s .= $record['page_data'] . "\n"; } $s .= $m . "</div>\n"; return $s; }
/** present the user with a dialog to modify the redirect that is connected to node $node_id * * this prepares a dialog for the user filled with existing data (if any), possibly allowing * the user to modify the content. If the flag $viewonly is TRUE, this routine should only * display the content rather than let the user edit it. If the flag $edit_again is TRUE, * the routine should use the data available in the $_POST array, otherwise it should read * the data from the database (or wherever the data comes from). The parameter $href is the * place where the form should be POST'ed. * * The dialog should be added to the $output object. Useful routines are: * <code> * $output->add_content($content): add $content to the content area * $output->add_message($message): add $message to the message area (feedback to the user) * $output->add_popup_bottom($message): make $message popup in the browser after loading the page (uses javascript) * $output->add_popup_top($message): make $message popup in the browser before loading the page (uses javascript) * </code> * * @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 this module is connected * @param array $module the module record straight from the database * @param bool $viewonly if TRUE, editing is not allowed (but simply showing the content is allowed) * @param bool $edit_again if TRUE start with data from $_POST, else use data from database * @param string $href the action property of the HTML-form, the place where data will be POST'ed * @return bool TRUE on success + output stored via $output, FALSE otherwise */ function redirect_show_edit(&$output, $area_id, $node_id, $module, $viewonly, $edit_again, $href) { $dialogdef = redirect_get_dialogdef($viewonly); if ($edit_again) { // retrieve and validate the POSTed values dialog_validate($dialogdef); // no need to show messages; we did that alread in redirect_save() bel0w } else { // make a fresh start with data from the database $table = 'nodes'; $fields = array('link_href', 'link_target'); $where = array('node_id' => intval($node_id)); if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf('%s(): error retrieving node record for redirect configuration: %s', __FUNCTION__, db_errormessage())); } else { foreach ($dialogdef as $name => $item) { switch ($name) { case 'link_href': case 'link_target': $dialogdef[$name]['value'] = strval($record[$name]); break; } } } } $output->add_content('<h2>' . t('redirect_content_header', 'm_redirect') . '</h2>'); $output->add_content(t('redirect_content_explanation', 'm_redirect')); $output->add_content(dialog_quickform($href, $dialogdef)); return TRUE; }
/** construct a dialog definition for the workshop configuration * * this generates an array which defines the dialog for workshop configuration. * There are a few plain fields that simply go into the appropriate workshops * record and the save and cancel button. However, there may also be items * related to ACLs. These fields are used to define the user's roles and the * should be presented in a table. We abuse the field names for this purpose: * if the first 3 characters are 'acl' we put the widgets in an HTML-table, otherwise * it is just an ordinary widget. * * Note that in the case of a single simple user without any aquaintances (ie. a user * that is not member of any group) the user is not able to add herself to the list * of authorised users. What is the point of having a _collaborative_ workshop when * you are the only one to collaborate with? (However, it would be fairly easy to force/add * an entry for this user if the tmp table would turn out empty. Maybe later....?) * @param object &$output collects the html output (if any) * @param int $viewonly if TRUE the Save button is not displayed and values cannot be changed * @param int $module_id indicates the id of the crew module in the database (needed for ACL) * @param int $area_id indicates the area where node_id lives (needed for ACL) * @param int $node_id indicates which page we are loooking at (needed for ACL) * @param int $user_id indicates the current user (needed for ACL) * @return array dialog definition */ function crew_get_dialogdef(&$output, $viewonly, $module_id, $area_id, $node_id, $user_id) { global $DB, $USER; static $dialogdef = NULL; if (!is_null($dialogdef)) { // recycle return $dialogdef; } $visibilities = array('2' => array('option' => t('visibility_world_label', 'm_crew'), 'title' => t('visibility_world_title', 'm_crew')), '1' => array('option' => t('visibility_all_label', 'm_crew'), 'title' => t('visibility_all_title', 'm_crew')), '0' => array('option' => t('visibility_workers_label', 'm_crew'), 'title' => t('visibility_workers_title', 'm_crew'))); $roles = array(ACL_ROLE_NONE => array('option' => t('acl_role_none_option', 'admin'), 'title' => t('acl_role_none_title', 'admin')), CREW_ACL_ROLE_READONLY => array('option' => t('crew_acl_role_readonly_option', 'm_crew'), 'title' => t('crew_acl_role_readonly_title', 'm_crew')), CREW_ACL_ROLE_READWRITE => array('option' => t('crew_acl_role_readwrite_option', 'm_crew'), 'title' => t('crew_acl_role_readwrite_title', 'm_crew')), ACL_ROLE_GURU => array('option' => t('acl_role_guru_option', 'admin'), 'title' => t('acl_role_guru_title', 'admin'))); // 1 -- plain & simple fields // make a fresh start with data from the database $dialogdef = array('header' => array('type' => F_ALPHANUMERIC, 'name' => 'header', 'minlength' => 0, 'maxlength' => 240, 'columns' => 30, 'label' => t('header_label', 'm_crew'), 'title' => t('header_title', 'm_crew'), 'viewonly' => $viewonly, 'value' => '', 'old_value' => ''), 'introduction' => array('type' => F_ALPHANUMERIC, 'name' => 'introduction', 'minlength' => 0, 'maxlength' => 32768, 'columns' => 50, 'rows' => 10, 'label' => t('introduction_label', 'm_crew'), 'title' => t('introduction_title', 'm_crew'), 'viewonly' => $viewonly, 'value' => '', 'old_value' => ''), 'visibility' => array('type' => F_RADIO, 'name' => 'visibility', 'value' => 0, 'old_value' => 0, 'options' => $visibilities, 'viewonly' => $viewonly, 'title' => t('visibility_title', 'm_crew'), 'label' => t('visibility_label', 'm_crew'))); $table = 'workshops'; $fields = array('header', 'introduction', 'visibility'); $where = array('node_id' => intval($node_id)); if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf('%s(): error retrieving CREW configuration: %s', __FUNCTION__, db_errormessage())); $output->add_message(t('error_retrieving_data', 'admin')); } else { foreach ($record as $name => $value) { $dialogdef[$name]['value'] = $dialogdef[$name]['old_value'] = $value; } } $sql = sprintf('DROP TEMPORARY TABLE IF EXISTS %screw_tmp', $DB->prefix); $retval = $DB->exec($sql); if ($USER->has_job_permissions(JOB_PERMISSION_ACCOUNTMANAGER)) { // Allow $USER to set/edit any user's permission because she is already able // to manipulate useraccounts, _all_ useraccounts. We are sure that $USER is a // valid user with at least JOB_PERMISSION_STARTCENTER or else we would not be here. $sql = sprintf('CREATE TEMPORARY TABLE %screw_tmp ' . 'SELECT u.acl_id, u.username, u.full_name, amn.permissions_modules ' . 'FROM %susers u ' . 'LEFT JOIN %sacls_modules_nodes amn ' . 'ON amn.acl_id = u.acl_id AND amn.module_id = %d AND amn.node_id = %d ' . 'ORDER BY u.full_name', $DB->prefix, $DB->prefix, $DB->prefix, $module_id, $node_id); } else { // Only allow $USER to set permissions for all her acquaintances, ie. all users // that are members of the group(s) that $USER is a also member of. $sql = sprintf('CREATE TEMPORARY TABLE %screw_tmp ' . 'SELECT DISTINCT u.acl_id, u.username, u.full_name, amn.permissions_modules ' . 'FROM %susers u ' . 'INNER JOIN %susers_groups_capacities ugc1 USING(user_id) ' . 'INNER JOIN %susers_groups_capacities ugc2 USING(group_id) ' . 'LEFT JOIN %sacls_modules_nodes amn ' . 'ON amn.acl_id = u.acl_id AND amn.module_id = %d AND amn.node_id = %d ' . 'WHERE ugc2.user_id = %d ' . 'ORDER BY u.full_name', $DB->prefix, $DB->prefix, $DB->prefix, $DB->prefix, $DB->prefix, $module_id, $node_id, $user_id); } $retval = $DB->exec($sql); // at this point we have a temporary table with all 'editable' accounts // we first add those to the dialogdef. $table = 'crew_tmp'; $fields = '*'; $where = ''; $order = array('full_name', 'username'); if (($records = db_select_all_records($table, $fields, $where, $order)) === FALSE) { logger(sprintf('%s(): error retrieving elegible CREW-members: %s', __FUNCTION__, db_errormessage())); $output->add_message(t('error_retrieving_data', 'admin')); } else { foreach ($records as $record) { $acl_id = intval($record['acl_id']); $name = 'acl_rw_' . $acl_id; $dialogdef[$name] = array('type' => F_LISTBOX, 'name' => $name, 'value' => is_null($record['permissions_modules']) ? 0 : $record['permissions_modules'], 'old_value' => $record['permissions_modules'], 'acl_id' => $acl_id, 'options' => $roles, 'viewonly' => $viewonly, 'title' => $record['username'], 'label' => $record['full_name']); } } // the next step is to generate a list of any OTHER accounts that happen to have // permissions for this module on this node other than ACL_ROLE_NONE. // This list consists of a few UNIONs that effectively yields all accounts that // somehow have a non-0 permissions_modules, either global (acls), any node for // this module (acls_modules), any node within this area (acls_modules_area), // any node that is an ancestor of node_id (acls_modules_nodes) OR this specific // node for a user that is NOT an acquaintance (ie. who is not in the temp table). // Note that we don't check the ancestors (parents) when node happens to be at // the top level within the area, ie. when parent is 0. We also peek inside // 'acls_areas' and 'acls_nodes'. Pfew, complicated... // All these OTHER accounts cannot be manipulated by $USER because all accounts // would then be in the temp table, so there. // Since there may be more records for the same user (or rather acl_id), we need // to drill down the results. As all permissions are additive we can simply OR // these together per acl_id/user which yields a single combined role for that user. $tree = tree_build($area_id); $ancestors = array(); for ($next_id = $tree[$node_id]['parent_id']; $next_id; $next_id = $tree[$next_id]['parent_id']) { $ancestors[] = $next_id; } unset($tree); $sql = empty($ancestors) ? '' : sprintf('SELECT u.acl_id, u.username, u.full_name, amn.permissions_modules ' . 'FROM %susers u INNER JOIN %sacls_modules_nodes amn USING (acl_id) ' . 'WHERE amn.permissions_modules <> 0 AND amn.module_id = %d AND amn.node_id IN (%s)', $DB->prefix, $DB->prefix, $module_id, join(',', $ancestors)) . ' UNION ' . sprintf('SELECT u.acl_id, u.username, u.full_name, an.permissions_modules ' . 'FROM %susers u INNER JOIN %sacls_nodes an USING (acl_id) ' . 'WHERE an.permissions_modules <> 0 AND an.node_id IN (%s)', $DB->prefix, $DB->prefix, join(',', $ancestors)) . ' UNION '; $sql .= sprintf('SELECT u.acl_id, u.username, u.full_name, a.permissions_modules ' . 'FROM %susers u INNER JOIN %sacls a USING (acl_id) ' . 'WHERE a.permissions_modules <> 0', $DB->prefix, $DB->prefix) . ' UNION ' . sprintf('SELECT u.acl_id, u.username, u.full_name, am.permissions_modules ' . 'FROM %susers u INNER JOIN %sacls_modules am USING (acl_id) ' . 'WHERE am.permissions_modules <> 0 AND am.module_id = %d', $DB->prefix, $DB->prefix, $module_id) . ' UNION ' . sprintf('SELECT u.acl_id, u.username, u.full_name, aa.permissions_modules ' . 'FROM %susers u INNER JOIN %sacls_areas aa USING (acl_id) ' . 'WHERE aa.permissions_modules <> 0 AND aa.area_id = %d', $DB->prefix, $DB->prefix, $area_id) . ' UNION ' . sprintf('SELECT u.acl_id, u.username, u.full_name, ama.permissions_modules ' . 'FROM %susers u INNER JOIN %sacls_modules_areas ama USING (acl_id) ' . 'WHERE ama.permissions_modules <> 0 AND ama.module_id = %d AND ama.area_id = %d', $DB->prefix, $DB->prefix, $module_id, $area_id) . ' UNION ' . sprintf('SELECT u.acl_id, u.username, u.full_name, an.permissions_modules ' . 'FROM %susers u INNER JOIN %sacls_nodes an USING (acl_id) ' . 'WHERE an.permissions_modules <> 0 AND an.node_id = %d', $DB->prefix, $DB->prefix, $node_id) . ' UNION ' . sprintf('SELECT u.acl_id, u.username, u.full_name, amn.permissions_modules ' . 'FROM %susers u INNER JOIN %sacls_modules_nodes amn USING (acl_id) ' . 'LEFT JOIN %screw_tmp tmp USING(acl_id) ' . 'WHERE amn.permissions_modules <> 0 AND amn.module_id = %d AND amn.node_id = %d ' . 'AND tmp.acl_id IS NULL ', $DB->prefix, $DB->prefix, $DB->prefix, $module_id, $node_id) . 'ORDER BY full_name, acl_id'; if (($result = $DB->query($sql)) === FALSE) { logger(sprintf('%s(): error retrieving other account names: %s', __FUNCTION__, db_errormessage())); $output->add_message(t('error_retrieving_data', 'admin')); } else { if ($result->num_rows > 0) { $records = array(); while (($record = $result->fetch_row_assoc()) !== FALSE) { $acl_id = intval($record['acl_id']); if (isset($records[$acl_id])) { $records[$acl_id]['permissions_modules'] |= intval($record['permissions_modules']); } else { $records[$acl_id] = $record; } } $result->close(); foreach ($records as $acl_id => $record) { $name = 'acl_ro_' . $acl_id; $dialogdef[$name] = array('type' => F_LISTBOX, 'name' => $name, 'value' => is_null($record['permissions_modules']) ? 0 : $record['permissions_modules'], 'options' => $roles, 'viewonly' => TRUE, 'title' => $record['username'], 'label' => $record['full_name']); } } } if (!$viewonly) { $dialogdef['button_save'] = dialog_buttondef(BUTTON_SAVE); } $dialogdef['button_cancel'] = dialog_buttondef(BUTTON_CANCEL); return $dialogdef; }
/** read the (serialised) session data from the database * * @param string the unique session_key that identifies the session * @return string empty string on failure, existing session data otherwise */ function dbsession_read($session_key) { $retval = db_select_single_record('sessions', 'session_data', array('session_key' => $session_key)); if ($retval === FALSE) { return ''; } // updating the atime of the session extends the session lifetime, // but a failure while updating will be ignored (it's not _that_ important) $current_time = strftime('%Y-%m-%d %T'); db_update('sessions', array('atime' => $current_time), array('session_key' => $session_key)); return (string) $retval['session_data']; }
/** quick and dirty logfile viewer * * this constructs a table of the HTML-variety with the contents of the logtable. * fields displayed are: datim, IP-address, username, logpriority and message * we use a LEFT JOIN in order to get to a meaningful username rather than a numeric user_id * an attempt is made to start with the last page of the logs because that would probably * be the most interesting part. We paginate the log in order to keep it manageable. * * <Rant><br> * I used to use the built-in constants like LOG_INFO and LOG_DEBUG to allow for different levels * of logging (see {@link logger()}). To my complete surprise logging didn't work at all on * Windows (it did on Linux). The reason was that LOG_DEBUG and LOG_INFO and LOG_NOTICE are all * defined to be the same value. WTF? Any test based on LOG_DEBUG and LOG_INFO being different * would fail, hence no logging at all. The mind boggles! So, instead of using built-in constants * I had to define my own and do a global search&replace. Aaarghhhhhh!!!!<br> * </Rant><br> * * @param object &$output collects output to show to user * @return void output displayed via $output * @todo should we allow for fancy selection mechanisms on the logfile or is that over the top? */ function task_logview(&$output) { global $CFG, $WAS_SCRIPT_NAME, $DB; static $priorities = array(WLOG_EMERG => 'LOG_EMERG', WLOG_ALERT => 'LOG_ALERT', WLOG_CRIT => 'LOG_CRIT', WLOG_ERR => 'LOG_ERR', WLOG_WARNING => 'LOG_WARNING', WLOG_NOTICE => 'LOG_NOTICE', WLOG_INFO => 'LOG_INFO', WLOG_DEBUG => 'LOG_DEBUG'); // 0 -- at least we allow the user to navigate away if something goes wrong $output->set_helptopic('logview'); show_tools_menu($output, TASK_LOGVIEW); // 1A -- how many messages are there anyway? $table = 'log_messages'; $where = ''; // could be used to select per user, per priority, etc. For now: always select everything if (($record = db_select_single_record($table, 'COUNT(log_message_id) AS messages', $where)) === FALSE) { $output->add_content('<h2>' . t('menu_logview', 'admin') . '</h2>'); $output->add_content(t('logview_error', 'admin')); $output->add_message(t('logview_error', 'admin')); logger(sprintf('%s(): cannot retrieve log message count: %s', __FUNCTION__, db_errormessage())); return; } // 1B -- if there are no message we leave if (($num_messages = intval($record['messages'])) < 1) { $output->add_content('<h2>' . t('menu_logview', 'admin') . '</h2>'); $output->add_content(t('logview_no_messages', 'admin')); $output->add_message(t('logview_no_messages', 'admin')); logger(sprintf('%s(): no messages to show', __FUNCTION__), WLOG_DEBUG); return; } // 2 -- which part of the logs do they want to see? (calculate/retrieve offset and limit) $limit = get_parameter_int('limit', $CFG->pagination_height); $limit = max(1, $limit); // make sure 1 <= $limit $offset = intval(floor($num_messages / $limit)) * $limit; // attempt to start at begin of LAST page $offset = get_parameter_int('offset', max($offset, 0)); $offset = max(min($num_messages - 1, $offset), 0); // make sure 0 <= $offset < $num_messages // 3 -- show the pagination in the page header (if necessary) if ($num_messages <= $limit && $offset == 0) { // listing fits on a single screen $header = '<h2>' . t('menu_logview', 'admin') . '</h2>'; } else { // pagination necessary, tell user where we are $param = array('{FIRST}' => strval($offset + 1), '{LAST}' => strval(min($num_messages, $offset + $limit)), '{TOTAL}' => strval($num_messages)); $header = '<h2>' . t('menu_logview', 'admin') . ' ' . t('pagination_count_of_total', 'admin', $param) . '</h2>'; $parameters = array('job' => JOB_TOOLS, 'task' => TASK_LOGVIEW); $output->add_pagination($WAS_SCRIPT_NAME, $parameters, $num_messages, $limit, $offset, $CFG->pagination_width); } // 4 -- retrieve the selected messages (including optional username via LEFT JOIN) $sql = sprintf('SELECT l.datim, l.remote_addr, l.priority, l.user_id, u.username, l.message ' . 'FROM %slog_messages l LEFT JOIN %susers u USING (user_id) ' . 'ORDER BY l.datim, l.log_message_id', $DB->prefix, $DB->prefix); if (($DBResult = $DB->query($sql, $limit, $offset)) === FALSE) { $output->add_message(t('logview_error', 'admin')); logger(sprintf('%s(): cannot retrieve log messages: %s', __FUNCTION__, db_errormessage())); return; } $records = $DBResult->fetch_all_assoc(); $DBResult->close(); // 5A -- setup a table with a header $index = $offset + 1; $output->add_content($header); $class = 'header'; $attributes = array('class' => $class, 'align' => 'right'); $output->add_content('<p>'); $output->add_content(html_table(array('cellpadding' => '3'))); $output->add_content(' ' . html_table_row($attributes)); $output->add_content(' ' . html_table_head($attributes, t('logview_nr', 'admin'))); $attributes['align'] = 'left'; $output->add_content(' ' . html_table_head($attributes, t('logview_datim', 'admin'))); $output->add_content(' ' . html_table_head($attributes, t('logview_remote_addr', 'admin'))); $output->add_content(' ' . html_table_head($attributes, t('logview_user_id', 'admin'))); $output->add_content(' ' . html_table_head($attributes, t('logview_priority', 'admin'))); $output->add_content(' ' . html_table_head($attributes, t('logview_message', 'admin'))); $output->add_content(' ' . html_table_row_close()); // 5B -- step through the recordset and dump into the table foreach ($records as $record) { $class = $class == 'odd' ? 'even' : 'odd'; $priority = isset($priorities[$record['priority']]) ? $priorities[$record['priority']] : strval(intval($record['priority'])); $attributes = array('class' => $class); $output->add_content(' ' . html_table_row($attributes)); $attributes['align'] = 'right'; $output->add_content(' ' . html_table_cell($attributes, strval($index++))); $attributes['align'] = 'left'; $output->add_content(' ' . html_table_cell($attributes, htmlspecialchars($record['datim']))); $output->add_content(' ' . html_table_cell($attributes, htmlspecialchars($record['remote_addr']))); $output->add_content(' ' . html_table_cell($attributes, htmlspecialchars($record['username']))); $output->add_content(' ' . html_table_cell($attributes, $priority)); $output->add_content(' ' . html_table_cell($attributes, htmlspecialchars($record['message']))); $output->add_content(' ' . html_table_row_close()); } // 5C -- all done $output->add_content(html_table_close()); $output->add_content('<p>'); }
/** main program for serving files * * this routine is called from /file.php. * * This routine is responsible for serving files to the visitor. * These files are stored in a (virtual) file hierarchy that looks * like this. * * <pre> * /areas/areaname * /another * /stillmore * ... * /users/username * /another * /stillmore * ... * /groups/groupname * /another * /stillmore * ... * /websiteatschool/program * /manual * /languages * </pre> * * This structure maps to the real file system as follows. The (virtual) * directories /areas, /users and /groups correspond to the fysical * directories {$CFG->datadir}/areas, {$CFG->datadir}/users and * {$CFG->datadir}/groups respectively. The subdirectories correspond to * a (unique) area, user or group and serve as a file repository for that * area, user or group. * * The (virtual) top-level directory /websiteatschool is a special case. * It is used to serve the currently running website program code and the * user-defined translations of active languages. * * Before any file is transmitted to the visitor the access privileges * are checked. The following rules apply. * * Access control for the /areas subdirectory * * - an area must be active before any files are served * - the visitor must have access to the private area if files are to be served * - non-existing files yield a 404 Not Found error * - non-existing areas also yield a 404 Not Found error * - if the visitor has no access to the private area, also a 404 Not Found error is returned * * Access control for /users and /groups * * - a user/group must be active before any files are served * - non-existing users/groups yield 404 Not Found * - non-existing files in existing directories also yield 404 Not Found * * Access control for /websiteatschool * * - there is no limit on downloading the currently active program code or user-defined translations of active languages * * Note: * The check on '..' in the requested filename would be inconclusive if the $path * is encoded in invalid UTF-8: the overlong sequence 2F C0 AE 2E 2F eventually * yields 2F 2E 2E 2F or '/../'. Reference: RFC3629 section 10. However, we use * the filename processed with get_requested_filename() which already checks for * utf8 validity, which rules out the trick with overlong sequences. * * @return void file sent to the browser OR 404 not found on error */ function main_file() { global $USER; global $CFG; global $WAS_SCRIPT_NAME; global $LANGUAGE; /** initialise the program, setup database, read configuration, etc. */ require_once $CFG->progdir . '/init.php'; initialise(); was_version_check(); // this never returns if versions don't match /** utility routines for manipulating files */ require_once $CFG->progdir . '/lib/filelib.php'; $filename = get_requested_filename(); if (is_null($filename)) { error_exit404(); } // 0 -- is the visitor logged in if (isset($_COOKIE[$CFG->session_name])) { /** dbsessionlib.php contains our own database based session handler */ require_once $CFG->progdir . '/lib/dbsessionlib.php'; dbsession_setup($CFG->session_name); if (dbsession_exists(magic_unquote($_COOKIE[$CFG->session_name]))) { session_start(); if (!isset($_SESSION['session_counter'])) { // first time after login, record start time of session $_SESSION['session_counter'] = 1; $_SESSION['session_start'] = strftime("%Y-%m-%d %T"); } else { $_SESSION['session_counter']++; } } } /** useraccount.class.php is used to define the USER object */ require_once $CFG->progdir . '/lib/useraccount.class.php'; if (isset($_SESSION) && isset($_SESSION['user_id'])) { $USER = new Useraccount($_SESSION['user_id']); $USER->is_logged_in = TRUE; $_SESSION['language_key'] = $LANGUAGE->get_current_language(); // remember language set via _GET or otherwise session_write_close(); // we no longer need this here, everything relevant is now in $USER } else { $USER = new Useraccount(); $USER->is_logged_in = FALSE; } // // 1 -- does the visitor want to download the source code // $path_components = explode('/', trim(strtr($filename, '\\', '/'), '/')); if (strtolower($path_components[0]) == 'websiteatschool') { $source = isset($path_components[1]) ? strtolower($path_components[1]) : 'program'; download_source($source); exit; } // // 2 -- no source code requested. check out regular files // $path = '/' . implode('/', $path_components); // 2A -- always disallow attempts to escape from tree via parent directory tricks if (in_array('..', $path_components)) { logger(sprintf("%s(): access denied for file '%s': no tricks with '/../': return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } // 2B -- check the 1st and 2nd component of the requested file switch ($path_components[0]) { case 'areas': $area_path = isset($path_components[1]) ? $path_components[1] : ''; $fields = array('area_id', 'is_private'); $where = array('is_active' => TRUE, 'path' => $area_path); $table = 'areas'; if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf("%s(): access denied for file '%s': non-existing or inactive area: return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } $area_id = intval($record['area_id']); if (db_bool_is(TRUE, $record['is_private']) && !$USER->has_intranet_permissions(ACL_ROLE_INTRANET_ACCESS, $area_id)) { logger(sprintf("%s(): access denied for file '%s' in private area '%d': return 404 Not Found", __FUNCTION__, $path, $area_id), WLOG_DEBUG); error_exit404($path); } break; case 'users': $user_path = isset($path_components[1]) ? $path_components[1] : ''; $fields = array('user_id'); $where = array('path' => $user_path, 'is_active' => TRUE); $table = 'users'; if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf("%s(): access denied for file '%s': non-existing or inactive user: return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } break; case 'groups': $group_path = isset($path_components[1]) ? $path_components[1] : ''; $fields = array('group_id'); $where = array('path' => $group_path, 'is_active' => TRUE); $table = 'groups'; if (($record = db_select_single_record($table, $fields, $where)) === FALSE) { logger(sprintf("%s(): access denied for file '%s': non-existing or inactive group: return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } break; default: logger(sprintf("%s(): access denied for file '%s': subdirectory '%s' not recognised: return 404 Not Found", __FUNCTION__, $path, $path_components[0]), WLOG_DEBUG); error_exit404($path); break; } // 2C -- still here? 1st and 2nd components are good but does the file exist? if (!is_file($CFG->datadir . $path)) { logger(sprintf("%s(): access denied for file '%s': file does not exist: return 404 Not Found", __FUNCTION__, $path), WLOG_DEBUG); error_exit404($path); } // // At this point we confident that the file exists within the data directory and also that // the visitor is allowed access to the file. Now send the file to the visitor. // $name = basename($path); if (($bytes_sent = send_file_from_datadir($path, $name)) === FALSE) { logger(sprintf("Failed to send '%s' using filename '%s'", $path, $name)); $retval = FALSE; } else { logger(sprintf("Success sending '%s' using filename '%s', size = %d bytes", $path, $name, $bytes_sent), WLOG_DEBUG); $retval = TRUE; } exit; }
/** display the content of the sitemap linked to node $node_id * * there are three different variations (depends on configuration parameter 'scope'): * * - 0 (small): only show a map of the tree in the current area $area_id * - 1 (medium): show a list of available areas followed by the map of the current area $area_id * - 2 (large): show the maps of all available areas * * The default is 0 (small). * * @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 sitemap_view(&$theme, $area_id, $node_id, $module) { global $USER; // // 1 -- determine scope of sitemap: 0=small, 1=medium, 2=large // $table = 'sitemaps'; $fields = array('header', 'introduction', 'scope'); $where = array('node_id' => intval($node_id)); $record = db_select_single_record($table, $fields, $where); if ($record === FALSE) { logger(sprintf('%s(): error retrieving configuration: %s', __FUNCTION__, db_errormessage())); $scope = 0; $header = ''; $introduction = ''; } else { $scope = intval($record['scope']); $header = trim($record['header']); $introduction = trim($record['introduction']); } // // 2 -- compute a list of areas to process (could be just 1) // // 2A -- retrieve all areas, including those out of bounds for this user if (($all_areas = get_area_records()) === FALSE) { logger(sprintf('%s(): huh? cannot get area records: %s', __FUNCTION__, db_errormessage())); return FALSE; // shouldn't happen } // 2B -- narrow down the selection (active, (private) access allowed, within scope) $areas = array(); foreach ($all_areas as $id => $area) { if (db_bool_is(TRUE, $area['is_active']) && (db_bool_is(FALSE, $area['is_private']) || $USER->has_intranet_permissions(ACL_ROLE_INTRANET_ACCESS, $id))) { if ($scope == 2 || $scope == 1 || $scope == 0 && $id == $area_id) { $title = $area['title']; $params = array('area' => $id); $href = was_node_url(NULL, $params, $title, $theme->preview_mode); $areas[$id] = html_a($href, NULL, NULL, $title); } } } unset($all_areas); // $areas now holds all areas that we should to process if (sizeof($areas) <= 0) { logger(sprintf('%s(): weird, no areas to process; bailing out', __FUNCTION__)); return FALSE; // shouldn't happen } // // 3 -- maybe output a header and an introduction // if (!empty($header)) { $theme->add_content('<h2>' . $header . '</h2>'); } if (!empty($introduction)) { $theme->add_content($introduction); } // // 4 - Actually output a sitemap by walking the tree once for every elegible area // foreach ($areas as $id => $area_anchor) { if ($scope == 1 && $area_id != $id) { // 1=medium only shows area map of $area_id (and an area list lateron) continue; } // 4A -- output a clickable area title $theme->add_content('<h2>' . $area_anchor . '</h2>'); // 4B -- fetch the tree for this area... $tree = tree_build($id); tree_visibility($tree[0]['first_child_id'], $tree); // 4C -- ...and walk the tree sitemap_tree_walk($theme, $tree[0]['first_child_id'], $tree); unset($tree); } if ($scope == 1) { $theme->add_content('<h2>' . t('sitemap_available_areas', 'm_sitemap') . '</h2>'); $theme->add_content('<ul>'); foreach ($areas as $id => $area_anchor) { $theme->add_content('<li>' . $area_anchor); } $theme->add_content('</ul>'); } return TRUE; // indicate success }
/** 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; }