format_date(REQUEST_TIME, 'custom', variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT)), )); $form['scheduler_date_format'] = array( '#type' => 'textfield', '#title' => t('Date format'), '#default_value' => variable_get('scheduler_date_format', SCHEDULER_DATE_FORMAT), '#size' => 20, '#maxlength' => 20, '#required' => TRUE, '#field_suffix' => ' ' . $now . '', '#description' => t('The format for entering scheduled dates and times. For the date use the letters !date_letters and for the time use !time_letters. See !url for more details.', array( '!date_letters' => SCHEDULER_DATE_LETTERS, '!time_letters' => SCHEDULER_TIME_LETTERS, '!url' => l(t('the PHP date() function'), 'http://www.php.net/manual/en/function.date.php'), )), ); $form['scheduler_field_type'] = array( '#type' => 'radios', '#title' => t('Field type'), '#default_value' => variable_get('scheduler_field_type', 'date_popup'), '#options' => array( 'textfield' => t('Standard text field'), 'date_popup' => t('Date Popup calendar'), ), '#description' => t('Date Popup is enabled. See the !date_popup_config for details.', array('!date_popup_config' => l(t('configuration page'), 'admin/config/date/date_popup'))), ); if (!module_exists('date_popup')) { $form['scheduler_field_type']['#default_value'] = 'textfield'; $form['scheduler_field_type']['#disabled'] = TRUE; $form['scheduler_field_type']['#description'] = t('To use the calendar you need to enable Date, Date API and Date Popup. Download the module from the !url.', array('!url' => l(t('Date project page'), 'http://drupal.org/project/date'))); } // Variable 'date_popup_timepicker' holds the type of timepicker selected. $timepicker_enabled = (variable_get('date_popup_timepicker', '') != 'none'); $options = array('@date_popup_config' => url('admin/config/date/date_popup')); $description[] = t('Restrict the time entry to specific minute increments.'); $description[] = ($timepicker_enabled ? t('The timepicker type can be selected via the Date Popup configuration page.', $options) : t('The timepicker is not enabled - turn it on via the Date Popup configuration page.', $options)); $form['scheduler_date_popup_minute_increment'] = array( '#type' => 'textfield', '#title' => t('Date Popup minute increment'), '#description' => implode(' ', $description), '#field_suffix' => t('minutes'), '#size' => 2, '#maxlength' => 2, '#disabled' => !$timepicker_enabled, '#default_value' => variable_get('scheduler_date_popup_minute_increment', 1), '#element_validate' => array('element_validate_integer_positive'), '#states' => array( 'visible' => array( ':input[name="scheduler_field_type"]' => array('value' => 'date_popup'), ), ), ); // 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.'), ); $form['scheduler_cache_clear_all'] = array( '#prefix' => '', '#type' => 'checkbox', '#title' => t('Clear all expired block and page caches after publishing or unpublishing via cron.'), '#default_value' => variable_get('scheduler_cache_clear_all', 0), '#description' => t('If a node has been published or unpublished by Scheduler during a cron run, this option will clear the caches instead of relying on the Drupal core system cron task. Warning: This may have a detrimental effect on performance for large sites.'), ); // Add a submit handler function. $form['#submit'][] = 'scheduler_admin_submit'; return system_settings_form($form); } /** * Form validation handler for scheduler_admin(). */ function scheduler_admin_validate($form, &$form_state) { // Replace all contiguous whitespaces (including tabs and newlines) with a // single plain space. $form_state['values']['scheduler_date_format'] = trim(preg_replace('/\s+/', ' ', $form_state['values']['scheduler_date_format'])); // Validate the letters used in the scheduler date format. All punctuation is // accepted, so remove everything except word characters then check that there // is nothing else which is not in the list of acceptable date/time letters. $no_punctuation = preg_replace('/[^\w+]/', '', $form_state['values']['scheduler_date_format']); if (preg_match_all('/[^' . SCHEDULER_DATE_LETTERS . SCHEDULER_TIME_LETTERS . ']/', $no_punctuation, $extra)) { form_set_error('scheduler_date_format', t('You may only use the letters $date_letters for the date and $time_letters for the time. Remove the extra characters $extra', array( '$date_letters' => SCHEDULER_DATE_LETTERS, '$time_letters' => SCHEDULER_TIME_LETTERS, '$extra' => implode(' ', $extra[0]), ))); }; $time_format = scheduler_get_time_only_format($form_state['values']['scheduler_date_format']); if ($form_state['values']['scheduler_field_type'] == 'date_popup') { // The Date Popup function date_popup_time_formats() only returns the values // 'H:i:s' and 'h:i:sA' but Scheduler can accept more variations than just // these. Firstly, we add the lowercase 'a' alternative. Secondly timepicker // always requires hours and minutes, but seconds are optional. Spaces are // allowed before the 'A' or 'a'. $acceptable = array( 'H:i:s', 'h:i:sA', 'h:i:s A', 'h:i:sa', 'h:i:s a', 'H:i', 'h:iA', 'h:i A', 'h:ia', 'h:i a', ); if ($time_format && !in_array($time_format, $acceptable)) { form_set_error('scheduler_date_format', t('When using the Date Popup module, the allowed time formats are: !formats', array('!formats' => implode(', ', $acceptable)))); } } // If date-only is enabled then check if a valid default time was entered. // Leading zeros and seconds can be omitted, eg. 6:30 is considered valid. if ($form_state['values']['scheduler_allow_date_only']) { $default_time = date_parse($form_state['values']['scheduler_default_time']); if ($default_time['error_count']) { 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['hour'], $default_time['minute'], $default_time['second']); $form_state['values']['scheduler_default_time'] = format_date($unix_time, 'custom', 'H:i:s'); } } // Check that either the date format has a time part or the date-only option // is turned on. if ($time_format == '' && !$form_state['values']['scheduler_allow_date_only']) { form_set_error('scheduler_date_format', t('You must either include a time within the date format or enable the date-only option.')); } } /** * Form submission handler for scheduler_admin(). */ function scheduler_admin_submit($form, &$form_state) { // For the minute increment, change a blank value to 1. Date popup does not // support blank values. if (empty($form_state['values']['scheduler_date_popup_minute_increment'])) { $form_state['values']['scheduler_date_popup_minute_increment'] = 1; } // 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_only_format = scheduler_get_time_only_format($format); variable_set('scheduler_time_only_format', $time_only_format); $date_only_format = scheduler_get_date_only_format($format); variable_set('scheduler_date_only_format', $date_only_format); if (empty($time_only_format)) { drupal_set_message(t('The date part of the Scheduler format is %date_part. There is no time part', array('%date_part' => $date_only_format))); } else { 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))); } } /** * Returns the time part of a date format. * * For example, when given the string 'Y-m-d H:s:i' it will return 'H:s:i'. * * @param string $format * A date format compatible with the PHP date() function. * * @return string * The time part of the date format, or an empty string if it does not contain * a time part. */ function scheduler_get_time_only_format($format) { $time_start = strcspn($format, SCHEDULER_TIME_LETTERS); $time_length = strlen($format) - strcspn(strrev($format), SCHEDULER_TIME_LETTERS) - $time_start; return substr($format, $time_start, $time_length); } /** * Returns the date part of a date format. * * For example, when given the string 'Y-m-d H:s:i' it will return 'Y-m-d'. * * @param string $format * A date format compatible with the PHP date() function. * * @return string * The date part of the date format, or an empty string if it does not contain * a date part. */ function scheduler_get_date_only_format($format) { $date_start = strcspn($format, SCHEDULER_DATE_LETTERS); $date_length = strlen($format) - strcspn(strrev($format), SCHEDULER_DATE_LETTERS) - $date_start; return substr($format, $date_start, $date_length); } /** * Helper function for the real hook_form_node_type_form_alter(). * * @see scheduler_form_node_type_form_alter() */ function _scheduler_form_node_type_form_alter(&$form, $form_state) { $form['scheduler'] = array( '#type' => 'fieldset', '#title' => t('Scheduler'), '#weight' => 35, '#group' => 'additional_settings', '#attached' => array( 'js' => array( 'vertical-tabs' => _scheduler_get_vertical_tabs_js(), ), ), ); $form['scheduler']['publish'] = array( '#type' => 'fieldset', '#title' => t('Publishing'), '#collapsible' => FALSE, '#weight' => 1, '#group' => 'scheduler', ); $form['scheduler']['publish']['scheduler_publish_enable'] = array( '#type' => 'checkbox', '#title' => t('Enable scheduled publishing for this content type'), '#default_value' => variable_get('scheduler_publish_enable_' . $form['#node_type']->type, 0), ); $form['scheduler']['publish']['scheduler_publish_touch'] = array( '#type' => 'checkbox', '#title' => t('Change content creation time to match the scheduled publish time'), '#default_value' => variable_get('scheduler_publish_touch_' . $form['#node_type']->type, 0), '#states' => array( 'visible' => array( ':input[name="scheduler_publish_enable"]' => array('checked' => TRUE), ), ), ); $form['scheduler']['publish']['scheduler_publish_required'] = array( '#type' => 'checkbox', '#title' => t('Require scheduled publishing'), '#default_value' => variable_get('scheduler_publish_required_' . $form['#node_type']->type, 0), '#states' => array( 'visible' => array( ':input[name="scheduler_publish_enable"]' => array('checked' => TRUE), ), ), ); $form['scheduler']['publish']['scheduler_publish_revision'] = array( '#type' => 'checkbox', '#title' => t('Create a new revision on publishing'), '#default_value' => variable_get('scheduler_publish_revision_' . $form['#node_type']->type, 0), '#states' => array( 'visible' => array( ':input[name="scheduler_publish_enable"]' => array('checked' => TRUE), ), ), ); $form['scheduler']['publish']['advanced'] = array( '#type' => 'fieldset', '#title' => t('Advanced options'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#states' => array( 'visible' => array( ':input[name="scheduler_publish_enable"]' => array('checked' => TRUE), ), ), ); $form['scheduler']['publish']['advanced']['scheduler_publish_past_date'] = array( '#type' => 'radios', '#title' => t('Action to be taken for publication dates in the past'), '#default_value' => variable_get('scheduler_publish_past_date_' . $form['#node_type']->type, 'error'), '#options' => array( 'error' => t('Display an error message - do not allow dates in the past'), 'publish' => t('Publish the content immediately after saving'), 'schedule' => t('Schedule the content for publication on the next cron run'), ), ); $form['scheduler']['unpublish'] = array( '#type' => 'fieldset', '#title' => t('Unpublishing'), '#collapsible' => FALSE, '#weight' => 2, '#group' => 'scheduler', ); $form['scheduler']['unpublish']['scheduler_unpublish_enable'] = array( '#type' => 'checkbox', '#title' => t('Enable scheduled unpublishing for this content type'), '#default_value' => variable_get('scheduler_unpublish_enable_' . $form['#node_type']->type, 0), ); $form['scheduler']['unpublish']['scheduler_unpublish_required'] = array( '#type' => 'checkbox', '#title' => t('Require scheduled unpublishing'), '#default_value' => variable_get('scheduler_unpublish_required_' . $form['#node_type']->type, 0), '#states' => array( 'visible' => array( ':input[name="scheduler_unpublish_enable"]' => array('checked' => TRUE), ), ), ); $form['scheduler']['unpublish']['scheduler_unpublish_revision'] = array( '#type' => 'checkbox', '#title' => t('Create a new revision on unpublishing'), '#default_value' => variable_get('scheduler_unpublish_revision_' . $form['#node_type']->type, 0), '#states' => array( 'visible' => array( ':input[name="scheduler_unpublish_enable"]' => array('checked' => TRUE), ), ), ); // The 'node_edit_layout' fieldset contains options to alter the layout of // node edit pages. $form['scheduler']['node_edit_layout'] = array( '#type' => 'fieldset', '#title' => t('Node edit page layout'), '#collapsible' => FALSE, '#weight' => 3, '#group' => 'scheduler', // The #states processing only caters for AND and does not do OR. So to set // the state to visible if either of the boxes are ticked we use the fact // that logical 'X = A or B' is equivalent to 'not X = not A and not B'. '#states' => array( '!visible' => array( ':input[name="scheduler_publish_enable"]' => array('!checked' => TRUE), ':input[name="scheduler_unpublish_enable"]' => array('!checked' => TRUE), ), ), ); $form['scheduler']['node_edit_layout']['scheduler_use_vertical_tabs'] = array( '#type' => 'radios', '#title' => t('Display scheduling options as'), '#default_value' => variable_get('scheduler_use_vertical_tabs_' . $form['#node_type']->type, 1), '#options' => array( '1' => t('Vertical tab'), '0' => t('Separate fieldset'), ), '#description' => t('Use this option to specify how the scheduling options will be displayed when editing a node.'), ); $form['scheduler']['node_edit_layout']['scheduler_expand_fieldset'] = array( '#type' => 'radios', '#title' => t('Expand fieldset'), '#default_value' => variable_get('scheduler_expand_fieldset_' . $form['#node_type']->type, 0), '#options' => array( '0' => t('Expand only when a scheduled date exists or when a date is required'), '1' => t('Always open the fieldset, even if no dates exist'), ), '#states' => array( 'visible' => array( ':input[name="scheduler_use_vertical_tabs"]' => array('value' => '0'), ), ), ); } /** * Form constructor for the lightweight cron form to allow a manual run. */ function _scheduler_lightweight_cron($form, &$form_state) { $form = array(); $prefix_text = t("You can test Scheduler's lightweight cron process interactively"); $form['scheduler_cron'] = array( '#type' => 'submit', '#prefix' => '
' . t('Your server\'s time is @utc. In most cases this should match Greenwich Mean Time (GMT) / Coordinated Universal Time (UTC)', $t_options) . '
' . t('The website default timezone is @date_default_timezone (@date_default_code) which is offset from GMT by @date_default_offset hours. This timezone can be changed by admin users with the appropriate access.', $t_options) . '
'; if (variable_get('configurable_timezones', 1)) { $output .= '' . t('Your local time is @localtime (@daylight_saving). You can change this via your user account.', $t_options) . '
'; if (empty($user->timezone)) { $output .= '' . t('Note: The user timezone has not been stored, so defaulting to the website timezone.') . '
'; } } else { $output .= '' . t('Your local time is @localtime (@daylight_saving). This is not configurable by you.', $t_options) . '
'; } return $output; } /** * Page callback: Displays a list of nodes scheduled for (un)publication. * * This will appear as a tab on the Scheduler admin page at * admin/config/content/scheduler/list and the content admin page at * admin/content/scheduler. It is also shown on the 'My account' page * user/{uid}/scheduler if the user has permission to schedule content. * * @param string $show * 'user_only' if viewing a user page, NULL otherwise. * @param int $uid * The uid when viewing a user page, NULL otherwise. * * @return array * A render array for a page containing a list of nodes. */ function scheduler_list($show, $uid) { $header = array( array( 'data' => t('Title'), 'field' => 'n.title', ), array( 'data' => t('Type'), 'field' => 'n.type', ), array( 'data' => t('Author'), 'field' => 'u.name', ), array( 'data' => t('Status'), 'field' => 'n.status', ), array( 'data' => t('Publish on'), 'field' => 's.publish_on', ), array( 'data' => t('Unpublish on'), 'field' => 's.unpublish_on', ), array( 'data' => t('Operations'), ), ); // Default ordering. if (!isset($_GET['order']) && !isset($_GET['sort'])) { $_GET['order'] = t('Publish on'); $_GET['sort'] = 'ASC'; } $query = db_select('scheduler', 's')->extend('PagerDefault'); $query->limit(50); $query->addJoin('LEFT', 'node', 'n', 's.nid = n.nid'); $query->addJoin('LEFT', 'users', 'u', 'u.uid = n.uid'); $query->fields('s', array('nid', 'publish_on', 'unpublish_on')); $query->fields('n', array('uid', 'status', 'title', 'type', 'status')); $query->addField('u', 'name'); // If this function is being called from a user account page then only select // the nodes owned by that user. If the current user is viewing another users' // profile and they do not have 'administer nodes' permission then it won't // even get this far, as the tab will not be accessible. if ($show == 'user_only') { $query->condition('n.uid', $uid, '='); // Get user account for use later. $user = user_load($uid); } $query = $query->extend('TableSort')->orderByHeader($header); $result = $query->execute(); $destination = drupal_get_destination(); $rows = array(); foreach ($result as $node) { // Check the access allowed on this node. $can_edit = node_access('update', $node); $can_delete = node_access('delete', $node); // Set the operations depending on whether the node is valid or corrupt. $ops = array(); if ($node->type) { // Node type is present so this indicates a valid join with the node // table. Provide regular operations to edit and delete the node. if ($can_edit) { $ops[] = l(t('edit'), 'node/' . $node->nid . '/edit', array('query' => $destination)); } if ($can_delete) { $ops[] = l(t('delete'), 'node/' . $node->nid . '/delete', array('query' => $destination)); } } else { // There was no matching row in the node table. Provide a special link to // delete the row from the Scheduler table. if ($can_delete) { $ops[] = l(t('delete'), 'admin/content/scheduler/delete/' . $node->nid, array('query' => $destination)); } } $rows[] = array( ($node->title ? l($node->title, "node/$node->nid") : t('Missing data for node @nid', array('@nid' => $node->nid))), ($node->type ? check_plain(node_type_get_name($node)) : ''), ($node->type ? theme('username', array('account' => $node)) : ''), ($node->type ? ($node->status ? t('Published') : t('Unpublished')) : ''), ($node->publish_on ? format_date($node->publish_on) : ' '), ($node->unpublish_on ? format_date($node->unpublish_on) : ' '), implode(' ', $ops), ); } if (count($rows) && ($pager = theme('pager'))) { $rows[] = array( array( 'data' => $pager, 'colspan' => count($rows['0']), ), ); } $build['scheduler_list'] = array( '#theme' => 'table', '#header' => $header, '#rows' => $rows, '#empty' => ($show == 'user_only') ? t('There are no scheduled nodes for @username.', array('@username' => $user->name)) : t('There are no scheduled nodes.'), ); return $build; } /** * Form constructor for the Scheduler Delete Row confirmation form. * * @see _scheduler_delete_row_confirm_submit() */ function _scheduler_delete_row_confirm($form, &$form_state, $nid) { $form['nid'] = array('#type' => 'value', '#value' => $nid); $details = array(t('Are you sure you want to delete the Scheduler row for missing node @nid?', array('@nid' => $nid))); $details[] = t('This action cannot be undone.'); return confirm_form( $form, $details[0], // The cancel path will be changed automatically because the calling url // contains a destination value. We need to provide a default here anyway. 'admin/content/scheduler', implode('