diff --git a/cron_example/cron_example.info.yml b/cron_example/cron_example.info.yml new file mode 100644 index 0000000..19bb5b4 --- /dev/null +++ b/cron_example/cron_example.info.yml @@ -0,0 +1,5 @@ +name: Cron example +type: module +description: 'Demonstrates hook_cron() and related features' +package: Example modules +core: 8.x diff --git a/cron_example/cron_example.module b/cron_example/cron_example.module new file mode 100644 index 0000000..871f532 --- /dev/null +++ b/cron_example/cron_example.module @@ -0,0 +1,134 @@ +getEditable('examples.cron'); + // Default to an hourly interval. Of course, cron has to be running at least + // hourly for this to work. + $interval = $cron_config->get('interval'); + $interval = !empty($interval) ? $interval : 3600; + + // We usually don't want to act every time cron runs (which could be every + // minute) so keep a time for the next run in a variable. + $next_execution = $cron_config->get('next_execution'); + $next_execution = !empty($next_execution) ? $next_execution : 0; + if (REQUEST_TIME >= $next_execution) { + // This is a silly example of a cron job. + // It just makes it obvious that the job has run without + // making any changes to your database. + \Drupal::logger('cron_example')->notice('cron_example ran'); + if (!empty($GLOBALS['cron_example_show_status_message'])) { + drupal_set_message(t('cron_example executed at %time', ['%time' => date_iso8601(REQUEST_TIME)])); + } + $cron_config->set('next_execution', REQUEST_TIME + $interval)->save(); + } +} + + +/** + * Implements hook_queue_info(). + * + * hook_queue_info() and family are new since Drupal 7, and allow any + * process to add work to the queue to be acted on when cron runs. Queues are + * described and worker callbacks are provided, and then only the worker + * callback needs to be implemented. + * + * All the details of queue use are done by the cron_queue implementation, so + * one doesn't need to know much about DrupalQueue(). + * + * @see queue_example.module + */ +function cron_example_queue_info() { + $queues['cron_example_queue_1'] = [ + 'worker callback' => 'cron_example_queue_1_worker', + 'cron' => [ + // One second max runtime per cron run. + 'time' => 1, + ], + ]; + $queues['cron_example_queue_2'] = [ + 'worker callback' => 'cron_example_queue_2_worker', + 'cron' => [ + 'time' => 20, + ], + ]; + return $queues; +} + +/** + * Queue worker for Queue 1. + * + * @param object $item + * Any object to be worked on. + */ +function cron_example_queue_1_worker($item) { + cron_example_queue_report_work(1, $item); +} + +/** + * Queue worker for Queue 2. + * + * @param object $item + * Any object to be worked on. + */ +function cron_example_queue_2_worker($item) { + cron_example_queue_report_work(2, $item); +} + +/** + * Simple reporter for the workers. + * + * @param int $worker + * Worker number. + * @param object $item + * The $item which was stored in the cron queue. + */ +function cron_example_queue_report_work($worker, $item) { + if (!empty($GLOBALS['cron_example_show_status_message'])) { + drupal_set_message( + t('Queue @worker worker processed item with sequence @sequence created at @time', + [ + '@worker' => $worker, + '@sequence' => $item->sequence, + '@time' => date_iso8601($item->created), + ] + ) + ); + } + \Drupal::logger('cron_example')->info('Queue @worker worker processed item with sequence @sequence created at @time', + [ + '@worker' => $worker, + '@sequence' => $item->sequence, + '@time' => date_iso8601($item->created), + ] + ); +} + +/** + * @} End of "defgroup cron_example". + */ diff --git a/cron_example/cron_example.routing.yml b/cron_example/cron_example.routing.yml new file mode 100644 index 0000000..29dac35 --- /dev/null +++ b/cron_example/cron_example.routing.yml @@ -0,0 +1,6 @@ +cron_example: + path: 'examples/cron_example' + defaults: + _form: '\Drupal\cron_example\Form\CronExampleForm' + requirements: + _access: 'TRUE' diff --git a/cron_example/src/Form/CronExampleForm.php b/cron_example/src/Form/CronExampleForm.php new file mode 100644 index 0000000..a23b9a4 --- /dev/null +++ b/cron_example/src/Form/CronExampleForm.php @@ -0,0 +1,234 @@ +setConfigFactory($config_factory); + $this->currentUser = $current_user; + $this->cron = $cron; + $this->queue = $queue; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('config.factory'), + $container->get('current_user'), + $container->get('cron'), + $container->get('queue') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'cron_example'; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $config = $this->configFactory->get('examples.cron'); + + $form['status'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Cron status information'), + ]; + $form['status']['intro'] = [ + '#type' => 'item', + '#markup' => $this->t('The cron example demonstrates hook_cron() and hook_queue_info() processing. If you have administrative privileges you can run cron from this page and see the results.'), + ]; + + $next_execution = $config->get('next_execution'); + $next_execution = !empty($next_execution) ? $next_execution : REQUEST_TIME; + $args = [ + '%time' => date_iso8601($config->get('next_execution')), + '%seconds' => $next_execution - REQUEST_TIME, + ]; + $form['status']['last'] = [ + '#type' => 'item', + '#markup' => $this->t('cron_example_cron() will next execute the first time cron runs after %time (%seconds seconds from now)', $args), + ]; + + if ($this->currentUser->hasPermission('administer site configuration')) { + $form['cron_run'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Run cron manually'), + ]; + $form['cron_run']['cron_reset'] = [ + '#type' => 'checkbox', + '#title' => $this->t("Run cron_example's cron regardless of whether interval has expired."), + '#default_value' => FALSE, + ]; + $form['cron_run']['cron_trigger'] = [ + '#type' => 'submit', + '#value' => $this->t('Run cron now'), + '#submit' => [[$this, 'cronRun']], + ]; + } + + $form['cron_queue_setup'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Cron queue setup (for hook_cron_queue_info(), etc.)'), + ]; + $queue_1 = \Drupal::queue('cron_example_queue_1'); + $queue_2 = \Drupal::queue('cron_example_queue_2'); + $args = [ + '%queue_1' => $queue_1->numberOfItems(), + '%queue_2' => $queue_2->numberOfItems(), + ]; + $form['cron_queue_setup']['current_cron_queue_status'] = [ + '#type' => 'item', + '#markup' => $this->t('There are currently %queue_1 items in queue 1 and %queue_2 items in queue 2', $args), + ]; + $form['cron_queue_setup']['num_items'] = [ + '#type' => 'select', + '#title' => $this->t('Number of items to add to queue'), + '#options' => array_combine([1, 5, 10, 100, 1000], [1, 5, 10, 100, 1000]), + '#default_value' => 5, + ]; + $form['cron_queue_setup']['queue'] = [ + '#type' => 'radios', + '#title' => $this->t('Queue to add items to'), + '#options' => [ + 'cron_example_queue_1' => $this->t('Queue 1'), + 'cron_example_queue_2' => $this->t('Queue 2'), + ], + '#default_value' => 'cron_example_queue_1', + ]; + $form['cron_queue_setup']['submit'] = [ + '#type' => 'submit', + '#value' => $this->t('Add jobs to queue'), + '#submit' => [[$this, 'addItems']], + ]; + + $form['configuration'] = [ + '#type' => 'fieldset', + '#title' => $this->t('Configuration of cron_example_cron()'), + ]; + $form['configuration']['cron_example_interval'] = [ + '#type' => 'select', + '#title' => $this->t('Cron interval'), + '#description' => $this->t('Time after which cron_example_cron will respond to a processing request.'), + '#default_value' => $config->get('interval'), + '#options' => [ + 60 => $this->t('1 minute'), + 300 => $this->t('5 minutes'), + 3600 => $this->t('1 hour'), + 86400 => $this->t('1 day'), + ], + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * Allow user to directly execute cron, optionally forcing it. + */ + public function cronRun(array &$form, FormStateInterface &$form_state) { + $config = $this->configFactory->get('examples.cron'); + + $cron_reset = $form_state->getValue('cron_reset'); + if (!empty($cron_reset)) { + $config->set('next_execution', 0); + } + + // We don't usually use globals in this way. This is used here only to + // make it easy to tell if cron was run by this form. + $GLOBALS['cron_example_show_status_message'] = TRUE; + if ($this->cron->run()) { + drupal_set_message($this->t('Cron ran successfully.')); + } + else { + drupal_set_message($this->t('Cron run failed.'), 'error'); + } + } + + /** + * Add the items to the queue when signaled by the form. + */ + public function addItems(array &$form, FormStateInterface &$form_state) { + $values = $form_state->getValues(); + $queue_name = $form['cron_queue_setup']['queue'][$values['queue']]['#title']; + $num_items = $form_state->getValue('num_items'); + + $queue = $this->queue->get($values['queue']); + + for ($i = 1; $i <= $num_items; $i++) { + $item = new \stdClass(); + $item->created = REQUEST_TIME; + $item->sequence = $i; + $queue->createItem($item); + } + + $args = [ + '%num' => $num_items, + '%queue' => $queue_name, + ]; + drupal_set_message($this->t('Added %num items to %queue', $args)); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->configFactory->get('examples.cron') + ->set('interval', $form_state->getValue('cron_example_interval')) + ->save(); + + parent::submitForm($form, $form_state); + } + + /** + * @{inheritdoc} + */ + protected function getEditableConfigNames() { + // TODO: Implement getEditableConfigNames() method. + } + +} diff --git a/cron_example/src/Tests/CronExampleTestCase.php b/cron_example/src/Tests/CronExampleTestCase.php new file mode 100644 index 0000000..e8c7434 --- /dev/null +++ b/cron_example/src/Tests/CronExampleTestCase.php @@ -0,0 +1,101 @@ + 'Cron example functionality', + 'description' => 'Test the functionality of the Cron Example.', + 'group' => 'Examples', + ]; + } + + /** + * {@inheritdoc} + */ + public function setUp() { + parent::setUp(); + // Create user. Search content permission granted for the search block to + // be shown. + $this->webUser = $this->drupalCreateUser(['administer site configuration']); + $this->drupalLogin($this->webUser); + + $this->cron_config = \Drupal::configFactory()->getEditable('examples.cron'); + } + + /** + * Create an example node, test block through admin and user interfaces. + */ + public function testCronExampleBasic() { + // Pretend that cron has never been run (even though simpletest seems to + // run it once...) + $this->cron_config->set('cron_example_next_execution', 0); + $this->drupalGet('examples/cron_example'); + + // Initial run should cause cron_example_cron() to fire. + $post = []; + $this->drupalPostForm('examples/cron_example', $post, t('Run cron now')); + $this->assertText($this->t('cron_example executed at')); + + // Forcing should also cause cron_example_cron() to fire. + $post['cron_reset'] = TRUE; + $this->drupalPostForm(NULL, $post, $this->t('Run cron now')); + $this->assertText($this->t('cron_example executed at')); + + // But if followed immediately and not forced, it should not fire. + $post['cron_reset'] = FALSE; + $this->drupalPostForm(NULL, $post, $this->t('Run cron now')); + $this->assertNoText($this->t('cron_example executed at')); + + $this->assertText($this->t('There are currently 0 items in queue 1 and 0 items in queue 2')); + $post = [ + 'num_items' => 5, + 'queue' => 'cron_example_queue_1', + ]; + $this->drupalPostForm(NULL, $post, $this->t('Add jobs to queue')); + $this->assertText('There are currently 5 items in queue 1 and 0 items in queue 2'); + $post = [ + 'num_items' => 100, + 'queue' => 'cron_example_queue_2', + ]; + $this->drupalPostForm(NULL, $post, $this->t('Add jobs to queue')); + $this->assertText('There are currently 5 items in queue 1 and 100 items in queue 2'); + + $post = []; + $this->drupalPostForm('examples/cron_example', $post, $this->t('Run cron now')); + $this->assertPattern('/Queue 1 worker processed item with sequence 5 /'); + $this->assertPattern('/Queue 2 worker processed item with sequence 100 /'); + } + +} + +/** + * @} End of "addtogroup cron_example". + */