/** add a message to message queue of 0 or more alerts
  *
  * this adds $alert_message to the message buffers of 0 or more alert accounts
  * The alerts that qualify to receive this addition via the alerts_areas_nodes table.
  * The logic in that table is as follows:
  *  - the area_id must match the area_id(s) (specified in $areas) OR 
  *    it must be 0 which acts as a wildcard for ALL areas
  *  - the node_id must match the node_id(s) specified in $nodes) OR
  *    it must be 0 which acts as a wildcard for ALL nodes
  * Also the account must be active and the flag for the area/node-combination
  * must be TRUE.
  *
  * As a rule this routine is called with a single area_id in $areas and
  * a collection of node_id's in $nodes. The nodes follow the path up through
  * the tree, in order to alert accounts that are only watching a section at
  * a higher level.
  * 
  * Example:
  * If user 'webmaster' adds new page, say 34, to subsection 8 in
  * section 4 in area 1, you get something like this:
  *
  *     queue_area_node_alert(1,array(8,4,34),'node 34 added','webmaster');
  *
  * The effect will be that all accounts with the following combinations of
  * area A and node N have the message added to their buffers:
  * A=0, N=1 - qualifies for all nodes in all areas
  * A=1, N=0 - qualifies for all nodes in area 1
  * A=1, N=4 - qualifies for node 4 in area 1
  * A=1, N=8 - qualifies for node 8 in area 1
  *
  * It is very well possible that no message is added at all if there is no
  * alert account watching the specified area and node (using wildcards or
  * otherwise).
  *
  * Near the end of this routine, we check the queue with pending messages,
  * and perhaps send out a few alerts. The number of messages that can be
  * sent from here is limited; we don't want to steal too much time from an
  * unsuspecting user. It is the task of cron.php to take care of eventually
  * sending the queued messages. However, sending only a few messages won't
  * be noticed. I hope.
  *
  * Note that this routine adds a timestamp to the message and, if it is 
  * specified, the name of the user.
  *
  * Also note that the messages are added to the buffer with the last message
  * at the top, it means that the receiver will travel back in time reading
  * the collection of messages. This is based on the assumption that the latest
  * messages sometimes override a previous message and therefore should be
  * read first.
  *
  * @param mixed $areas an array or a single int identifying the area(s) of interest
  * @param mixed $nodes an array or a single int identifying the node(s) of interest
  * @param string $message the message to add to the buffer of qualifying alert accounts
  * @param string $username (optional) the name of the user that initiated the action
  * @return void
  * @uses $DB;
  */
 function queue_area_node_alert($areas, $nodes, $alert_message, $username = '')
 {
     global $DB;
     // 0 -- massage the message, add a timestamp, optional username and an extra empty line
     $message = sprintf("%s%s\n%s\n\n", strftime('%Y-%m-%d %T'), empty($username) ? '' : ' (' . $username . ')', $alert_message);
     // 1 -- construct the area part of the statement
     // example where-clause (part 1/3): ((area_id = 0) OR (area_id = 1))
     $where_clause = '';
     if (!empty($areas)) {
         $where_clause_area = '(n.area_id = 0)';
         // area_id = 0 means 'any area' in this context
         if (is_array($areas)) {
             foreach ($areas as $area_id) {
                 $where_clause_area .= sprintf(' OR (n.area_id = %d)', intval($area_id));
             }
         } else {
             $where_clause_area .= sprintf(' OR (n.area_id = %d)', intval($areas));
         }
         $where_clause .= '(' . $where_clause_area . ') AND ';
     }
     // 2 -- construct the node part of the statement
     // example where-clause (part 2/3): ((node_id = 0) OR (node_id = 4) OR (node_id = 8) OR (node_id = 34))
     if (!empty($nodes)) {
         $where_clause_node = '(n.node_id = 0)';
         // node_id = 0 means 'any node' in this context
         if (is_array($nodes)) {
             foreach ($nodes as $node_id) {
                 $where_clause_node .= sprintf(' OR (n.node_id = %d)', intval($node_id));
             }
         } else {
             $where_clause_node .= sprintf(' OR (n.node_id = %d)', intval($nodes));
         }
         $where_clause .= '(' . $where_clause_node . ') AND ';
     }
     // 3 -- only send msgs to active alerts
     // example where-clause (part 3/3): combine previous two parts with check for active alert accounts/flags
     $where_clause .= '(a.is_active) AND (n.flag)';
     // 4 -- construct complete statement
     // Note that we also are constructing the update of the message field manually, so
     // we MUST take care of proper escaping and quoting. Also, the trick with $DB->concat()
     // complicates this SQL-statement, but alas this is necessary due to the non-standard way
     // MySQL interprets the string concatenation operator '||'. Aaarrrghhhhhh
     // (see http://troels.arvin.dk/db/rdbms/#functions-concat)
     $sql = sprintf('UPDATE %salerts AS a INNER JOIN %salerts_areas_nodes AS n ON a.alert_id = n.alert_id ' . 'SET a.message_buffer = %s, a.messages = a.messages + 1 ' . 'WHERE %s', $DB->prefix, $DB->prefix, $DB->concat(db_escape_and_quote($message), 'a.message_buffer'), $where_clause);
     $retval = $DB->exec($sql);
     if ($retval === FALSE) {
         logger(__CLASS__ . ": error queueing alerts: '" . db_errormessage() . "' with sql = {$sql}");
     } else {
         logger(__CLASS__ . ": number of alerts queued: {$retval}");
         logger(__FUNCTION__ . "(): sql = " . $sql, WLOG_DEBUG);
     }
     // Even if we did not add a message ourselves, we can 'steal' some time
     // of the current user and see if there are mails that need to be sent out.
     cron_send_queued_alerts(2);
     // limit processing to 2 messages at this time
 }
/** main program for site maintenance
 *
 * This is the main administrator program.
 * First step is to deal with users logging in or out.
 * If a user is not logged in, a login dialog is displayed.
 * If a user is logged in but has no admin privileges, she
 * is redirected to the public site (ie. index.php).
 *
 * Once we have established that the user is an administrator,
 * we setup an output collecting object and see what the user
 * wants us to do by interpreting the parameter 'job'.
 * If the user has access to the specified job, the corresponding
 * code is included and the main routine of that handler is called.
 * It is then the responsability of that handler to further decide
 * what needs to be done.
 * After the handler returns, the collected output is sent to the user.
 * This includes the main navigation (i.e. links to the various
 * 'managers') and also the menu and the content generated by the
 * handler.
 *
 * If the user has no privilege to access a particular manager,
 * an error messate is displayed in both the message area and the content
 * area. This makes it clear to the user that access is denied.
 * Note that the inaccessible items are displayed in the main navigation
 * via 'dimmed' (light-grey) links or black/white images.
 * By showing these 'dimmed' links, the user will be aware that there
 * is more that just what she is allowed to see. This is more transparent
 * than suppressing items and keeping them secret.
 *
 * @return void generated page sent to user's browser
 * @uses $CFG;
 * @uses $LANGUAGE;
 * @uses $USER;
 * @todo should we cater for a special 'print' button + 
 *       support for a special style sheet for media="print"?
 */
function main_admin()
{
    global $CFG;
    global $LANGUAGE;
    global $USER;
    /** initialise, setup database, read configuration, etc. */
    require_once $CFG->progdir . '/init.php';
    initialise();
    // user must be logged in to perform any admin tasks at all
    if (isset($_GET['logout'])) {
        admin_logout_and_exit();
    } elseif (isset($_GET['login'])) {
        $user_id = admin_login(magic_unquote($_GET['login']));
    } elseif (isset($_COOKIE[$CFG->session_name])) {
        $user_id = admin_continue_session();
    } else {
        admin_show_login_and_exit();
    }
    /** useraccount.class.php is used to define the USER object */
    require_once $CFG->progdir . '/lib/useraccount.class.php';
    $USER = new Useraccount($user_id);
    $USER->is_logged_in = TRUE;
    $_SESSION['language_key'] = $LANGUAGE->get_current_language();
    // remember language set via _GET or otherwise
    // Only admins are allowed, others are redirected to index.php
    if (!$USER->is_admin()) {
        logger("admin.php: '{$USER->username}' ({$USER->user_id}) is no admin and was redirected to index.php or login");
        session_write_close();
        non_admin_redirect_and_exit();
    }
    // We now know that this user is an admin, but
    // is she allowed to perform upgrades if any? Check it out in 2 steps
    // 1--we do NOT want exit on error if the user has enough privileges
    // 2--we check the version and stay here if the user has enough privileges
    $exit_on_error = $USER->has_job_permissions(JOB_PERMISSION_UPDATE) ? FALSE : TRUE;
    $need_to_update = was_version_check($exit_on_error) ? FALSE : TRUE;
    // We are still here if versions are OK _or_ versions mismatch but user has UPDATE privilege.
    // Now we know we _will_ be generating output => setup output object
    // using the specified skin OR the user's prefererred skin OR the one
    // stored before in $_SESSION
    $_SESSION['skin'] = get_current_skin();
    // echo "DDD: {$_SESSION['skin']}";
    $output = new AdminOutput($_SESSION['skin'], $CFG->title);
    // Display a 'welcome message' if this is the first page after logging in.
    if ($_SESSION['session_counter'] == 1) {
        $output->add_message(t('login_user_success', 'admin', array('{USERNAME}' => $USER->username)));
    }
    // Let's see what what job needs to be done
    $job = $need_to_update ? JOB_UPDATE : get_parameter_string('job', JOB_STARTCENTER);
    // main dispatcher
    switch ($job) {
        case JOB_STARTCENTER:
            job_start($output);
            break;
        case JOB_PAGEMANAGER:
            add_javascript_popup_function($output, '  ');
            if ($USER->has_job_permissions(JOB_PERMISSION_PAGEMANAGER)) {
                include $CFG->progdir . '/lib/pagemanager.class.php';
                $manager = new PageManager($output);
            } else {
                $output->add_content("<h2>" . t('access_denied', 'admin') . "</h2>");
                $output->add_content(t('job_access_denied', 'admin'));
                $output->add_message(t('job_access_denied', 'admin'));
            }
            break;
        case JOB_FILEMANAGER:
        case JOB_FILEBROWSER:
        case JOB_IMAGEBROWSER:
        case JOB_FLASHBROWSER:
            add_javascript_popup_function($output, '  ');
            add_javascript_select_url_function($output, '  ');
            if ($USER->has_job_permissions(JOB_PERMISSION_FILEMANAGER)) {
                include $CFG->progdir . '/lib/filemanager.class.php';
                $manager = new FileManager($output, $job);
            } else {
                $output->add_content("<h2>" . t('access_denied', 'admin') . "</h2>");
                $output->add_content(t('job_access_denied', 'admin'));
                $output->add_message(t('job_access_denied', 'admin'));
            }
            break;
        case JOB_MODULEMANAGER:
            if ($USER->has_job_permissions(JOB_PERMISSION_MODULEMANAGER)) {
                include $CFG->progdir . '/lib/modulemanagerlib.php';
                job_modulemanager($output);
            } else {
                $output->add_content("<h2>" . t('access_denied', 'admin') . "</h2>");
                $output->add_content(t('job_access_denied', 'admin'));
                $output->add_message(t('job_access_denied', 'admin'));
            }
            break;
        case JOB_ACCOUNTMANAGER:
            if ($USER->has_job_permissions(JOB_PERMISSION_ACCOUNTMANAGER)) {
                include $CFG->progdir . '/lib/accountmanagerlib.php';
                job_accountmanager($output);
            } else {
                $output->add_content("<h2>" . t('access_denied', 'admin') . "</h2>");
                $output->add_content(t('job_access_denied', 'admin'));
                $output->add_message(t('job_access_denied', 'admin'));
            }
            break;
        case JOB_CONFIGURATIONMANAGER:
            if ($USER->has_job_permissions(JOB_PERMISSION_CONFIGURATIONMANAGER)) {
                include $CFG->progdir . '/lib/configurationmanagerlib.php';
                job_configurationmanager($output);
            } else {
                $output->add_content("<h2>" . t('access_denied', 'admin') . "</h2>");
                $output->add_content(t('job_access_denied', 'admin'));
                $output->add_message(t('job_access_denied', 'admin'));
            }
            break;
        case JOB_STATISTICS:
            if ($USER->has_job_permissions(JOB_PERMISSION_STATISTICS)) {
                include $CFG->progdir . '/lib/statisticslib.php';
                job_statistics($output);
            } else {
                $output->add_content("<h2>" . t('access_denied', 'admin') . "</h2>");
                $output->add_content(t('job_access_denied', 'admin'));
                $output->add_message(t('job_access_denied', 'admin'));
            }
            break;
        case JOB_TOOLS:
            if ($USER->has_job_permissions(JOB_PERMISSION_TOOLS)) {
                // user has permission to access at least one of the tools
                include $CFG->progdir . '/lib/toolslib.php';
                job_tools($output);
            } else {
                $output->add_content("<h2>" . t('access_denied', 'admin') . "</h2>");
                $output->add_content(t('job_access_denied', 'admin'));
                $output->add_message(t('job_access_denied', 'admin'));
            }
            break;
        case JOB_UPDATE:
            if ($USER->has_job_permissions(JOB_PERMISSION_UPDATE)) {
                // user has permission to access the update routine(s)
                include $CFG->progdir . '/lib/updatelib.php';
                job_update($output);
            } else {
                $output->add_content("<h2>" . t('access_denied', 'admin') . "</h2>");
                $output->add_content(t('job_access_denied', 'admin'));
                $output->add_message(t('job_access_denied', 'admin'));
            }
            break;
        default:
            if (!empty($job)) {
                $output->add_content("<h2>" . t('access_denied', 'admin') . "</h2>");
                $output->add_content(t('unknown_job', 'admin', array('{JOB}' => htmlspecialchars($job))));
                $output->add_message(t('unknown_job', 'admin', array('{JOB}' => htmlspecialchars($job))));
                logger("'" . $USER->username . "': unknown job '" . htmlspecialchars($job) . "'");
            } else {
                job_start($output);
            }
            break;
    }
    // the various functions job_*() will have put their output in $output
    // Now it is time to actually output the output to the user's browser.
    $output->send_output();
    // make sure that any changes in $_SESSION are properly stored
    // note that we close the session only after all processing is done,
    // allowing the various job_*()'s to manipulate the session variables
    session_write_close();
    // at this point we have sent the page to the user,
    // we can now use the remaining time in this run to process
    // a few alerts (if any).
    cron_send_queued_alerts(25);
    // if there are more than 25, do them later or let cron do it.
    return;
}