diff --git a/scheduler.info b/scheduler.info index 062d8cc..6853243 100644 --- a/scheduler.info +++ b/scheduler.info @@ -7,3 +7,4 @@ files[] = scheduler.module files[] = scheduler.views.inc files[] = scheduler.test files[] = scheduler_handler_field_scheduler_countdown.inc +test_dependencies[] = date diff --git a/scheduler.install b/scheduler.install index 484be85..6ffbf60 100644 --- a/scheduler.install +++ b/scheduler.install @@ -47,11 +47,16 @@ function scheduler_schema() { * Implements hook_uninstall(). */ function scheduler_uninstall() { + // Keep variables in alphabetic order for easier maintenance and patching. $variables = array( + 'scheduler_allow_date_only', 'scheduler_date_format', - 'scheduler_field_type', + 'scheduler_date_only_format', + 'scheduler_default_time', 'scheduler_extra_info', + 'scheduler_field_type', 'scheduler_lightweight_log', + 'scheduler_time_only_format', ); $types = node_type_get_types(); diff --git a/scheduler.module b/scheduler.module index d1e9d0e..8bd832b 100644 --- a/scheduler.module +++ b/scheduler.module @@ -5,8 +5,17 @@ * Scheduler publishes and unpublishes nodes on dates specified by the user. */ +// The default format to use if no custom format has been configured. define('SCHEDULER_DATE_FORMAT', 'Y-m-d H:i:s'); +// The default date and time formats to use when only a date has been entered. +// These should match the date and time parts of SCHEDULER_DATE_FORMAT above. +define('SCHEDULER_DATE_ONLY_FORMAT', 'Y-m-d'); +define('SCHEDULER_TIME_ONLY_FORMAT', 'H:i:s'); + +// The default time that will be used, until Admin sets a different value. +define('SCHEDULER_DEFAULT_TIME', '00:00:00'); + /** * Implements hook_permission(). */ @@ -148,6 +157,7 @@ function scheduler_admin() { '#default_value' => variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT), '#size' => 20, '#maxlength' => 20, + '#required' => TRUE, '#description' => t('The input format for the (un)scheduling time/date. See the date() function for formatting options: http://www.php.net/manual/en/function.date.php (only the following format characters are supported (don\'t use \'G\', \'a\' or \'A\' with Date Popup): djmnyYhHgGisaA)'), ); @@ -171,12 +181,42 @@ function scheduler_admin() { $form['scheduler_date_format']['#description'] .= t('If you are using Date Popup, the following time formats are supported: !formats', array('!formats' => $acceptable)); } + // Options for setting date-only with default time. + $form['scheduler_date_only_fieldset'] = array( + '#type' => 'fieldset', + '#title' => t('Date only'), + '#collapsible' => FALSE, + ); + $form['scheduler_date_only_fieldset']['scheduler_allow_date_only'] = array( + '#type' => 'checkbox', + '#title' => t('Allow users to enter only a date and provide a default time.'), + '#default_value' => variable_get('scheduler_allow_date_only', FALSE), + '#description' => t('When only a date is entered the time will default to a specified value, but the user can change this if required.'), + ); + $form['scheduler_date_only_fieldset']['scheduler_default_time'] = array( + '#type' => 'textfield', + '#title' => t('Default time'), + '#default_value' => variable_get('scheduler_default_time', SCHEDULER_DEFAULT_TIME), + '#size' => 20, + '#maxlength' => 20, + '#description' => t('This is the time that will be used if the user does not enter a value. Format: HH:MM:SS.'), + '#states' => array( + 'visible' => array( + ':input[name="scheduler_allow_date_only"]' => array('checked' => TRUE), + ), + ), + ); + $form['scheduler_extra_info'] = array( '#type' => 'textarea', '#title' => t('Extra Info'), '#default_value' => variable_get('scheduler_extra_info', ''), '#description' => t('The text entered into this field will be displayed above the scheduling fields in the node edit form.'), ); + + // Add a submit handler function. + $form['#submit'][] = 'scheduler_admin_submit'; + return system_settings_form($form); } @@ -192,10 +232,55 @@ function scheduler_admin_validate($form, &$form_state) { $time_format = date_limit_format($format, array('hour', 'minute', 'second')); $acceptable = date_popup_time_formats(); - if (!in_array($time_format, $acceptable)) { + if ($time_format && !in_array($time_format, $acceptable)) { form_set_error('scheduler_date_format', t('The Date Popup module only accepts the following formats: !formats', array('!formats' => implode($acceptable, ', ')))); } + if ($time_format == '' && !$form_state['values']['scheduler_allow_date_only']) { + // When using date popup check that either the main format has a time + // part or the date-only option is turned on. + form_set_error('scheduler_date_format', t('When using Date Popup you must either include a time within the date format or enable the date-only option')); + } } + + // If date-only is enabled then check if a valid default time was entered. + // Allow leading zeros to be omitted, eg. 6:30:00 is considered valid. + if ($form_state['values']['scheduler_allow_date_only']) { + $default_time = strptime($form_state['values']['scheduler_default_time'], '%H:%M:%S'); + if (!$default_time) { + form_set_error('scheduler_default_time', t('The default time should be in the format HH:MM:SS')); + } + else { + // Insert any possibly omitted leading zeroes. + $unix_time = mktime($default_time['tm_hour'], $default_time['tm_min'], $default_time['tm_sec']); + $form_state['values']['scheduler_default_time'] = format_date($unix_time, 'custom', 'H:i:s'); + } + } +} + +/** + * Submit handler for scheduler admin settings. + */ +function scheduler_admin_submit($form, &$form_state) { + // Extract the date part and time part of the full format, for use with the + // default time functionality. Assume the date and time time parts begin + // and end with a letter, but any punctuation between these will be retained. + $format = $form_state['values']['scheduler_date_format']; + $time_letters = 'gGhHisaA'; + $time_start = strcspn($format, $time_letters); + $time_length = strlen($format) - strcspn(strrev($format), $time_letters) - $time_start; + $time_only_format = substr($format, $time_start, $time_length); + variable_set('scheduler_time_only_format', $time_only_format); + + $date_letters = 'djFmMnyY'; + $date_start = strcspn($format, $date_letters); + $date_length = strlen($format) - strcspn(strrev($format), $date_letters) - $date_start; + $date_only_format = substr($format, $date_start, $date_length); + variable_set('scheduler_date_only_format', $date_only_format); + + drupal_set_message(t('The date part of the Scheduler format is %date_part and the time part is %time_part', array( + '%date_part' => $date_only_format, + '%time_part' => $time_only_format, + ))); } /** @@ -288,6 +373,9 @@ function scheduler_form_alter(&$form, $form_state) { $node = $form['#node']; $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT); + $date_only_format = variable_get('scheduler_date_only_format', SCHEDULER_DATE_ONLY_FORMAT); + $time_only_format = variable_get('scheduler_time_only_format', SCHEDULER_TIME_ONLY_FORMAT); + $date_only_allowed = variable_get('scheduler_allow_date_only', FALSE); $use_date_popup = _scheduler_use_date_popup(); $internal_date_format = $use_date_popup ? SCHEDULER_DATE_FORMAT : $date_format; @@ -353,11 +441,31 @@ function scheduler_form_alter(&$form, $form_state) { ); } - $description_format = t('Format: %time.', array('%time' => format_date(time(), 'custom', $date_format))); + // Define the descriptions depending on whether the time can be skipped. + $descriptions = array(); + if ($date_only_allowed && ($date_only_format != $date_format)) { + $descriptions['format'] = t('Format: %date_only_format or %standard_format.', array( + '%date_only_format' => format_date(time(), 'custom', $date_only_format), + '%standard_format' => format_date(time(), 'custom', $date_format), + )); + } + else { + $descriptions['format'] = t('Format: %standard_format.', array( + '%standard_format' => format_date(time(), 'custom', $date_format), + )); + } + // Show the default time so users know what they will get if they do not + // enter a time. + if ($date_only_allowed) { + $default_time = strtotime(variable_get('scheduler_default_time', SCHEDULER_DEFAULT_TIME)); + $descriptions['default'] = t('The default time is @default_time.', array( + '@default_time' => format_date($default_time, 'custom', $time_only_format ? $time_only_format : SCHEDULER_TIME_ONLY_FORMAT), + )); + } + if ($publishing_enabled) { - $description_blank = ''; if (!$publishing_required) { - $description_blank .= ' ' . t('Leave blank to disable scheduled publishing.'); + $descriptions['blank'] = t('Leave the date blank for no scheduled publishing.'); } $form['scheduler_settings']['publish_on'] = array( @@ -366,14 +474,25 @@ function scheduler_form_alter(&$form, $form_state) { '#maxlength' => 25, '#required' => $publishing_required, '#default_value' => isset($defaults->publish_on) && $defaults->publish_on ? format_date($defaults->publish_on, 'custom', $internal_date_format) : '', - '#description' => filter_xss($description_format . $description_blank), + '#description' => filter_xss(implode(' ', $descriptions)), ); + if ($use_date_popup) { + // Make this a popup calendar widget. + $form['scheduler_settings']['publish_on']['#type'] = 'date_popup'; + $form['scheduler_settings']['publish_on']['#date_format'] = $date_format; + $form['scheduler_settings']['publish_on']['#date_year_range'] = '0:+10'; + unset($descriptions['format']); + $form['scheduler_settings']['publish_on']['#description'] = filter_xss(implode(' ', $descriptions)); + unset($form['scheduler_settings']['publish_on']['#maxlength']); + } } if ($unpublishing_enabled) { - $description_blank = ''; if (!$unpublishing_required) { - $description_blank .= ' ' . t('Leave blank to disable scheduled unpublishing.'); + $descriptions['blank'] = t('Leave the date blank for no scheduled unpublishing.'); + } + else { + unset($descriptions['blank']); } $form['scheduler_settings']['unpublish_on'] = array( '#type' => 'textfield', @@ -381,28 +500,16 @@ function scheduler_form_alter(&$form, $form_state) { '#maxlength' => 25, '#required' => $unpublishing_required, '#default_value' => isset($defaults->unpublish_on) && $defaults->unpublish_on ? format_date($defaults->unpublish_on, 'custom', $internal_date_format) : '', - '#description' => filter_xss($description_format . $description_blank), + '#description' => filter_xss(implode(' ', $descriptions)), ); - } - if ($use_date_popup) { - // Make this a popup calendar widget if Date Popup module is enabled. - if ($publishing_enabled) { - $form['scheduler_settings']['publish_on']['#type'] = 'date_popup'; - $form['scheduler_settings']['publish_on']['#date_format'] = $date_format; - $form['scheduler_settings']['publish_on']['#date_year_range'] = '0:+10'; - if (!$publishing_required) { - $form['scheduler_settings']['publish_on']['#description'] = t('Leave blank to disable scheduled publishing.'); - } - unset($form['scheduler_settings']['publish_on']['#maxlength']); - } - if ($unpublishing_enabled) { + if ($use_date_popup) { + // Make this a popup calendar widget. $form['scheduler_settings']['unpublish_on']['#type'] = 'date_popup'; $form['scheduler_settings']['unpublish_on']['#date_format'] = $date_format; $form['scheduler_settings']['unpublish_on']['#date_year_range'] = '0:+10'; - if (!$unpublishing_required) { - $form['scheduler_settings']['unpublish_on']['#description'] = t('Leave blank to disable scheduled unpublishing.'); - } + unset($descriptions['format']); + $form['scheduler_settings']['unpublish_on']['#description'] = filter_xss(implode(' ', $descriptions)); unset($form['scheduler_settings']['unpublish_on']['#maxlength']); } } @@ -508,6 +615,29 @@ function _scheduler_strtotime($str) { } $str = trim(preg_replace('/\s+/', ' ', $str)); $time = _scheduler_strptime($str, $date_format); + + // If the time failed using the full $date_format or no time entry is + // allowed, but date-only with a default time is enabled, check if the input + // matches the "date only" date format. + $time_only_format = variable_get('scheduler_time_only_format', SCHEDULER_TIME_ONLY_FORMAT); + if ((!$time || $time_only_format == '') && variable_get('scheduler_allow_date_only', FALSE)) { + $date_only_format = variable_get('scheduler_date_only_format', SCHEDULER_DATE_ONLY_FORMAT); + if ($time = _scheduler_strptime($str, $date_only_format)) { + // A time has been calculated, but also check that there was only a + // date entered and no extra mal-formed time elements. + if ($str == date($date_only_format, $time)) { + // Parse the default time string into elements, then add the total + // offset in seconds to the timestamp. + $default_time = strptime(variable_get('scheduler_default_time', SCHEDULER_DEFAULT_TIME), '%H:%M:%S'); + $time_offset = ($default_time['tm_hour'] * 3600) + ($default_time['tm_min'] * 60) + $default_time['tm_sec']; + $time += $time_offset; + } + else { + // The date was not the only text entered, so reject it. + $time = FALSE; + } + } + } } else { // $str is empty @@ -1164,3 +1294,16 @@ function scheduler_field_attach_prepare_translation_alter($entity, $context) { $entity->unpublish_on = $source_entity->scheduler['unpublish_on']; } } + +/** + * Implements hook_date_popup_pre_validate_alter(). + */ +function scheduler_date_popup_pre_validate_alter($element, $form_state, &$input) { + // Provide a default time if this is enabled and the time field is empty. + if (variable_get('scheduler_allow_date_only', FALSE) && $element['#array_parents'][0] == 'scheduler_settings' && $input['date'] != '' && $input['time'] == '') { + // Get the default time as a timestamp number. + $default_time = strtotime(variable_get('scheduler_default_time', SCHEDULER_DEFAULT_TIME)); + // Set the time in the required format just as if the user had typed it. + $input['time'] = format_date($default_time, 'custom', variable_get('scheduler_time_only_format', SCHEDULER_TIME_ONLY_FORMAT)); + } +} diff --git a/scheduler.test b/scheduler.test index d26a8ce..3b079fb 100644 --- a/scheduler.test +++ b/scheduler.test @@ -14,11 +14,12 @@ class SchedulerTestCase extends DrupalWebTestCase { } function setUp() { - parent::setUp('scheduler'); - $this->web_user = $this->drupalCreateUser(array('edit own page content', 'create page content', 'administer nodes', 'schedule (un)publishing of nodes')); + parent::setUp('date', 'date_popup', 'scheduler'); + $this->web_user = $this->drupalCreateUser(array('access content', 'edit own page content', 'view own unpublished content', 'create page content', 'administer nodes', 'schedule (un)publishing of nodes')); // Add scheduler functionality to the page node type. variable_set('scheduler_publish_enable_page', 1); variable_set('scheduler_unpublish_enable_page', 1); + variable_set('scheduler_field_type', 'textfield'); } function testScheduler() { @@ -63,4 +64,75 @@ class SchedulerTestCase extends DrupalWebTestCase { $this->assertNoText($body, t('Node is unpublished')); } } + + /** + * Test the default time functionality. + */ + public function testDefaultTime() { + $date_format = variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT); + $date_only_format = variable_get('scheduler_date_only_format', SCHEDULER_DATE_ONLY_FORMAT); + + // For testing we use an offset of 6 hours 30 minutes (23400 seconds). + variable_set('scheduler_default_time', '06:30:00'); + + $this->drupalLogin($this->web_user); + + // First test the regular date entry, without popup. + debug('Testing date entry using a textfield'); + $this->assertDefaultTime(); + + // Enable date popup and repeat the test. + debug('Testing date entry using a date popup'); + variable_set('scheduler_field_type', 'date_popup'); + $this->assertDefaultTime(); + } + + /** + * Asserts that the default time works as expected. + */ + protected function assertDefaultTime() { + // Define the form fields and date formats we will test according to whether + // date popups have been enabled or not. + $using_popup = variable_get('scheduler_field_type', 'date_popup') == 'date_popup'; + $publish_date_field = $using_popup ? 'publish_on[date]' : 'publish_on'; + $unpublish_date_field = $using_popup ? 'unpublish_on[date]' : 'unpublish_on'; + $publish_time_field = $using_popup ? 'publish_on[time]' : 'publish_on'; + $unpublish_time_field = $using_popup ? 'unpublish_on[time]' : 'unpublish_on'; + $time_format = $using_popup ? 'H:i:s' : 'Y-m-d H:i:s'; + + // We cannot easily test the exact validation messages as they contain the + // REQUEST_TIME of the POST request, which can be one or more seconds in the + // past. Best we can do is check the fixed part of the message as it is when + // passed to t(). This will only work in English. + $publish_validation_message = $using_popup ? t('The value input for field %field is invalid:', array('%field' => 'Publish on')) : "The 'publish on' value does not match the expected format of"; + $unpublish_validation_message = $using_popup ? t('The value input for field %field is invalid:', array('%field' => 'Unpublish on')) : "The 'unpublish on' value does not match the expected format of"; + + // First test with the "date only" functionality disabled. + variable_set('scheduler_allow_date_only', FALSE); + + // Test if entering a time is required. + $edit = array( + 'title' => $this->randomName(), + $publish_date_field => date('Y-m-d', strtotime('+1 day', REQUEST_TIME)), + $unpublish_date_field => date('Y-m-d', strtotime('+2 day', REQUEST_TIME)), + ); + $this->drupalPost('node/add/page', $edit, t('Save')); + + $this->assertRaw($publish_validation_message, 'By default it is required to enter a time when scheduling content for publication.'); + $this->assertRaw($unpublish_validation_message, 'By default it is required to enter a time when scheduling content for unpublication.'); + + // Allow the user to enter only the date and repeat the test. + variable_set('scheduler_allow_date_only', TRUE); + + $this->drupalPost('node/add/page', $edit, t('Save')); + $this->assertNoRaw("The 'publish on' value does not match the expected format of", 'If the default time option is enabled the user can skip the time when scheduling content for publication.'); + $this->assertNoRaw("The 'unpublish on' value does not match the expected format of", 'If the default time option is enabled the user can skip the time when scheduling content for unpublication.'); + $this->assertRaw(t('This post is unpublished and will be published @publish_time.', array('@publish_time' => date('Y-m-d H:i:s', strtotime('tomorrow', REQUEST_TIME) + 23400))), 'The user is informed that the content will be published on the requested date, on the default time.'); + + // Check that the default time has been added to the scheduler form fields. + $this->clickLink(t('Edit')); + $this->assertFieldByName($publish_time_field, date($time_format, strtotime('tomorrow', REQUEST_TIME) + 23400), 'The default time offset has been added to the date field when scheduling content for publication.'); + $this->assertFieldByName($unpublish_time_field, date($time_format, strtotime('tomorrow +1 day', REQUEST_TIME) + 23400), 'The default time offset has been added to the date field when scheduling content for unpublication.'); + } + }