/** 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
}
/** 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;
}
/** main program for visitors
 *
 * this routine is called from /index.php. It is the main program for visitors.
 *
 * @return void page sent to the browser
 * @todo cleanup login/logout-code
 */
function main_index()
{
    global $USER;
    global $CFG;
    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
    // TODO: cleanup like in main_admin()
    // handle login/logout/continuation so we quickly find out which user is calling
    if (isset($_GET['logout'])) {
        /** loginlib.php contains both login- and logout-routines */
        require_once $CFG->progdir . '/lib/loginlib.php';
        was_logout();
        // may or may not return here
    } elseif (isset($_GET['login'])) {
        /** loginlib.php contains both login- and logout-routines */
        require_once $CFG->progdir . '/lib/loginlib.php';
        was_login(magic_unquote($_GET['login']));
        // may or may not return here
    } elseif (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();
        }
    }
    // At this point we either have a valid session with a logged-in user
    // (indicated via existence of $_SESSION) or we are dealing with an anonymous
    // visitor with non-existing $_SESSION. Keep track of the number of calls
    // this user makes (may be logged lateron on logout).
    if (isset($_SESSION)) {
        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']++;
        }
    }
    // Now is the time to create a USER object, even when the visitor is just a passerby
    // because we can then determine easily if a visitor is allowed certain things, e.g.
    // view a protected area or something
    /** 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
    } else {
        $USER = new Useraccount();
        $USER->is_logged_in = FALSE;
    }
    // Check for the special preview-mode
    // This allows a webmaster to preview a page in the correct environment (theme)
    // even when the page is under embargo. Note that the node_id and area_id are
    // retrieved from the session; the user only has a cryptic preview-code.
    // See pagemanagerlib.php for more information (function task_page_preview()).
    $in_preview_mode = FALSE;
    if ($USER->is_logged_in) {
        $preview_code_from_url = get_parameter_string('preview');
        if (!is_null($preview_code_from_url) && isset($_SESSION['preview_salt']) && isset($_SESSION['preview_node'])) {
            $hash = md5($_SESSION['preview_salt'] . $_SESSION['preview_node']);
            if ($hash === $preview_code_from_url) {
                $node_id = intval($_SESSION['preview_node']);
                $area_id = intval($_SESSION['preview_area']);
                $area = db_select_single_record('areas', '*', array('area_id' => $area_id));
                if ($area === FALSE) {
                    logger("Fatal error 070: cannot preview node '{$node_id}' in area '{$area_id}'");
                    error_exit('070');
                } else {
                    $tree = tree_build($area_id);
                    $in_preview_mode = TRUE;
                }
            }
        }
    }
    if ($in_preview_mode == FALSE) {
        $requested_area = get_requested_area();
        $requested_node = get_requested_node();
        $req_area_str = is_null($requested_area) ? "NULL" : strval($requested_area);
        $req_node_str = is_null($requested_node) ? "NULL" : strval($requested_node);
        if (($area = calculate_area($requested_area, $requested_node)) === FALSE) {
            logger("Fatal error 080: no valid area (request: area='{$req_area_str}', node='{$req_node_str}')");
            error_exit('080');
            // no such area
        }
        $area_id = intval($area['area_id']);
        // If $USER has no permission to view area $area_id, we simply bail out.
        // Rationale: if the user is genuine, she knows about logging in first.
        // If the user is NOT logged in and tries to view a protected area, I'd consider
        // it malicious, and in that case I won't even confirm the existence of
        // the requested area. (If a cracker simply tries areas 0,1,.. and sometimes is greeted
        // with 'please enter credentials' and sometimes with 'area does not exist', this
        // provides information to the cracker. I don't want that). Note that the error code
        // is the same as the one for non-existing area. In other words: for an unauthorised
        // visitor an existing private area is just as non-existent as a non-existing public area.
        if (db_bool_is(TRUE, $area['is_private']) && !$USER->has_intranet_permissions(ACL_ROLE_INTRANET_ACCESS, $area_id)) {
            logger(sprintf("Fatal error 080: no view permissions for area '%d' (request: area='%s', node='%s')", $area_id, $req_area_str, $req_node_str));
            error_exit('080');
            // no such area
        }
        // still here?
        // then we've got a valid $area_id and corresponding $area record.
        // now we need to figure out which $node_id to use
        $tree = tree_build($area_id);
        if (($node_id = calculate_node_id($tree, $area_id, $requested_node)) === FALSE) {
            logger(sprintf("Fatal error 080: no valid node within area '%d' (request: area='%s', node='%s')", $area_id, $req_area_str, $req_node_str));
            error_exit('080');
            // no such area
        }
    }
    // At this point we have the following in our hands
    // - a valid $area_id
    // - a valid $node_id
    // - the complete tree from area $area_id in $tree
    // - the area record from database in $area
    // - the node record from database in $tree[$node_id]['record']
    // - a flag that signals preview mode in $in_preview_mode
    // We are on our way to generate a full page with content and all,
    // but otoh we MIGHT be in the middle of a redirect, so we may have to
    // leave without showing anything at all...
    if (!empty($tree[$node_id]['record']['link_href'])) {
        update_statistics($node_id);
        if (isset($_SESSION)) {
            session_write_close();
        }
        redirect_and_exit(htmlspecialchars($tree[$node_id]['record']['link_href']));
        // exit; redirect_and_exit() never returns
    }
    /** themelib contains the theme factory */
    require_once $CFG->progdir . '/lib/themelib.php';
    // And now we know about the $area, we can carry on determining which $theme to use.
    //
    $theme = theme_factory($area['theme_id'], $area_id, $node_id);
    if ($theme === FALSE) {
        logger("Fatal error 090: cannot setup theme '{$area['theme_id']}' in area '{$area_id}'");
        error_exit('090');
    }
    // Tell the theme about the preview mode
    $theme->set_preview_mode($in_preview_mode);
    // Now all we need to do is let the module connected to node $node_id generate output
    $module_id = $tree[$node_id]['record']['module_id'];
    module_view($theme, $area_id, $node_id, $module_id);
    // Remember this visitor
    update_statistics($node_id);
    // Finally, send output to user
    $theme->send_output();
    if (isset($_SESSION)) {
        session_write_close();
    }
    // done!
    exit;
}
 /** calculate an ordered list of filenames to try for translation of phrases
  *
  * WAS uses a separate language file for every text domain;
  * basically the name of the text domain is the name of the
  * file without the .php-extension. However, in order to prevent
  * name clashes, modules and themes and addons have their own 
  * prefix: 'm_' for modules and 't_' for themes and 'a_' for
  * addons.
  *
  * The language translations for the installer are based on more or
  * less the same trick: the prefix 'i_' identifies files in the
  * directory /program/install/languages.
  *
  * This trick with prefixing leads to the following search orders
  * for generic phrases and module-, theme- and addon-specific phrases.
  *
  * Example 1: phrases with $domain='login':
  * {$CFG->progdir}/languages/{$language_key}/login.php
  * {$CFG->datadir}/languages/{$language_key}/login.php
  *
  * Example 2: phrases with $domain='m_guestbook':
  * {$CFG->progdir}/modules/guestbook/languages/{$language_key}/guestbook.php
  * {$CFG->progdir}/languages/{$language_key}/m_guestbook.php
  * {$CFG->datadir}/languages/{$language_key}/m_guestbook.php
  *
  * Example 3: phrases with $domain='login' and a hint in $location_hint:
  * {$location_hint}/{$language_key}/login.php
  * {$CFG->datadir}/languages/{$language_key}/login.php
  *
  * Example 4: phrases with $domain='m_guestbook' and a hint in $location_hint:
  * {$location_hint}/{$language_key}/guestbook.php
  * {$location_hint}/{$language_key}/m_guestbook.php
  * {$CFG->datadir}/languages/{$language_key}/m_guestbook.php
  *
  * Example 5: phrases with $domain='i_demodata':
  * {$CFG->progdir}/install/languages/{$language_key}/demodata.php
  * {$CFG->datadir}/languages/{$language_key}/i_demodata.php
  *
  * @param string $full_domain indicates the text domain including optional module/theme/addon prefix
  * @param string $location_hint hints at a location of language file(s)
  * @param string $language_key target language
  * @return array an ordered list of filenames
  * @uses $CFG
  */
 function get_filenames_to_try($full_domain, $location_hint, $language_key)
 {
     global $CFG;
     static $extensions = array('m_' => 'modules', 't_' => 'themes', 'a_' => 'addons');
     $filenames = array();
     // Minimal validation of location hint: this directory should at least exist
     $directory = !empty($location_hint) && is_dir($location_hint) ? $location_hint : '';
     // Modules/themes/addons have a 2-char prefix in their full_domain.
     // If this is the case, we start with a language path as close to the
     // module/theme/addon as possible _or_ in the hint location.
     $prefix = substr($full_domain, 0, 2);
     $domain = substr($full_domain, 2);
     if (isset($extensions[$prefix])) {
         $extension = $extensions[$prefix];
         if (empty($directory)) {
             $filenames[] = $CFG->progdir . '/' . $extension . '/' . $domain . '/languages/' . $language_key . '/' . $domain . '.php';
         } else {
             $filenames[] = $directory . '/' . $language_key . '/' . $domain . '.php';
         }
     } elseif ($prefix == 'i_') {
         if (empty($directory)) {
             $filenames[] = $CFG->progdir . '/install/languages/' . $language_key . '/' . $domain . '.php';
         } else {
             $filenames[] = $directory . '/' . $language_key . '/' . $domain . '.php';
         }
     }
     if (empty($directory)) {
         $filenames[] = $CFG->progdir . '/languages/' . $language_key . '/' . $full_domain . '.php';
     } else {
         $filenames[] = $directory . '/' . $language_key . '/' . $full_domain . '.php';
     }
     // If this language has the flag 'dialect_in_file' set, we try $CFG->datadir/languages too
     if (db_bool_is(TRUE, $this->languages[$language_key]['dialect_in_file'])) {
         $filenames[] = $CFG->datadir . '/languages/' . $language_key . '/' . $full_domain . '.php';
     }
     return $filenames;
 }
 /** construct a clickable icon to set the default area
  *
  * the 'default' icon is displayed for the default area, the
  * 'non-default' icon for all others. The user is allowed to make the
  * area the default area if the user has edit permissions for both the
  * old and the new default area.
  *
  * @param int $area_id 
  * @param array &$areas records with area information of all areas
  * @return string ready-to-use A-tag
  * @uses $CFG
  * @uses $USER
  * @uses $WAS_SCRIPT_NAME
  */
 function get_icon_home($area_id, &$areas)
 {
     global $CFG, $WAS_SCRIPT_NAME, $USER;
     // 1A -- check out permissions: we need them for this area AND the current default area
     $user_has_permission = FALSE;
     if ($USER->has_area_permissions(PERMISSION_AREA_EDIT_AREA, $area_id)) {
         $user_has_permission = TRUE;
         foreach ($areas as $area) {
             if (db_bool_is(TRUE, $area['is_default'])) {
                 $user_has_permission = $USER->has_area_permissions(PERMISSION_AREA_EDIT_AREA, $area['area_id']);
                 break;
             }
         }
     }
     // 1B -- construct a title/mouseover depending on permissions/current default area
     if (!$user_has_permission) {
         $title = t('icon_area_default_access_denied', 'admin');
     } elseif (db_bool_is(TRUE, $areas[$area_id]['is_default'])) {
         $title = t('icon_area_is_default', 'admin');
     } else {
         $title = t('icon_area_default', 'admin');
     }
     // 2 -- construct the icon (image or text)
     if (db_bool_is(TRUE, $areas[$area_id]['is_default'])) {
         $icon = 'startsection';
         $alt = t('icon_area_default_alt', 'admin');
         $text = t('icon_area_default_text', 'admin');
     } else {
         $icon = 'not_startsection';
         $alt = t('icon_area_not_default_alt', 'admin');
         $text = t('icon_area_not_default_text', 'admin');
     }
     $anchor = $this->output->skin->get_icon($icon, $title, $alt, $text);
     // 3 -- construct the A tag
     $a_params = $this->a_param(AREAMANAGER_CHORE_SET_DEFAULT, $area_id);
     $a_attr = array('name' => 'area' . strval($area_id), 'title' => $title);
     if (!$user_has_permission) {
         $a_attr['class'] = 'dimmed';
     }
     return html_a($WAS_SCRIPT_NAME, $a_params, $a_attr, $anchor);
 }
/** retrieve an array with node records straight from the database
 *
 * this routine constructs a list of 0, 1 or more node records based
 * on the $node_id provided by the caller. The node records are
 * retrieved from the database.
 *
 * This routine takes care of the showstoppers like embargo and
 * expiry and also access permissions to the area. We can not
 * be sure if the user actually has access to a page until we
 * are have checked to area in which the node $node_id resides.
 * This is an extra test compared to 
 * {@link aggregator_view_get_node_from_tree()} above.
 *
 * @param int $node_id identifies page or section to evaluate
 * @param array &$config points to the aggregator configuration
 * @param array &$modules points to array with supported modules
 * @return array ordered list of nodes to aggregate (could be empty)
 */
function aggregator_view_get_node_from_db($node_id, &$config, &$modules)
{
    global $USER;
    $nodes = array();
    $table = 'nodes';
    $fields = '*';
    $order = $config['reverse_order'] ? 'sort_order DESC' : 'sort_order';
    $where = array('node_id' => intval($node_id));
    if (($record = db_select_single_record($table, $fields, $where)) === FALSE) {
        logger(sprintf('%s(): error retrieving node record: %s', __FUNCTION__, db_errormessage()));
        return $nodes;
    }
    $now = strftime("%Y-%m-%d %T");
    // don't show expired nodes or nodes under embargo
    if ($now < $record['embargo'] || $record['expiry'] < $now) {
        return $nodes;
    }
    // don't show private or inactive areas to random strangers
    $areas = get_area_records();
    $area_id = intval($record['area_id']);
    if (db_bool_is(FALSE, $areas[$area_id]['is_active']) || db_bool_is(TRUE, $areas[$area_id]['is_private']) && !$USER->has_intranet_permissions(ACL_ROLE_INTRANET_ACCESS, $area_id)) {
        return $nodes;
    }
    // if it was but a plain page we're done (even if not htmlpage or snapshots)
    if (db_bool_is(TRUE, $record['is_page'])) {
        $module_id = intval($record['module_id']);
        if (isset($modules[$module_id])) {
            $nodes[] = $record;
        }
        return $nodes;
    }
    // mmm, must have been a section (but at least in the correct area)
    // go get the pages in this section in this area
    $where = array('parent_id' => $node_id, 'area_id' => $area_id, 'is_page' => TRUE);
    if (($records = db_select_all_records($table, $fields, $where, $order)) === FALSE) {
        logger(sprintf('%s(): error retrieving node records: %s', __FUNCTION__, db_errormessage()));
        return $nodes;
    }
    $counter = 0;
    foreach ($records as $record) {
        // don't show expired nodes or nodes under embargo
        if ($now < $record['embargo'] || $record['expiry'] < $now) {
            continue;
        }
        $module_id = intval($record['module_id']);
        if (isset($modules[$module_id])) {
            $nodes[] = $record;
            if (++$counter >= $config['items']) {
                break;
            }
        }
    }
    return $nodes;
}
 /** build a tree of all nodes in an area
  *
  * this routine constructs a tree-structure of all nodes in area $area_id in much
  * the same way as {@link tree_build()} does. However, in this routine we keep the
  * cargo limited to a minimum: the fields we retrieve from the nodes table and
  * store in the tree are:
  *  - node_id
  *  - parent_id
  *  - is_page
  *  - title
  *  - link_text
  *  - module_id
  * Also, the tree is not cached because that does not make sense here: we only
  * use it to construct a dialogdef and that is a one-time operation too.
  *
  * @parameter int $area_id the area for which to build the tree
  * @param int $acl_id the primary acl_id (used for both users and groups)
  * @param array|null $related_acls an array with related acls for this user keyed by 'acl_id' or NULL for group acls
  * @return array ready to use tree structure w/ permissions
  */
 function tree_build($area_id)
 {
     // 1 -- Start with 'special' node 0 is root of the tree
     $tree = array(0 => array('node_id' => 0, 'parent_id' => 0, 'prev_sibling_id' => 0, 'next_sibling_id' => 0, 'first_child_id' => 0, 'is_page' => FALSE, 'title' => '', 'link_text' => '', 'module_id' => 0));
     $where = array('area_id' => intval($area_id));
     $order = array('CASE WHEN (parent_id = node_id) THEN 0 ELSE parent_id END', 'sort_order', 'node_id');
     $fields = array('node_id', 'parent_id', 'is_page', 'title', 'link_text', 'module_id');
     $records = db_select_all_records('nodes', $fields, $where, $order, 'node_id');
     // 2 -- step through all node records and copy the relevant fields
     if ($records !== FALSE) {
         foreach ($records as $record) {
             $node_id = intval($record['node_id']);
             $parent_id = intval($record['parent_id']);
             $is_page = db_bool_is(TRUE, $record['is_page']);
             if ($parent_id == $node_id) {
                 // top level
                 $parent_id = 0;
             }
             $tree[$node_id] = array('node_id' => $node_id, 'parent_id' => $parent_id, 'prev_sibling_id' => 0, 'next_sibling_id' => 0, 'first_child_id' => 0, 'is_page' => $is_page, 'title' => $record['title'], 'link_text' => $record['link_text'], 'module_id' => intval($record['module_id']));
         }
     }
     unset($records);
     // free memory
     // 3 -- step through all collected records and add links to childeren and siblings
     $prev_node_id = 0;
     $sort_order = 0;
     foreach ($tree as $node_id => $node) {
         $parent_id = $node['parent_id'];
         if (!isset($tree[$parent_id])) {
             logger("aclmanager: node '{$node_id}' is an orphan because parent '{$parent_id}' does not exist in tree[]");
         } elseif ($parent_id == $tree[$prev_node_id]['parent_id']) {
             $tree[$prev_node_id]['next_sibling_id'] = $node_id;
             $tree[$node_id]['prev_sibling_id'] = $prev_node_id;
         } else {
             $tree[$parent_id]['first_child_id'] = $node_id;
         }
         $prev_node_id = $node_id;
     }
     // 4 -- 'root node' 0 is a special case, the top level nodes are in fact childeren, not siblings
     $tree[0]['first_child_id'] = $tree[0]['next_sibling_id'];
     $tree[0]['next_sibling_id'] = 0;
     // 5 -- done!
     return $tree;
 }
 /** generate a list of areas for use in a dropdown list (for moving a node to another area)
  *
  * this creates an array containing a list of areas to which the user
  * is allowed to move a node. Permissions for 
  * moving a node is a combination of permissions for deleting a node from the
  * current area, and adding a node to the target area. The current area 
  * $this->area_id is always in the list, because even if the user isn't
  * allowed to move a node to somewhere else, she is at least allowed to leave
  * the node in the area it currently is in. Therefore the option for the
  * current area MUST be possible.
  *
  * We sepcifically check for these permissions:
  * PERMISSION_AREA_ADD_PAGE or PERMISSION_AREA_ADD_SECTION
  * and not PERMISSION_NODE_ADD_PAGE or PERMISSION_NODE_ADD_SECTION
  * because the target of the move is always the top level, and not some
  * (sub)section. 
  *
  * @param int $node_id the node for which we are building this picklist
  * @param bool $is_page TRUE if this concerns a page, FALSE for a section
  * @return array ready for use as an options array in a listbox or radiobuttons
  */
 function get_options_area($node_id, $is_page)
 {
     global $USER;
     $options = array();
     $can_drop_current = $this->permission_delete_node($node_id, $is_page);
     $permissions = $is_page ? PERMISSION_AREA_ADD_PAGE : PERMISSION_AREA_ADD_SECTION;
     foreach ($this->areas as $area_id => $area) {
         if ($this->area_id == $area_id || $can_drop_current && $USER->has_area_permissions($permissions, $area_id)) {
             $is_active = db_bool_is(TRUE, $area['is_active']);
             $a_area = array('{AREA}' => $area_id, '{AREANAME}' => $area['title']);
             if (db_bool_is(TRUE, $area['is_private'])) {
                 $option_text = t($is_active ? 'options_private_area' : 'options_private_area_inactive', 'admin', $a_area);
             } else {
                 $option_text = t($is_active ? 'options_public_area' : 'options_public_area_inactive', 'admin', $a_area);
             }
             $options[$area_id] = array('option' => $option_text, 'title' => $area['title'], 'class' => 'level0');
         }
     }
     return $options;
 }
Ejemplo n.º 9
0
/** construct a zipfile with the current source and stream it to the visitor
 *
 * this routine streams a ZIP-archive to the visitor with either the current
 * websiteatschool program code or the selected manual. This routine is necessary
 * to comply with the provisions of the program license which basically says that the
 * source code of the running program must be made available.
 *
 * Note that it is not strictly necessary to also provide the manual, but this
 * routine can do that nevertheless.
 *
 * Note that we take special care not to download the (private) data directory
 * $CFG->datadir. Of course the datadirectory should live outside the document
 * root and certainly outside the /program directory tree, but accidents will
 * happen and we don't want to create a gaping security hole.
 *
 * If there are severe errors (e.g. no manual is available for download or an invalid
 * component was specified) the program exist immediately with a 404 not found error.
 * Otherwise the ZIP-archive is streamed to the user. If all goes well, we return TRUE,
 * if there were errors we immediately return TRUE (without finishing the task at hand
 * other than a perhasp futile attempt to properly close the  ZIP-archive). The last
 * error message from the Zip is logged.
 *
 * @param string $component either 'program' or 'manual' or 'languages'
 * @return void|bool Exit with 404 not found, OR TRUE and generated ZIP-file sent to user OR FALSE on error
 * @uses download_source_tree()
 */
function download_source($component)
{
    global $CFG;
    global $LANGUAGE;
    $time_start = microtime();
    //
    // Step 0 -- decide what needs to be done
    //
    switch ($component) {
        case 'program':
            $files = array('index.php', 'admin.php', 'cron.php', 'file.php', 'config-example.php');
            $directories = array('program' => $CFG->progdir);
            $excludes = array(realpath($CFG->datadir), realpath($CFG->progdir . '/manuals'));
            $archive = 'websiteatschool-program.zip';
            break;
        case 'manual':
            $language = $LANGUAGE->get_current_language();
            $manuals = $CFG->progdir . '/manuals';
            if (!is_dir($manuals . '/' . $language)) {
                if (!is_dir($manuals . '/en')) {
                    logger(sprintf("Failed download 'websiteatschool/manual/%s': 404 Not Found", $language));
                    error_exit404("websiteatschool/{$component}");
                } else {
                    $language = 'en';
                }
            }
            $files = array();
            $directories = array('program/manuals/' . $language => $manuals . '/' . $language);
            $excludes = array(realpath($CFG->datadir));
            $archive = sprintf('websiteatschool-manual-%s.zip', $language);
            break;
        case 'languages':
            $files = array();
            $directories = array();
            $excludes = array();
            $archive = 'websiteatschool-languages.zip';
            $languages = $LANGUAGE->retrieve_languages();
            foreach ($languages as $language_key => $language) {
                if (db_bool_is(TRUE, $language['is_active']) && db_bool_is(TRUE, $language['dialect_in_file'])) {
                    $directories['languages/' . $language_key] = $CFG->datadir . '/languages/' . $language_key;
                }
            }
            if (sizeof($directories) < 1) {
                logger(sprintf("Failed download websiteatschool/%s': 404 Not Found", $component));
                error_exit404('websiteatschool/' . $component);
            }
            break;
        default:
            logger(sprintf("Failed download websiteatschool/%s': 404 Not Found", $component));
            error_exit404('websiteatschool/' . $component);
            break;
    }
    //
    // Step 1 -- setup Ziparchive
    //
    include_once $CFG->progdir . '/lib/zip.class.php';
    $zip = new Zip();
    $comment = $CFG->www;
    if (!$zip->OpenZipstream($archive, $comment)) {
        $elapsed = diff_microtime($time_start, microtime());
        logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
        return FALSE;
    }
    //
    // Step 2 -- add files in the root directory (if any)
    //
    if (sizeof($files) > 0) {
        foreach ($files as $file) {
            $path = $CFG->dir . '/' . $file;
            if (!file_exists($path)) {
                logger(sprintf("%s(): missing file '%s' in archive '%s'", __FUNCTION__, $path, $archive), WLOG_DEBUG);
                $data = sprintf('<' . '?' . 'php echo "%s: file was not found"; ?' . '>', $file);
                $comment = sprintf('%s: missing', $file);
                if (!$zip->AddData($data, $file, $comment)) {
                    $elapsed = diff_microtime($time_start, microtime());
                    logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
                    $zip->CloseZip();
                    return FALSE;
                }
            } else {
                if (!$zip->AddFile($path, $file)) {
                    $elapsed = diff_microtime($time_start, microtime());
                    logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
                    $zip->CloseZip();
                    return FALSE;
                }
            }
        }
    }
    //
    // Step 3 -- add directories to archive
    //
    foreach ($directories as $vpath => $path) {
        if (!download_source_tree($zip, $path, $vpath, $excludes)) {
            $elapsed = diff_microtime($time_start, microtime());
            logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
            $zip->CloseZip();
            return FALSE;
        }
    }
    //
    // Step 4 -- we're done
    //
    if (!$zip->CloseZip()) {
        $elapsed = diff_microtime($time_start, microtime());
        logger(sprintf("Failed download '%s' (%0.2f seconds): %s", $archive, $elapsed, $zip->Error));
        return FALSE;
    }
    logger(sprintf("Download '%s' (%0.2f seconds): success", $archive, diff_microtime($time_start, microtime())));
    return TRUE;
}
 /** construct a Theme object
  *
  * this stores the information about this theme from the database.
  * Also, we construct/read the tree of nodes for this area $area_id. This
  * information will be used lateron when constructing the navigation.
  * The node to display is $node_id.
  *
  * Also, we prepare a list of areas where the current user is allowed to go.
  * This is handy when constructing a jumpmenu and doing it here saves a trip
  * to the database lateron in {@link get_jumpmenu()}.
  *
  * @param array $theme_record the record straight from the database
  * @param int $area_id the area of interest
  * @param int $node_id the node that will be displayed
  * @return void
  */
 function Theme($theme_record, $area_id, $node_id)
 {
     global $USER, $CFG;
     $charset = 'UTF-8';
     $content_type = 'text/html; charset=' . $charset;
     $this->add_http_header('Content-Type: ' . $content_type);
     $this->theme_record = $theme_record;
     $this->theme_id = intval($theme_record['theme_id']);
     $this->area_id = intval($area_id);
     $this->jumps = array();
     // extract areas information and
     //  - grab a copy of the full area_record 'for future reference', and
     //  - make a list of areas accessible for this user (for the area jumpmenu)
     if (($areas = get_area_records()) !== FALSE) {
         $this->area_record = $areas[$this->area_id];
         foreach ($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))) {
                 $this->jumps[$id] = $area['title'];
             }
         }
     } else {
         $this->area_record = array('area_id' => $this->area_id, 'title' => '?');
         logger(sprintf('constructor %s(): cannot get list of areas: %s', __FUNCTION__, db_errormessage()), WLOG_DEBUG);
     }
     $this->node_id = intval($node_id);
     $this->tree = $this->construct_tree($this->area_id);
     $this->node_record = $this->tree[$node_id]['record'];
     $this->config = $this->get_properties($this->theme_id, $this->area_id);
     $this->add_meta_http_equiv(array('Content-Type' => $content_type, 'Content-Script-Type' => 'text/javascript', 'Content-Style-Type' => 'text/css'));
     $this->title = $this->area_record['title'];
     $this->add_meta(array('MSSmartTagsPreventParsing' => 'TRUE', 'generator' => 'Website@School', 'description' => 'Website@School Content Management System for schools', 'keywords' => 'Website@School, CMS for schools'));
     $this->calc_breadcrumb_trail($node_id);
     // only set markers in tree, don't collect anchors yet
     $this->domain = 't_' . $this->theme_record['name'];
     // indicates where to look for translations
     if (isset($this->config['style_usage_static']) && $this->config['style_usage_static'] && isset($this->config['stylesheet'])) {
         $this->add_stylesheet($this->config['stylesheet']);
     }
 }
Ejemplo n.º 11
0
/** check the user's credentials in one of three ways
 *
 * This authenticates the user's credentials. There are some variants:
 *
 *  - by password: the user's password should match
 *  - by email: the user's email should match
 *  - by laissez passer: the one-time authentication code should match
 *
 * Strategy: we first read the active record for user $username in core.
 * If there is none, the user does not exist or is inactive => return FALSE.
 *
 * After that we check the validity of the token:
 *
 *  - a password is checked via the password hash or, if that fails, via the
 *    bypass hash. In the latter case, the bypass should not yet be expired
 *    (a bypass and a laissez_passer are valid until the 'bypass_expiry' time).
 *
 *  - an email address is checked caseINsensitive and without leading/trailing spaces
 *
 *  - a laissez_passer is check much the same way as the bypass password, be it
 *    that the code is stored 'as-is' rather than as a hash. The comparison is
 *    caseINsensitive.
 *
 * If the credentials are considered valid, an array with the user record
 * is returned, otherwise FALSE is returned.
 *
 * Because there are actually several checks to be done, we decided not to use SQL 
 * like: SELECT * FROM users WHERE username=$username AND password=$password,
 * not the least because we need to have the salt in our hands before we can
 * successfully compare password hashes.
 *
 * Note:
 * The 'special cases' (checking email, checking laissez_passer, checking bypass)
 * all have their token stripped from leading and trailing spaces. We don't want
 * to further confuse the user by not accepting a spurious space that was entered
 * in the heat of the moment when the user has 'lost' her password. Therefore we
 * also always trim the username. Rationale: usernames and also the generated
 * passwords etc. never have leading/trailing spaces. However, one cannot be sure
 * that a user has not entered a real password with leading/trailing space, so we
 * do NOT trim the $token in the first attempt in the case 'BY_PASSWORD' below.
 *
 * @param int $by_what_token which authentication token to use
 * @param string $username username the user entered in the dialog
 * @param string $token the token is either password, email or laissez_passer entered by the user
 * @return bool|array FALSE if invalid credentials, array with user record otherwise
 */
function authenticate_user($by_what_token, $username, $token)
{
    $u = db_select_single_record('users', '*', array('username' => trim($username), 'is_active' => TRUE));
    if ($u === FALSE) {
        return FALSE;
    }
    switch ($by_what_token) {
        case BY_PASSWORD:
            if (password_hash_check($u['salt'], $token, $u['password_hash'])) {
                // no trim() on password, see above
                return $u;
            } elseif (db_bool_is(TRUE, $u['bypass_mode'])) {
                $expiry = string2time($u['bypass_expiry']);
                if ($expiry != FALSE) {
                    if (time() < $expiry && password_hash_check($u['salt'], trim($token), $u['bypass_hash'])) {
                        return $u;
                    }
                }
            }
            break;
        case BY_EMAIL:
            if (strcasecmp(trim($token), trim($u['email'])) == 0) {
                return $u;
            }
            break;
        case BY_LAISSEZ_PASSER:
            $expiry = string2time($u['bypass_expiry']);
            if ($expiry != FALSE) {
                if (time() < $expiry && strcmp(trim($token), trim($u['bypass_hash'])) == 0) {
                    return $u;
                }
            }
            break;
        default:
            trigger_error('internal error: unknown value for \'by_what_token\'');
            break;
    }
    return FALSE;
}
 /** construct the edit group dialog
  *
  * @param int $group_id the group that will be edited
  * @return array|bool FALSE on errors retrieving data, otherwise array containing the dialog definition
  */
 function get_dialogdef_edit_group($group_id)
 {
     $group_id = intval($group_id);
     // 1A -- retrieve data from groups-record
     if (($group = $this->get_group_record($group_id)) === FALSE) {
         return FALSE;
     }
     // 1B -- retrieve the available capacities for this group (could be 0)
     $capacities = db_select_all_records('groups_capacities', '*', array('group_id' => $group_id), 'sort_order');
     if ($capacities === FALSE) {
         return FALSE;
     }
     // 2 -- construct dialog definition including current values from database
     $dialogdef = array('dialog' => array('type' => F_INTEGER, 'name' => 'dialog', 'value' => GROUPMANAGER_DIALOG_EDIT, 'hidden' => TRUE), 'group_name' => array('type' => F_ALPHANUMERIC, 'name' => 'group_name', 'minlength' => 1, 'maxlength' => 60, 'columns' => 30, 'label' => t('groupmanager_edit_group_name_label', 'admin'), 'title' => t('groupmanager_edit_group_name_title', 'admin'), 'value' => $group['groupname'], 'old_value' => $group['groupname']), 'group_fullname' => array('type' => F_ALPHANUMERIC, 'name' => 'group_fullname', 'minlength' => 1, 'maxlength' => 255, 'columns' => 30, 'label' => t('groupmanager_edit_group_fullname_label', 'admin'), 'title' => t('groupmanager_edit_group_fullname_title', 'admin'), 'value' => $group['full_name'], 'old_value' => $group['full_name']), 'group_is_active' => array('type' => F_CHECKBOX, 'name' => 'group_is_active', 'options' => array(1 => t('groupmanager_edit_group_is_active_check', 'admin')), 'label' => t('groupmanager_edit_group_is_active_label', 'admin'), 'title' => t('groupmanager_edit_group_is_active_title', 'admin'), 'value' => db_bool_is(TRUE, $group['is_active']) ? '1' : '', 'old_value' => db_bool_is(TRUE, $group['is_active']) ? '1' : ''));
     $options = $this->get_options_capacities();
     for ($i = 1; $i <= GROUPMANAGER_MAX_CAPACITIES; ++$i) {
         if (isset($capacities[$i - 1])) {
             $value = intval($capacities[$i - 1]['capacity_code']);
             $sort_order = intval($capacities[$i - 1]['sort_order']);
             $acl_id = intval($capacities[$i - 1]['acl_id']);
         } else {
             $value = CAPACITY_NONE;
             $sort_order = 0;
             $acl_id = 0;
         }
         $dialogdef['group_capacity_' . $i] = array('type' => F_LISTBOX, 'name' => 'group_capacity_' . $i, 'value' => $value, 'old_value' => $value, 'label' => t('groupmanager_edit_group_capacity_label', 'admin', array('{INDEX}' => strval($i))), 'title' => t('groupmanager_edit_group_capacity_title', 'admin'), 'options' => $options, 'sort_order' => $sort_order, 'acl_id' => $acl_id);
     }
     $dialogdef['group_path'] = array('type' => F_ALPHANUMERIC, 'name' => 'group_path', 'minlength' => 1, 'maxlength' => 60, 'columns' => 30, 'label' => t('groupmanager_edit_group_path_label', 'admin'), 'title' => t('groupmanager_edit_group_path_title', 'admin'), 'value' => $group['path'], 'old_value' => $group['path'], 'viewonly' => TRUE);
     $dialogdef['button_save'] = dialog_buttondef(BUTTON_SAVE);
     $dialogdef['button_cancel'] = dialog_buttondef(BUTTON_CANCEL);
     return $dialogdef;
 }
Ejemplo n.º 13
0
/** construct a tree of nodes in memory
 *
 * this reads the nodes in the specified area from disk and
 * constructs a tree via linked lists (sort of).
 * If parameter $force is TRUE, the data is read from the database,
 * otherwise a cached version is returned (if available).
 *
 * Note that this routine also 'repairs' the tree when an orphan is
 * detected. The orphan is automagically moved to the top of the area.
 * Of course, it shouldn't happen, but if it does we are better off
 * with a magically _appearing_ orphan than a _disappearing_ node.
 *
 * A lot of operations in the page manager work with a tree of nodes in
 * some way, e.g. walking the tree and displaying it or walking the
 * tree and collecting the sections (but not the pages), etc.
 *
 * The tree starts with a 'root' with key 0 ($tree[0]). This is the starting
 * point of the tree. The nodes at the top level of an area are linked from
 * this root node via the field 'first_child_id'. If there are no nodes
 * in the area, this field 'first_child_id' is zero. The linked list is
 * constructed by using the node_id. All nodes in an area are collected in
 * an array. This array us used to construct the linked lists.
 *
 * Every node has a parent (via 'parent_id'), where the nodes at the top level
 * have a parent_id of zero; this points to the 'root'. The nodes within a section
 * or at the top level are linked forward via 'next_sibling_id' and backward
 * via 'prev_sibling_id'. A zero indicates the end of the list. Childeren start
 * with 'first_child_id'. A value of zero means: no childeren.
 *
 * The complete node record from the database is also stored in the tree.
 * This is used extensively throughout the pagemanager; it acts as a cache for
 * all nodes in an area. 
 *
 * Note that we cache the node records per area. If two areas are involved,
 * the cache doesn't work very well anymore. However, this doesn't happen very
 * often; only in case of moving nodes from one area to another (and even then).
 *
 * @param int $area_id the area to make the tree for
 * @param bool $force if TRUE forces reread from database (resets the cache)
 * @return array contains a 'root node' 0 plus all nodes from the requested area if any
 * @todo what if we need the trees of two different areas?
 *       should the static var here be an array, keyed by area_id?
 * @todo repairing a node doesn't really belong here, in this routine.
 *       we really should have a separate 'database repair tool' for this purpose.
 *       someday we'll fix this....
 */
function tree_build($area_id, $force = FALSE)
{
    global $DB;
    static $tree = NULL;
    static $cached_area_id = 0;
    if ($tree !== NULL && !$force && $area_id == $cached_area_id) {
        return $tree;
    }
    // 1 -- Start with 'special' node 0 is root of the tree
    $tree = array(0 => array('node_id' => 0, 'parent_id' => 0, 'prev_sibling_id' => 0, 'next_sibling_id' => 0, 'first_child_id' => 0, 'is_hidden' => FALSE, 'is_page' => FALSE, 'record' => array()));
    $where = array('area_id' => intval($area_id));
    $order = array('CASE WHEN (parent_id = node_id) THEN 0 ELSE parent_id END', 'sort_order', 'node_id');
    $records = db_select_all_records('nodes', '*', $where, $order, 'node_id');
    // 2 -- step through all node records and copy the relevant fields + integral record too
    if ($records !== FALSE) {
        foreach ($records as $record) {
            $node_id = intval($record['node_id']);
            $parent_id = intval($record['parent_id']);
            $is_hidden = db_bool_is(TRUE, $record['is_hidden']);
            $is_page = db_bool_is(TRUE, $record['is_page']);
            $is_default = db_bool_is(TRUE, $record['is_default']);
            if ($parent_id == $node_id) {
                // top level
                $parent_id = 0;
            }
            $tree[$node_id] = array('node_id' => $node_id, 'parent_id' => $parent_id, 'prev_sibling_id' => 0, 'next_sibling_id' => 0, 'first_child_id' => 0, 'is_hidden' => $is_hidden, 'is_page' => $is_page, 'is_default' => $is_default, 'record' => $record);
        }
    }
    unset($records);
    // free memory
    // 3 -- step through all collected records and add links to childeren and siblings
    $prev_node_id = 0;
    $sort_order = 0;
    foreach ($tree as $node_id => $node) {
        $parent_id = $node['parent_id'];
        if (!isset($tree[$parent_id])) {
            // obviously this shouldn't happen but if it does, we DO log it and we do something about it!
            // Note: changes will be effective the NEXT time the tree is read from database.
            logger("pagemanager: node '{$node_id}' was orphaned because parent '{$parent_id}' does not exist; fixed");
            $sort_order -= 10;
            // insert orphans in reverse order at the top level
            $fields = array('parent_id' => $node_id, 'sort_order' => $sort_order);
            $where = array('node_id' => $node_id);
            $sql = db_update_sql('nodes', $fields, $where);
            logger("tree_build(): moved orphan '{$node_id}' (original parent '{$parent_id}') to top with '{$sql}'", WLOG_DEBUG);
            $DB->exec($sql);
        } elseif ($parent_id == $tree[$prev_node_id]['parent_id']) {
            $tree[$prev_node_id]['next_sibling_id'] = $node_id;
            $tree[$node_id]['prev_sibling_id'] = $prev_node_id;
        } else {
            $tree[$parent_id]['first_child_id'] = $node_id;
        }
        $prev_node_id = $node_id;
    }
    // 4 -- 'root node' 0 is a special case, the top level nodes are in fact childeren, not siblings
    $tree[0]['first_child_id'] = $tree[0]['next_sibling_id'];
    $tree[0]['next_sibling_id'] = 0;
    // 5 -- done!
    $cached_area_id = $area_id;
    return $tree;
}
/** display an introductory text for the account manager + menu
 *
 * @param object &$output collects the html output
 * @return void results are returned as output in $output
 */
function show_accounts_intro(&$output)
{
    global $DB;
    $tables = array('users' => array('pkey' => 'user_id', 'active' => 0, 'inactive' => 0, 'total' => 0), 'groups' => array('pkey' => 'group_id', 'active' => 0, 'inactive' => 0, 'total' => 0));
    $output->add_content('<h2>' . t('accountmanager_header', 'admin') . '</h2>');
    $output->add_content(t('accountmanager_intro', 'admin'));
    $class = 'header';
    $output->add_content('<p>');
    $output->add_content(html_table());
    $output->add_content('  ' . html_table_row(array('class' => $class)));
    $output->add_content('    ' . html_table_head(NULL, t('accountmanager_summary', 'admin')));
    $output->add_content('    ' . html_table_head(NULL, t('accountmanager_active', 'admin')));
    $output->add_content('    ' . html_table_head(NULL, t('accountmanager_inactive', 'admin')));
    $output->add_content('    ' . html_table_head(NULL, t('accountmanager_total', 'admin')));
    $output->add_content('  ' . html_table_row_close());
    foreach ($tables as $table_name => $table) {
        $class = $class == 'odd' ? 'even' : 'odd';
        $sql = sprintf("SELECT is_active, COUNT(%s) AS total FROM %s%s GROUP BY is_active", $table['pkey'], $DB->prefix, $table_name);
        if (($DBResult = $DB->query($sql)) !== FALSE) {
            $records = $DBResult->fetch_all_assoc();
            $DBResult->close();
            foreach ($records as $record) {
                $key = db_bool_is(TRUE, $record['is_active']) ? 'active' : 'inactive';
                $tables[$table_name][$key] += intval($record['total']);
                $tables[$table_name]['total'] += intval($record['total']);
            }
        }
        $attributes = array('class' => $class);
        $output->add_content('  ' . html_table_row($attributes));
        $output->add_content('    ' . html_table_cell($attributes, t('accountmanager_' . $table_name, 'admin')));
        $attributes['align'] = 'right';
        $output->add_content('    ' . html_table_cell($attributes, $tables[$table_name]['active']));
        $output->add_content('    ' . html_table_cell($attributes, $tables[$table_name]['inactive']));
        $output->add_content('    ' . html_table_cell($attributes, $tables[$table_name]['total']));
        $output->add_content('  ' . html_table_row_close());
    }
    $output->add_content(html_table_close());
}
 /** construct the edit user dialog
  *
  * @param int $user_id indicates which user to edit
  * @return bool|array FALSE on error or array with dialog definition and existing data from database
  * @uses $LANGUAGE
  */
 function get_dialogdef_edit_user($user_id)
 {
     global $LANGUAGE;
     $user_id = intval($user_id);
     // 1 -- retrieve data from users-record
     if (($user = $this->get_user_record($user_id)) === FALSE) {
         return FALSE;
     }
     $params = array('{MIN_LENGTH}' => MINIMUM_PASSWORD_LENGTH, '{MIN_LOWER}' => MINIMUM_PASSWORD_LOWERCASE, '{MIN_UPPER}' => MINIMUM_PASSWORD_UPPERCASE, '{MIN_DIGIT}' => MINIMUM_PASSWORD_DIGITS);
     // 2 -- construct dialog definition including current values from database
     $dialogdef = array('dialog' => array('type' => F_INTEGER, 'name' => 'dialog', 'value' => USERMANAGER_DIALOG_EDIT, 'hidden' => TRUE), 'username' => array('type' => F_ALPHANUMERIC, 'name' => 'username', 'minlength' => 1, 'maxlength' => 60, 'columns' => 30, 'label' => t('usermanager_edit_username_label', 'admin'), 'title' => t('usermanager_edit_username_title', 'admin'), 'value' => $user['username'], 'old_value' => $user['username']), 'user_password1' => array('type' => F_PASSWORD, 'name' => 'user_password1', 'minlength' => 0, 'maxlength' => 255, 'columns' => 30, 'label' => t('usermanager_edit_user_password1_label', 'admin', $params), 'title' => t('usermanager_edit_user_password1_title', 'admin', $params), 'value' => ''), 'user_password2' => array('type' => F_PASSWORD, 'name' => 'user_password2', 'minlength' => 0, 'maxlength' => 255, 'columns' => 30, 'label' => t('usermanager_edit_user_password2_label', 'admin', $params), 'title' => t('usermanager_edit_user_password2_title', 'admin', $params), 'value' => ''), 'user_fullname' => array('type' => F_ALPHANUMERIC, 'name' => 'user_fullname', 'minlength' => 1, 'maxlength' => 255, 'columns' => 50, 'label' => t('usermanager_edit_user_fullname_label', 'admin'), 'title' => t('usermanager_edit_user_fullname_title', 'admin'), 'value' => $user['full_name'], 'old_value' => $user['full_name']), 'user_email' => array('type' => F_ALPHANUMERIC, 'name' => 'user_email', 'minlength' => 0, 'maxlength' => 255, 'columns' => 50, 'label' => t('usermanager_edit_user_email_label', 'admin'), 'title' => t('usermanager_edit_user_email_title', 'admin'), 'value' => $user['email'], 'old_value' => $user['email']), 'user_is_active' => array('type' => F_CHECKBOX, 'name' => 'user_is_active', 'options' => array(1 => t('usermanager_edit_user_is_active_check', 'admin')), 'label' => t('usermanager_edit_user_is_active_label', 'admin'), 'title' => t('usermanager_edit_user_is_active_title', 'admin'), 'value' => db_bool_is(TRUE, $user['is_active']) ? '1' : '', 'old_value' => db_bool_is(TRUE, $user['is_active']) ? '1' : ''), 'user_redirect' => array('type' => F_ALPHANUMERIC, 'name' => 'user_redirect', 'minlength' => 0, 'maxlength' => 255, 'columns' => 50, 'label' => t('usermanager_edit_user_redirect_label', 'admin'), 'title' => t('usermanager_edit_user_redirect_title', 'admin'), 'value' => $user['redirect'], 'old_value' => $user['redirect']), 'user_language_key' => array('type' => F_LISTBOX, 'name' => 'user_language_key', 'options' => $LANGUAGE->get_active_language_names(), 'label' => t('usermanager_edit_user_language_label', 'admin'), 'title' => t('usermanager_edit_user_language_title', 'admin'), 'value' => $user['language_key'], 'old_value' => $user['language_key']), 'user_editor' => array('type' => F_LISTBOX, 'name' => 'user_editor', 'options' => $this->get_editor_names(), 'label' => t('usermanager_edit_user_editor_label', 'admin'), 'title' => t('usermanager_edit_user_editor_title', 'admin'), 'value' => $user['editor'], 'old_value' => $user['editor']), 'user_skin' => array('type' => F_LISTBOX, 'name' => 'user_skin', 'options' => $this->get_skin_names(), 'label' => t('usermanager_edit_user_skin_label', 'admin'), 'title' => t('usermanager_edit_user_skin_title', 'admin'), 'value' => $user['skin'], 'old_value' => $user['skin']), 'user_path' => array('type' => F_ALPHANUMERIC, 'name' => 'user_path', 'minlength' => 1, 'maxlength' => 60, 'columns' => 30, 'label' => t('usermanager_edit_user_path_label', 'admin'), 'title' => t('usermanager_edit_user_path_title', 'admin'), 'value' => $user['path'], 'old_value' => $user['path'], 'viewonly' => TRUE), 'button_save' => dialog_buttondef(BUTTON_SAVE), 'button_cancel' => dialog_buttondef(BUTTON_CANCEL));
     return $dialogdef;
 }
 /** fetch a list of languages available as parent language
  *
  * this constructs a list of languages that can be used as a list of parent
  * language options in a listbox or radiobuttons.
  *
  * @param string $skip_language_key suppress this language in list (language cannot be its own parent)
  * @return array ready for use as an options array in a listbox or radiobuttons
  */
 function get_options_languages($skip_language_key = '')
 {
     $options['--'] = array('option' => t('translatetool_parent_language_none_option', 'admin'), 'title' => t('translatetool_parent_language_none_title', 'admin'));
     if (sizeof($this->languages) > 0) {
         foreach ($this->languages as $language_key => $language) {
             if (!empty($skip_language_key) && $skip_language_key == $language_key) {
                 continue;
             }
             $params = array('{LANGUAGE_KEY}' => $language_key, '{LANGUAGE_NAME}' => $language['language_name']);
             $title = t('translatetool_parent_language_option_title', 'admin', $params);
             $option = t('translatetool_parent_language_option_option', 'admin', $params);
             $option .= db_bool_is(TRUE, $language['is_active']) ? '' : ' (' . t('inactive', 'admin') . ')';
             $options[$language_key] = array('option' => $option, 'title' => $title);
         }
     }
     return $options;
 }