/** * process one single HTTP request * * @return void */ public static function check_request() { global $context; // ensure we know where we are if (!isset($context['script_url']) || !$context['script_url']) { return; } // don't bother with HEAD requests if (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'HEAD') { return; } // ensure we have a valid database resource if (!isset($context['connection']) || !$context['connection']) { return; } // only on regular operation if (!file_exists($context['path_to_root'] . 'parameters/switch.on')) { return; } // script used for this request $url = $context['script_url']; // execution time up to now $execution_time = round(get_micro_time() - $context['start_time'], 4); // if a record exists for this url $query = "SELECT * FROM " . SQL::table_name('profiles') . " AS profiles WHERE profiles.url = '{$url}'"; $item = SQL::query_first($query); // update figures if ($item['id']) { $query = "UPDATE " . SQL::table_name('profiles') . " SET " . "total_hits='" . ($item['total_hits'] + 1) . "', " . "total_time='" . ($item['total_time'] + $execution_time) . "', " . "minimum_time='" . min($item['minimum_time'], $execution_time) . "', " . "maximum_time='" . max($item['maximum_time'], $execution_time) . "' " . " WHERE id = " . $item['id']; } else { $query = "INSERT INTO " . SQL::table_name('profiles') . " SET " . "url='" . $url . "', " . "total_hits='1', " . "total_time='" . $execution_time . "', " . "minimum_time='" . $execution_time . "', " . "maximum_time='" . $execution_time . "'"; } SQL::query($query); }
$context['text'] .= i18n::s('One letter has been transmitted.') . BR . "\n"; } else { $context['text'] .= sprintf(i18n::s('%d letters have been transmitted.'), $recipients_ok) . BR . "\n"; } // transmission errors, if any if ($recipients_errors == 1) { $context['text'] .= i18n::s('One transmission error has been encountered.') . BR . "\n"; } elseif ($recipients_errors > 1) { $context['text'] .= sprintf(i18n::s('%d transmission errors have been encountered.'), $recipients_errors) . BR . "\n"; } // save digest stamp, if any if (isset($_REQUEST['digest_stamp']) && $_REQUEST['digest_stamp'] > NULL_DATE) { Values::set('letters.digest.stamp', $_REQUEST['digest_stamp']); } // display the execution time $time = round(get_micro_time() - $context['start_time'], 2); $context['text'] .= '<p>' . sprintf(i18n::s('Script terminated in %.2f seconds.'), $time) . '</p>'; // forward to the index page $menu = array('letters/' => i18n::s('Newsletters')); $context['text'] .= Skin::build_list($menu, 'menu_bar'); // make the user select an option } else { // the splash message $context['text'] .= '<p>' . i18n::s('This script will help you to prepare and to send a electronic message to community members. Please select below the action you would like to perform. Depending on your choice, the assistant may ask for additional parameters on successive panels.') . '</p>' . "\n"; // the form $context['text'] .= '<form method="get" action="' . $context['script_url'] . '" id="main_form">' . "\n"; // a digest of most recent articles $context['text'] .= '<p><input type="radio" name="action" value="digest" selected="selected" /> ' . i18n::s('Send a digest of articles published recently') . '</p>' . "\n"; // list featured pages $context['text'] .= '<p><input type="radio" name="action" value="featured" /> ' . i18n::s('List featured pages') . '</p>' . "\n"; // some announcement
/** * dynamically generate the page * * @see skins/index.php */ function send_body() { global $context, $local; // $local is required to localize included scripts // include every script that has to be run once global $scripts, $scripts_count; if (@count($scripts)) { // the alphabetical order may be used to control script execution order sort($scripts); reset($scripts); // process each script one by one foreach ($scripts as $item) { // do not execute on first installation if (file_exists('../parameters/switch.on') || file_exists('../parameters/switch.off')) { // ensure we have a valid database resource if (!$context['connection']) { break; } // remember this as an event Logger::remember('scripts/run_once.php: ' . sprintf(i18n::c('script %s has been executed'), $item)); // where scripts actually are $actual_item = str_replace('//', '/', $context['path_to_root'] . 'scripts/run_once/' . $item); // include the script to execute it $scripts_count++; echo Skin::build_block($item, 'subtitle'); include $actual_item; echo "\n"; } // ensure enough overall execution time Safe::set_time_limit(30); // stamp the file to remember execution time Safe::touch($actual_item); // rename the script to avoid further execution Safe::unlink($actual_item . '.done'); Safe::rename($actual_item, $actual_item . '.done'); } // refresh javascript libraries Cache::purge('js'); } // report on actual execution if ($scripts_count) { echo '<p> </p><p>' . sprintf(i18n::ns('%d script has been executed', '%d scripts have been executed', $scripts_count), $scripts_count) . "</p>\n"; } else { echo '<p>' . i18n::s('No script has been executed') . "</p>\n"; } // display the total execution time $time = round(get_micro_time() - $context['start_time'], 2); if ($time > 30) { echo '<p>' . sprintf(i18n::s('Script terminated in %.2f seconds.'), $time) . '</p>'; } // if the server has been switched off, go back to the control panel if (file_exists('../parameters/switch.off')) { echo '<form method="get" action="' . $context['url_to_root'] . 'control/">' . "\n" . '<p class="assistant_bar">' . Skin::build_submit_button(i18n::s('Control Panel')) . '</p>' . "\n" . '</form>' . "\n"; // else back to the control panel as well, but without a button } else { $menu = array('control/' => i18n::s('Control Panel')); echo Skin::build_list($menu, 'menu_bar'); } // purge the cache, since it is likely that we have modified some data Cache::clear(); }
Safe::redirect($context['url_to_home'] . $context['url_to_root'] . 'users/login.php?url=' . urlencode('servers/ping.php')); } elseif (!Surfer::is_associate()) { Safe::header('Status: 401 Unauthorized', TRUE, 401); Logger::error(i18n::s('You are not allowed to perform this operation.')); // do the ping } elseif (isset($_REQUEST['action']) && $_REQUEST['action'] == 'ping') { // list servers to be advertised if ($servers = Servers::list_for_ping(0, 20, 'ping')) { $context['text'] .= '<p>' . i18n::s('Servers that have been notified') . '</p><ul>'; // ping each server foreach ($servers as $server_url => $attributes) { list($server_ping, $server_label) = $attributes; $milestone = get_micro_time(); $result = @Call::invoke($server_ping, 'weblogUpdates.ping', array(strip_tags($context['site_name']), $context['url_to_home'] . $context['url_to_root']), 'XML-RPC'); if ($result[0]) { $label = round(get_micro_time() - $milestone, 2) . ' sec.'; } else { $label = @$result[1]; } $context['text'] .= '<li>' . $server_label . ' (' . $label . ')</li>'; } $context['text'] .= '</ul>'; // no server to ping } else { $context['text'] .= '<p>' . i18n::s('No server has been created yet.') . '</p>'; } // back to the index of servers $menu = array('servers/' => i18n::s('Servers')); $context['text'] .= Skin::build_list($menu, 'menu_bar'); // remember this in log as well Logger::remember('servers/ping.php: The cloud has been pinged');
/** * notify servers about a new page * * @param string page URL * @param string server name */ public static function notify($link, $title = NULL) { global $context; if (!$title) { $title = $context['site_name']; } // the list of recipients contacted during overall script execution if (!isset($context['servers_endpoints'])) { $context['servers_endpoints'] = array(); } // list servers to be advertised if ($servers = Servers::list_for_ping(0, COMPACT_LIST_SIZE, 'ping')) { // ping each server include_once $context['path_to_root'] . 'services/call.php'; foreach ($servers as $server_url => $attributes) { list($server_ping, $server_label) = $attributes; $milestone = get_micro_time(); $result = Call::invoke($server_ping, 'weblogUpdates.ping', array(strip_tags($title), $context['url_to_home'] . $context['url_to_root'] . $link), 'XML-RPC'); if ($result[0]) { $server_label .= ' (' . round(get_micro_time() - $milestone, 2) . ' sec.)'; } $context['servers_endpoints'][] = $server_label; } } }
/** * echo the standard footer * * Note that this one does not echo $context['page_footer'], and you have * to do it yourself. * * @param string footer prefix, if any * @param string footer suffix, if any */ public static function footer($prefix = '', $suffix = '') { global $context; // the last paragraph echo '<p>'; // add footer prefix echo $prefix; $details = array(); // execution time and surfer name, for logged user only (not for indexing robots!) if (is_callable(array('Surfer', 'get_name')) && is_callable(array('i18n', 's'))) { $execution_time = round(get_micro_time() - $context['start_time'], 2); $details[] = sprintf(i18n::s('page prepared in %.2f seconds for %s'), $execution_time, ucwords(Surfer::get_name())); } // site copyright if (isset($context['site_copyright']) && $context['site_copyright']) { $details[] = '© ' . $context['site_copyright'] . "\n"; } // a command to authenticate if (is_callable(array('Surfer', 'is_logged')) && !Surfer::is_logged() && is_callable(array('i18n', 's'))) { $details[] = Skin::build_link('users/login.php', i18n::s('login'), 'basic'); } // about this site if (is_callable(array('i18n', 's')) && is_callable(array('Articles', 'get_url'))) { $details[] = Skin::build_link(Articles::get_url('about'), i18n::s('about this site'), 'basic'); } // privacy statement if (is_callable(array('i18n', 's')) && is_callable(array('Articles', 'get_url'))) { $details[] = Skin::build_link(Articles::get_url('privacy'), i18n::s('privacy statement'), 'basic'); } // a reference to YACS if (is_callable(array('i18n', 's')) && $context['host_name'] != 'www.yacs.fr') { $details[] = sprintf(i18n::s('powered by %s'), Skin::build_link(i18n::s('http://www.yacs.fr/'), 'Yacs', 'external')); } // all our feeds if (is_callable(array('i18n', 's'))) { $details[] = Skin::build_link('feeds/', i18n::s('information channels'), 'basic'); } echo join(' - ', $details); // add footer suffix echo $suffix; // end of the last paragraph echo '</p>' . "\n"; }
if (SQL::query($query) === FALSE) { $context['text'] .= Logger::error_pop() . BR . "\n"; } // display the execution time $time_end = get_micro_time(); $time = round($time_end - $context['start_time'], 2); $context['text'] .= '<p>' . sprintf(i18n::s('Script terminated in %.2f seconds.'), $time) . '</p>'; // forward to the control panel $menu = array('control/' => i18n::s('Control Panel'), 'control/purge.php' => i18n::s('Purge again')); $context['text'] .= Skin::build_list($menu, 'menu_bar'); // delete formatting code patterns, will be rebuild automaticaly } elseif (isset($_REQUEST['action']) && $_REQUEST['action'] == 'codeyacs') { $context['text'] .= '<p>' . i18n::s('Deleting formatting codes cache...') . "</p>\n"; Safe::unlink($context['path_to_root'] . 'codes/patterns.auto.php'); // display the execution time $time_end = get_micro_time(); $time = round($time_end - $context['start_time'], 2); $context['text'] .= '<p>' . sprintf(i18n::s('Script terminated in %.2f seconds.'), $time) . '</p>'; // forward to the control panel $menu = array('control/' => i18n::s('Control Panel'), 'control/purge.php' => i18n::s('Purge again')); $context['text'] .= Skin::build_list($menu, 'menu_bar'); // which check? } else { // the splash message $context['text'] .= '<p>' . i18n::s('Please select the action to perform.') . "</p>\n"; // the form $context['text'] .= '<form method="post" action="' . $context['script_url'] . '" id="main_form">'; // purge the cache $context['text'] .= '<p><input type="radio" name="action" value="cache" checked="checked" /> ' . i18n::s('Purge the server cache.') . '</p>'; // purge .bak scripts $context['text'] .= '<p><input type="radio" name="action" value="bak" /> ' . i18n::s('Delete all files with the suffix .bak.') . '</p>';
/** * dynamically generate the page * * @see skins/index.php */ function send_body() { global $context; // only associates can proceed if (!Surfer::is_associate()) { Safe::header('Status: 401 Unauthorized', TRUE, 401); echo '<p>' . i18n::s('You are not allowed to perform this operation.') . "</p>\n"; // forward to the index page $menu = array('scripts/' => i18n::s('Server software')); echo Skin::build_list($menu, 'menu_bar'); // ask for confirmation } elseif (isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'GET') { // the splash message echo '<p>' . i18n::s('This tool will include most of the running reference PHP scripts. Any syntax error should be spotted easily.') . '</p>'; // the submit button echo '<form method="post" action="' . $context['script_url'] . '" id="main_form"><p>' . Skin::build_submit_button(i18n::s('Yes, I want to validate scripts'), NULL, NULL, 'confirmed') . '</p></form>'; // set the focus on the button Page::insert_script('$("#confirmed").focus();'); // this may take some time echo '<p>' . i18n::s('When you will click on the button the server will be immediately requested to proceed. However, because of the so many things to do on the back-end, you may have to wait for minutes before getting a response displayed. Thank you for your patience.') . '</p>'; // just do it } else { // the splash message echo '<p>' . i18n::s('All reference scripts are included, to show evidence of possible syntax errors.') . "</p>\n"; // list running scripts echo '<p>' . i18n::s('Listing files...') . BR . "\n"; // locate script files starting at root $scripts = Scripts::list_scripts_at(NULL); if (is_array($scripts) && count($scripts)) { echo BR . sprintf(i18n::s('%d scripts have been found.'), count($scripts)) . "\n"; natsort($scripts); } echo "</p>\n"; // including scripts echo '<p>' . i18n::s('Including reference scripts...') . BR . "\n"; // strip as much output as possible $_SERVER['REQUEST_METHOD'] = 'HEAD'; // we will finalize this page later on global $finalizing_fuse; $finalizing_fuse = FALSE; // take care of dependancies include_once '../behaviors/behavior.php'; include_once '../services/codec.php'; include_once '../users/authenticator.php'; // analyse each script $included_files = 0; $links_to_be_checked_manually = array(); foreach ($scripts as $file) { // ensure we have enough time to process this script Safe::set_time_limit(30); // skip run once scripts if (strpos($file, 'run_once/')) { continue; } // don't include ourself if ($file == 'scripts/validate.php') { continue; } // process only reference scripts if (!Scripts::hash($file)) { continue; } // check file content if (!($handle = Safe::fopen($file, 'rb'))) { echo sprintf(i18n::s('%s has no readable content.'), $file) . BR . "\n"; continue; } // look at the beginning of the file if (!($header = fread($handle, 16384))) { echo sprintf(i18n::s('%s has no readable content.'), $file) . BR . "\n"; fclose($handle); continue; } fclose($handle); // skip scripts that generate content asynchronously if (stripos($header, 'send_body') || stripos($header, 'page::content')) { $links_to_be_checked_manually[$file] = '(asynchronous)'; continue; } // skip scripts that would redefine our skin if (stripos($header, 'extends skin_skeleton')) { $links_to_be_checked_manually[$file] = '(skin)'; continue; } // log script inclusion on development host if ($context['with_debug'] == 'Y') { logger::remember('scripts/validate.php: inclusion of ' . $file, '', 'debug'); } // include the script and display any error $included_files += 1; $validate_stamp = time(); echo sprintf(i18n::s('inclusion of %s'), $file) . "\n"; Safe::chdir($context['path_to_root'] . dirname($file)); include_once $context['path_to_root'] . $file; $duration = time() - $validate_stamp; if ($duration) { echo ' (' . $duration . 's.)'; } echo BR; } // memory status $used_memory = ''; if (is_callable('memory_get_usage')) { $used_memory = ' (' . memory_get_usage() . ' bytes)'; } // report of included files if ($included_files > 1) { echo '<p>' . sprintf(i18n::s('%d files have been included.'), $included_files) . $used_memory . '</p>'; } // list files to be checked manually if (count($links_to_be_checked_manually)) { echo '<p>' . i18n::s('Following scripts have to be included separately:') . BR . "\n"; ksort($links_to_be_checked_manually); foreach ($links_to_be_checked_manually as $file => $label) { echo Skin::build_link($file, $file, 'basic') . ' ' . $label . BR . "\n"; } echo sprintf(i18n::s('%d files to be checked manually.'), count($links_to_be_checked_manually)) . '</p>' . "\n"; } // display the execution time $time = round(get_micro_time() - $context['start_time'], 2); echo '<p>' . sprintf(i18n::s('Script terminated in %.2f seconds.'), $time) . '</p>'; // forward to the referential building echo '<form method="get" action="' . $context['url_to_root'] . 'scripts/build.php"><p>' . "\n" . Skin::build_submit_button(i18n::s('If no error has popped up, build the reference set >>')) . "\n" . '</p></form>' . "\n"; // this may take some time echo '<p>' . i18n::s('When you will click on the button the server will be immediately requested to proceed. However, because of the so many things to do on the back-end, you may have to wait for minutes before getting a response displayed. Thank you for your patience.') . '</p>'; // clear text some scripts could have added $context['debug'] = ''; $context['extra'] = ''; $context['navigation'] = ''; $context['suffix'] = ''; $context['text'] = ''; $context['page_details'] = ''; $context['page_footer'] = ''; $context['page_menu'] = array(); $context['page_tags'] = ''; $context['page_tools'] = ''; // now we will finalize this page global $finalizing_fuse; unset($finalizing_fuse); } }
?> <tr> <td style="border: none bgcolor="555555"> <font size="1" face="verdana" color="#fcfcfc"> <br> <tr> <tr> <td style="border: none bgcolor="555555"> <font size="1" face="verdana" color="#fcfcfc"> <br> </tr> </table> </div> </td> </tr> </table> <table width=950> <tr> <td style="border: 1 solid #000000" bgcolor="677667" > <font size="1" face="verdana" color="#000000"> <center> <?php echo "-=[" . $cshver . " | Page generation time: <font color=#fcfcfc>[<b>" . round(get_micro_time() - start_time, 4) . "</b>]</font> seconds.]=-"; ?> </td> </tr> </table> </BODY> </HTML>
/** * process new messages, if any * * This function checks inbound mailboxes, and process new messages on their arrival. * * This function is aiming to run silently, therefore errors are logged in a file. * * @return a string to be displayed in resulting page, if any */ public static function tick_hook() { global $context; // useless if we don't have a valid database connection if (!$context['connection']) { return; } // we need some queue definitions Safe::load('parameters/agents.include.php'); if (!isset($context['mail_queues']) || !is_array($context['mail_queues']) || !count($context['mail_queues'])) { return 'agents/messages.php: no queue has been defined' . BR; } // remember start time $stamp = get_micro_time(); // process each inbound queue include_once $context['path_to_root'] . 'shared/values.php'; // messages.tick $count = 0; foreach ($context['mail_queues'] as $name => $queue) { // count messages retrieved $messages = Messages::process_queue($queue); $count += $messages; // remember tick date Values::set('messages.tick.' . $name, $messages); } // rebuild index pages if ($count) { Cache::clear(); } // compute execution time $time = round(get_micro_time() - $stamp, 2); // report on work achieved if ($count > 1) { return 'agents/messages.php: ' . $count . ' messages have been processed (' . $time . ' seconds)' . BR; } elseif ($count == 1) { return 'agents/messages.php: 1 message has been processed (' . $time . ' seconds)' . BR; } else { return 'agents/messages.php: nothing to do (' . $time . ' seconds)' . BR; } }
/** * query the database * * This function populates the error context, where applicable. * * @param string the SQL query * @param boolean optional TRUE to not report on any error * @param resource connection to be considered, if any * @return the resource returned by the database server, or the number of affected rows, or FALSE on error */ public static function query(&$query, $silent = FALSE, $connection = NULL) { global $context; // allow for reference $output = FALSE; // use the default connection if (!$connection) { // we do need a connection to the database if (!isset($context['connection']) || !$context['connection']) { return $output; } $connection = $context['connection']; } // reopen a connection if database is not reachable anymore if (get_micro_time() - $context['start_time'] > 1.0 && !SQL::ping($connection)) { // remember the error, if any -- we may not have a skin yet if (!$silent) { if (is_callable(array('Skin', 'error'))) { Logger::error(i18n::s('Connection to the database has been lost')); } else { die(i18n::s('Connection to the database has been lost')); } } // query cannot be processed return $output; } // ensure enough execution time Safe::set_time_limit(30); // profile database requests $query_stamp = get_micro_time(); // do the job if (is_callable('mysqli_query')) { $result = mysqli_query($connection, $query); } else { $result = mysql_query($query, $connection); } // finalize result if ($result) { // provide more than a boolean result if ($result === TRUE) { if (is_callable('mysqli_affected_rows')) { $result = mysqli_affected_rows($connection); } else { $result = mysql_affected_rows($connection); } } // flag slow requests $duration = get_micro_time() - $query_stamp; if ($duration >= 0.5 && $context['with_debug'] == 'Y') { Logger::remember('shared/sql.php: SQL::query() slow request', $duration . "\n\n" . $query, 'debug'); } // return the set of selected rows return $result; } // remember the error, if any if (SQL::errno($connection)) { // display some error message if (!$silent) { if (is_callable(array('Skin', 'error'))) { Logger::error($query . '<br />' . SQL::error($connection)); } else { die($query . '<br />' . SQL::error($connection)); } } // log the error at development host if ($context['with_debug'] == 'Y') { Logger::remember('shared/sql.php: SQL::query()', SQL::error($connection) . "\n\n" . $query, 'debug'); } } // no valid result return $output; }
/** * process new uploads, if any * * This function checks the input queue, and process new files on their arrival. * * This function is aiming to run silently, therefore errors are logged in a file. * * @return a string to be displayed in resulting page, if any * */ public static function tick_hook() { global $context; // useless if we don't have a valid database connection if (!$context['connection']) { return; } // remember start time $stamp = get_micro_time(); // process handx weblog entries, if any $count = 0; if (($files = Uploads::list_files('inbox/entries')) && @count($files) > 0) { foreach ($files as $file) { // help the webmaster Logger::remember('agents/upload.php: processing ' . $file); // create articles Uploads::process_handx_weblog($file); // no more than 10 entries per tick $count += 1; if ($count >= 10) { break; } } // remember tick date include_once $context['path_to_root'] . 'shared/values.php'; Values::set('uploads.tick.entries', $count); } // rebuild index pages if ($count) { Cache::clear(); } // compute execution time $time = round(get_micro_time() - $stamp, 2); // report on work achieved if ($count > 1) { return 'agents/uploads.php: ' . $count . ' files have been processed (' . $time . " seconds)" . BR; } elseif ($count == 1) { return 'agents/uploads.php: 1 file has been processed (' . $time . " seconds)" . BR; } else { return 'agents/uploads.php: nothing to do (' . $time . " seconds)" . BR; } }
/** * process deferred messages * * Most often, the server has to stay below a given rate of messages, * for example 50 messages per hour. * * Of course, any lively community will feature bursts of activity and of * messages, therefore the need for a shaping mechanism. * * YACS implements a leaking bucket algorithm to take care of messages sent * previously: * * 1. Initially, the bucket is empty. * * 2. New messages are queued in the database, to be processed asynchronously. * * 3. On background ticks, the bucket is decremented. If the bucket becomes * empty, and if some messages have been queued, a couple of them are sent, and * the bucket is incremented accordingly. * * Bucket content is managed as value 'bucket.content' saved in the database. * * The bucket size is given by parameter $context['mail_hourly_maximum'], set * in the configuration panel for system parameters. * * This parameter has a default value of 50, meaning YACS will not send more * than 50 messages per hour. * * Background processing is either added to regular page generation or delegated * to an external sub-system (e.g., cron). In case of a large site, we recommend * to use the second solution, even if this adds additional setup steps. Your * choice will be recorded in the configuration panel for system parameters. * * @see control/configure.php * * The number of messages sent on each tick can go up to the bucket size if * background processing is external. Else it is one fourth of bucket size, to * minimize impact on watching surfer. * * @see cron.php */ public static function tick_hook() { global $context; // email services have to be activated if (!isset($context['with_email']) || $context['with_email'] != 'Y') { return; } // useless if we don't have a valid database connection if (!$context['connection']) { return; } // remember start time $start = get_micro_time(); // get bucket size --force it if set to 0 if (!isset($context['mail_hourly_maximum']) || $context['mail_hourly_maximum'] < 5) { $context['mail_hourly_maximum'] = 50; } // get record related to last tick include_once $context['path_to_root'] . 'shared/values.php'; $bucket = Values::get_record('mailer.bucket.content', 0); $bucket['value'] = intval($bucket['value']); // some content to leak if ($bucket['value'] > 0) { // date of last stamp if (isset($bucket['edit_date'])) { $stamp = SQL::strtotime($bucket['edit_date']); } else { $stamp = time() - 3600; } // leak is maximum after one hour $leak = intval($context['mail_hourly_maximum'] * (time() - $stamp) / 3600); // preserve previous value until actual leak if ($leak < 1) { return; } // actual leak $bucket['value'] = max(0, $bucket['value'] - $leak); } // process some messages only when bucket is empty $count = 0; if ($bucket['value'] < 1) { // reduced speed if on-line processing if (isset($_SERVER['REMOTE_ADDR'])) { $slice = intval($context['mail_hourly_maximum'] / 4); } else { $slice = intval($context['mail_hourly_maximum']); } // get some messages, if any $query = "SELECT * FROM " . SQL::table_name('messages') . " ORDER BY edit_date LIMIT 0, " . $slice; if ($result = SQL::query($query)) { // process every message while ($item = SQL::fetch($result)) { Mailer::process($item['recipient'], $item['subject'], $item['message'], $item['headers']); // purge the queue $query = 'DELETE FROM ' . SQL::table_name('messages') . ' WHERE id = ' . $item['id']; SQL::query($query); // fill the bucket $bucket['value'] += 1; $count++; // take care of time if (!($count % 50)) { // ensure enough execution time Safe::set_time_limit(30); } } // close connection Mailer::close(); } } // remember new state of the bucket Values::set('mailer.bucket.content', $bucket['value']); // compute execution time $time = round(get_micro_time() - $start, 2); // report on work achieved if ($count > 1) { return 'shared/mailer.php: ' . $count . ' messages have been processed (' . $time . ' seconds)' . BR; } elseif ($count == 1) { return 'shared/mailer.php: 1 message has been processed (' . $time . ' seconds)' . BR; } elseif ($bucket['value']) { return 'shared/mailer.php: delaying messages (' . $time . ' seconds)' . BR; } else { return 'shared/mailer.php: nothing to do (' . $time . ' seconds)' . BR; } }
/** * get news from remote servers * * This function queries remote sources and populate the table of links based on fetched news. * * On tick, the including hook calls [code]Feeds::tick_hook()[/code]. * See [script]control/scan.php[/script] for a more complete description of hooks. * * The function browses the database to locate servers acting as feeders, and read the URLs to use. * * A round-robin algorithm is implemented, meaning that servers are polled in sequence throughout successive ticks. * At most 1 feed is parsed on each tick, to limit impact when the "poor-man" cron mechanism is used, * which is the default setting. * * XML feeds are fetched and parsed according to their type. * At the moment YACS is able to process RSS and slashdot feeds. * Link records are created or updated in the database saving as much of possible of provided data. * Item data is reflected in Link, Title, and Description fields. * Channel data is used to populate the Source field. * Stamping information is based on feeding date, and channel title. * Also, the edit action 'link:feed' marks links that are collected from feeders. * The anchor field is set to the category assigned in the server profile. * * At the end of the feeding process, the database is purged from oldest links according to the limit * defined in parameters/feeds.include.php, set through feeds/configure.php. * See Links::purge_old_news(). * * @param boolean if set to true, fetch news on each call; else use normal period of time * @return a string to be displayed in resulting page, if any * * @see control/scan.php * @see feeds/configure.php */ public static function tick_hook($forced = FALSE) { global $context; // load librairies only once include_once $context['path_to_root'] . 'links/links.php'; include_once $context['path_to_root'] . 'servers/servers.php'; include_once $context['path_to_root'] . 'shared/values.php'; // feeds.tick // get feeding parameters Safe::load('parameters/feeds.include.php'); // delay between feeds - minimum is 5 minutes if (!isset($context['minutes_between_feeds']) || $context['minutes_between_feeds'] < 5) { $context['minutes_between_feeds'] = 5; } // do not wait for the end of a feeding cycle if ($forced) { $threshold = gmstrftime('%Y-%m-%d %H:%M:%S'); } else { $threshold = gmstrftime('%Y-%m-%d %H:%M:%S', time() - $context['minutes_between_feeds'] * 60); } // get a batch of feeders if (!($feeders = Servers::list_for_feed(0, 1, 'feed'))) { return 'feeds/feeds.php: no feed has been defined' . BR; } // remember start time $start_time = get_micro_time(); // list banned tokens $banned_pattern = Servers::get_banned_pattern(); // browse each feed $count = 0; foreach ($feeders as $server_id => $attributes) { // get specific feed parameters list($feed_url, $feed_title, $anchor, $stamp) = $attributes; // skip servers processed recently if ($stamp > $threshold) { continue; } // flag this record to enable round-robin even on error Servers::stamp($server_id); // fetch news from the provided link if (!($news = Feeds::get_remote_news_from($feed_url)) || !is_array($news)) { continue; } // no anchor has been defined for this feed if (!$anchor) { // create a default section if necessary if (!($anchor = Sections::lookup('external_news'))) { $fields = array(); $fields['nick_name'] = 'external_news'; $fields['create_date'] = gmstrftime('%Y-%m-%d %H:%M:%S', time()); $fields['edit_date'] = gmstrftime('%Y-%m-%d %H:%M:%S', time()); $fields['index_map'] = 'N'; $fields['locked'] = 'Y'; // no direct contributions $fields['rank'] = 40000; // at the end of the list $fields['title'] = i18n::c('External News'); $fields['description'] = i18n::c('Received from feeding servers'); if (!($fields['id'] = Sections::post($fields))) { Logger::remember('feeds/feeds.php: Impossible to add a section.'); return; } $anchor = 'section:' . $fields['id']; } } // process retrieved links $links = 0; foreach ($news as $item) { // link has to be valid if (!isset($item['link']) || !($item['title'] . $item['description'])) { if (isset($context['debug_feeds']) && $context['debug_feeds'] == 'Y') { Logger::remember('feeds/feeds.php: feed item is invalid', $item, 'debug'); } continue; } // skip banned servers if ($banned_pattern && preg_match($banned_pattern, $item['link'])) { if (isset($context['debug_feeds']) && $context['debug_feeds'] == 'Y') { Logger::remember('feeds/feeds.php: feed host has been banned', $item['link'], 'debug'); } continue; } // one link processed $links++; // link description $fields = array(); $fields['anchor'] = $anchor; $fields['link_url'] = $item['link']; $fields['title'] = $item['title']; $fields['description'] = $item['description']; if ($item['category']) { $fields['description'] .= ' (' . $item['category'] . ')'; } $fields['edit_name'] = $feed_title; $fields['edit_address'] = $feed_url; $fields['edit_action'] = 'link:feed'; if ($item['pubDate']) { $fields['edit_date'] = gmstrftime('%Y-%m-%d %H:%M:%S', strtotime($item['pubDate'])); } // update links that already exist in the database if (Links::have($item['link'], $anchor, $fields)) { continue; } // save link in the database if (!Links::post($fields)) { Logger::remember('feeds/feeds.php: Impossible to save feed link: ' . Logger::error_pop()); } } // one feed has been processed $count += 1; // remember tick date Values::set('feeds.tick.' . $feed_url, $links); } // cap the number of links used for news if (!isset($context['maximum_news']) || !$context['maximum_news']) { $context['maximum_news'] = 1000; } if ($context['maximum_news'] > 10) { include_once $context['path_to_root'] . 'links/links.php'; Links::purge_old_news($context['maximum_news']); } // compute execution time $time = round(get_micro_time() - $start_time, 2); // report on work achieved if ($count > 1) { return 'feeds/feeds.php: ' . $count . ' feeds have been processed (' . $time . ' seconds)' . BR; } elseif ($count == 1) { return 'feeds/feeds.php: 1 feed has been processed (' . $time . ' seconds)' . BR; } else { return 'feeds/feeds.php: nothing to do (' . $time . ' seconds)' . BR; } }
/** * compare two sets of lines by finding the longest common sequence * * @param string or array the left set * @param string or array the right set * @param int maximm number of lines to consider * @return an array of ('=', $left, $right) or ('-', $left, '-') or ('+', '-', $right) */ public static function &compare($old_stream, $new_stream, $maximum = 500) { $start_time = get_micro_time(); // the resulting sequence $sequence = array(); // make lists of nodes if (is_array($old_stream)) { $old_lines = $old_stream; } else { $old_lines = explode("\n", $old_stream); } if (is_array($new_stream)) { $new_lines = $new_stream; } else { $new_lines = explode("\n", $new_stream); } // don't count things too many times $old_lines_count = count($old_lines); $new_lines_count = count($new_lines); // hash nodes for ($i = $old_lines_count - 1; $i >= 0; $i--) { $old_lines[$i] = rtrim($old_lines[$i]); $old_hash[$i] = md5(strtolower(trim($old_lines[$i]))); } // ensure enough execution time Safe::set_time_limit(30); for ($j = $new_lines_count - 1; $j >= 0; $j--) { $new_lines[$j] = rtrim($new_lines[$j]); $new_hash[$j] = md5(strtolower(trim($new_lines[$j]))); } // ensure enough execution time Safe::set_time_limit(30); // skip the head of common nodes $head = 0; while ($head < $old_lines_count && $head < $new_lines_count) { if ($old_hash[$head] == $new_hash[$head]) { $head++; } else { break; } } // skip the tail of common nodes $tail = 0; $oindex = $old_lines_count; $nindex = $new_lines_count; while (--$oindex > $head && --$nindex > $head) { if ($old_hash[$oindex] == $new_hash[$nindex]) { $tail++; } else { break; } } // compute lengths $lengths = array(); $lengths[0][0] = 0; $lengths[0][1] = 0; $i_count = min($maximum, $old_lines_count - $head - $tail); $j_count = min($maximum, $new_lines_count - $head - $tail); for ($i = $i_count; $i >= 0; $i--) { $lengths[$i + 1][$j_count + 1] = 0; $lengths[$i + 1][$j_count] = 0; $lengths[$i][$j_count + 1] = 0; for ($j = $j_count; $j >= 0; $j--) { if ($i == $i_count || $j == $j_count) { $lengths[$i][$j] = 0; } elseif ($old_hash[$i + $head] == $new_hash[$j + $head]) { $lengths[$i][$j] = 1 + $lengths[$i + 1][$j + 1]; } else { $lengths[$i][$j] = max($lengths[$i + 1][$j], $lengths[$i][$j + 1]); } } } // ensure enough execution time Safe::set_time_limit(30); // parse the resulting matrix $i = $j = 0; while ($i < $old_lines_count && $j < $new_lines_count) { // same nodes if ($old_hash[$i] == $new_hash[$j]) { $sequence[] = array('=', $old_lines[$i], $new_lines[$j]); $i++; $j++; // one node has been deleted } elseif (isset($lengths[$i - $head + 1][$j - $head]) && $lengths[$i - $head + 1][$j - $head] >= $lengths[$i - $head][$j - $head + 1]) { $sequence[] = array('-', $old_lines[$i], '-'); $i++; // one node has been inserted } else { $sequence[] = array('+', '-', $new_lines[$j]); $j++; } } // other nodes that have been removed while ($i < $old_lines_count) { $sequence[] = array('-', $old_lines[$i++], '-'); } // nodes that have been appended while ($j < $new_lines_count) { $sequence[] = array('+', '-', $new_lines[$j++]); } // return the whole diff sequence return $sequence; }