summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2009-08-22 16:01:10 +0000
committerDries Buytaert <dries@buytaert.net>2009-08-22 16:01:10 +0000
commit4c028a19fbe9d5fc81f21bdce862f6c74a61b7ee (patch)
treeaaf42312065ef361c46e4176092103a18c761798 /modules
parent06d296d5f96ee2a580e5d0c7a799775f52d71379 (diff)
downloadbrdo-4c028a19fbe9d5fc81f21bdce862f6c74a61b7ee.tar.gz
brdo-4c028a19fbe9d5fc81f21bdce862f6c74a61b7ee.tar.bz2
- Patch #331611 by sun, joshmiller, TheRec, Rob Loach, Damien Tournoud: add a poormanscron-like feature to core.
Diffstat (limited to 'modules')
-rw-r--r--modules/system/system.admin.inc21
-rw-r--r--modules/system/system.module115
-rw-r--r--modules/system/system.test53
3 files changed, 185 insertions, 4 deletions
diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc
index 88c8f2f70..9bd326ff2 100644
--- a/modules/system/system.admin.inc
+++ b/modules/system/system.admin.inc
@@ -1313,7 +1313,16 @@ function system_site_information_settings() {
'#description' => t('Render all blocks on the default 404 (not found) page. Disabling blocks can help with performance but might leave users with a less functional site.'),
'#default_value' => variable_get('site_404_blocks', 0)
);
+ $form['cron_safe_threshold'] = array(
+ '#type' => 'select',
+ '#title' => t('Automatically run cron'),
+ '#default_value' => variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD),
+ '#options' => array(0 => t('Never')) + drupal_map_assoc(array(3600, 10800, 21600, 43200, 86400, 604800), 'format_interval'),
+ '#description' => t('When enabled, the site will check whether cron has been run in the configured interval and automatically run it upon the next page request. For more information visit the <a href="@status-report-url">status report page</a>.', array('@status-report-url' => url('admin/reports/status'))),
+ );
+
$form['#validate'][] = 'system_site_information_settings_validate';
+ $form['#submit'][] = 'system_site_information_settings_submit';
return system_settings_form($form);
}
@@ -1334,6 +1343,18 @@ function system_site_information_settings_validate($form, &$form_state) {
}
/**
+ * Form submit handler for the site-information form.
+ */
+function system_site_information_settings_submit($form, &$form_state) {
+ // Clear caches when the cron threshold is changed to ensure that the cron
+ // image is not contained in cached pages.
+ $cron_threshold = variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD);
+ if (($cron_threshold > 0 && $form_state['input']['cron_safe_threshold'] == 0) || ($cron_threshold == 0 && $form_state['input']['cron_safe_threshold'] > 0)) {
+ cache_clear_all();
+ }
+}
+
+/**
* Form builder; Configure error reporting settings.
*
* @ingroup forms
diff --git a/modules/system/system.module b/modules/system/system.module
index 5088b4cf8..e768944e3 100644
--- a/modules/system/system.module
+++ b/modules/system/system.module
@@ -42,6 +42,11 @@ define('DRUPAL_MINIMUM_PGSQL', '8.3');
define('DRUPAL_MAXIMUM_TEMP_FILE_AGE', 21600);
/**
+ * Default interval for automatic cron executions in seconds.
+ */
+define('DRUPAL_CRON_DEFAULT_THRESHOLD', 10800);
+
+/**
* New users will be set to the default time zone at registration.
*/
define('DRUPAL_USER_TIMEZONE_DEFAULT', 0);
@@ -196,6 +201,9 @@ function system_theme() {
'arguments' => array('version' => NULL),
),
'system_compact_link' => array(),
+ 'system_run_cron_image' => array(
+ 'arguments' => array('image_path' => NULL),
+ ),
));
}
@@ -496,6 +504,12 @@ function system_menu() {
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
+ $items['system/run-cron-image'] = array(
+ 'title' => 'Execute cron',
+ 'page callback' => 'system_run_cron_image',
+ 'access callback' => 'system_run_cron_image_access',
+ 'type' => MENU_CALLBACK,
+ );
$items['admin'] = array(
'title' => 'Administer',
'access arguments' => array('access administration pages'),
@@ -2968,3 +2982,104 @@ function system_retrieve_file($url, $destination = NULL, $overwrite = TRUE) {
return $local;
}
+
+/**
+ * Implement hook_page_alter().
+ */
+function system_page_alter(&$page) {
+ // Automatic cron runs.
+ // @see system_run_cron_image()
+ if (system_run_cron_image_access()) {
+ $page['page_bottom']['run_cron'] = array(
+ // Trigger cron run via AJAX.
+ '#attached_js' => array(
+ '(function($){ $.get(' . drupal_to_js(url('system/run-cron-image')) . '); })(jQuery);' => array('type' => 'inline', 'scope' => 'header'),
+ ),
+ // Trigger cron run for clients not supporting JavaScript (fall-back).
+ '#markup' => theme('system_run_cron_image', 'system/run-cron-image'),
+ );
+ }
+}
+
+/**
+ * Menu callback; executes cron via an image callback.
+ *
+ * This callback runs cron in a separate HTTP request to prevent "mysterious"
+ * slow-downs of regular HTTP requests. It is either invoked via an AJAX request
+ * (if the client's browser supports JavaScript) or by an IMG tag directly in
+ * the page output (for clients not supporting JavaScript). For the latter case,
+ * we need to output a transparent 1x1 image, so the browser does not render the
+ * image's alternate text or a 'missing image placeholder'. The AJAX request
+ * does not process the returned output.
+ *
+ * @see system_page_alter()
+ * @see theme_system_run_cron_image()
+ * @see system_run_cron_image_access()
+ */
+function system_run_cron_image() {
+ drupal_page_is_cacheable(FALSE);
+
+ // Output a transparent 1x1 image to the browser; required for clients not
+ // supporting JavaScript.
+ drupal_set_header('Content-Type', 'image/gif');
+ echo "\x47\x49\x46\x38\x39\x61\x1\x0\x1\x0\x80\xff\x0\xc0\xc0\xc0\x0\x0\x0\x21\xf9\x4\x1\x0\x0\x0\x0\x2c\x0\x0\x0\x0\x1\x0\x1\x0\x0\x2\x2\x44\x1\x0\x3b";
+
+ // Cron threshold semaphore is used to avoid errors every time the image
+ // callback is displayed when a previous cron is still running.
+ $threshold_semaphore = variable_get('cron_threshold_semaphore', FALSE);
+ if ($threshold_semaphore) {
+ if (REQUEST_TIME - $threshold_semaphore > 3600) {
+ // Either cron has been running for more than an hour or the semaphore
+ // was not reset due to a database error.
+ watchdog('cron', 'Cron has been running for more than an hour and is most likely stuck.', array(), WATCHDOG_ERROR);
+
+ // Release the cron threshold semaphore.
+ variable_del('cron_threshold_semaphore');
+ }
+ }
+ else {
+ // Run cron automatically if it has never run or threshold was crossed.
+ $cron_last = variable_get('cron_last', NULL);
+ $cron_threshold = variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD);
+ if (!isset($cron_last) || (REQUEST_TIME - $cron_last > $cron_threshold)) {
+ // Lock cron threshold semaphore.
+ variable_set('cron_threshold_semaphore', REQUEST_TIME);
+ drupal_cron_run();
+ // Release the cron threshold semaphore.
+ variable_del('cron_threshold_semaphore');
+ }
+ }
+
+ exit;
+}
+
+/**
+ * Checks if the feature to automatically run cron is enabled.
+ *
+ * Also used as a menu access callback for this feature.
+ *
+ * @return
+ * TRUE if cron threshold is enabled, FALSE otherwise.
+ *
+ * @see system_run_cron_image()
+ * @see system_page_alter()
+ */
+function system_run_cron_image_access() {
+ return variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD) > 0;
+}
+
+/**
+ * Display image used to run cron automatically.
+ *
+ * Renders an image pointing to the automatic cron run menu callback for
+ * graceful degradation when Javascript is not available. The wrapping NOSCRIPT
+ * tag ensures that only browsers not supporting JavaScript render the image.
+ *
+ * @see system_page_alter()
+ * @see system_run_cron_image()
+ * @ingroup themeable
+ */
+function theme_system_run_cron_image($image_path) {
+ return '<noscript><div id="system-cron-image">' . theme('image', $image_path, '', '', array(), FALSE) . '</div></noscript>';
+}
+
diff --git a/modules/system/system.test b/modules/system/system.test
index 2c642adff..92d0e32dc 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -372,6 +372,7 @@ class CronRunTestCase extends DrupalWebTestCase {
*/
function testCronRun() {
global $base_url;
+
// Run cron anonymously without any cron key.
$this->drupalGet($base_url . '/cron.php', array('external' => TRUE));
$this->assertResponse(403);
@@ -391,13 +392,57 @@ class CronRunTestCase extends DrupalWebTestCase {
}
/**
+ * Follow every image paths in the previously retrieved content.
+ */
+ function drupalGetAllImages() {
+ foreach ($this->xpath('//img') as $image) {
+ $this->drupalGet($this->getAbsoluteUrl($image['src']));
+ }
+ }
+
+ /**
+ * Ensure that the cron image callback to run it automatically is working.
+ *
+ * In these tests we do not use REQUEST_TIME to track start time, because we
+ * need the exact time when cron is triggered.
+ */
+ function testCronThreshold() {
+ // Ensure cron does not run when the cron threshold is enabled and was
+ // not passed.
+ $start_cron_last = time();
+ variable_set('cron_last', $start_cron_last);
+ variable_set('cron_safe_threshold', 10);
+ $this->drupalGet('');
+ // Follow every image path on the page.
+ $this->drupalGetAllImages();
+ $this->assertTrue($start_cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is not passed.'));
+
+ // Test if cron runs when the cron threshold was passed.
+ $start_cron_last = time() - 15;
+ variable_set('cron_last', $start_cron_last);
+ $this->drupalGet('');
+ // Follow every image path on the page.
+ $this->drupalGetAllImages();
+ $this->assertTrue(variable_get('cron_last', NULL) > $start_cron_last, t('Cron runs when the cron threshold is passed.'));
+
+ // Test if cron does not run when the cron threshold was is disabled.
+ $start_cron_last = time() - 15;
+ variable_set('cron_safe_threshold', 0);
+ variable_set('cron_last', $start_cron_last);
+ $this->drupalGet('');
+ // Follow every image path on the page.
+ $this->drupalGetAllImages();
+ $this->assertTrue($start_cron_last == variable_get('cron_last', NULL), t('Cron does not run when the cron threshold is disabled.'));
+ }
+
+ /**
* Ensure that temporary files are removed.
+ *
+ * Create files for all the possible combinations of age and status. We are
+ * using UPDATE statments rather than file_save() because it would set the
+ * timestamp.
*/
function testTempFileCleanup() {
- // Create files for all the possible combinations of age and status. We're
- // using UPDATE statments rather than file_save() because it would set the
- // timestamp.
-
// Temporary file that is older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
$temp_old = file_save_data('');
db_update('file')