diff --git a/includes/date.inc b/includes/date.inc new file mode 100644 index 000000000000..6158ac22daba --- /dev/null +++ b/includes/date.inc @@ -0,0 +1,197 @@ + 'short', + 'format' => 'Y-m-d H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'm/d/Y - H:i', + 'locales' => array('en-us'), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'd/m/Y - H:i', + 'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'Y/m/d - H:i', + 'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'd.m.Y - H:i', + 'locales' => array('de-ch', 'de-de', 'de-lu', 'fi-fi', 'fr-ch', 'is-is', 'pl-pl', 'ro-ro', 'ru-ru'), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'm/d/Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'd/m/Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'Y/m/d - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'M j Y - H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'j M Y - H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'Y M j - H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'M j Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'j M Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'short', + 'format' => 'Y M j - g:ia', + 'locales' => array(), + ); + + // Medium date formats. + $formats[] = array( + 'type' => 'medium', + 'format' => 'D, Y-m-d H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'D, m/d/Y - H:i', + 'locales' => array('en-us'), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'D, d/m/Y - H:i', + 'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'D, Y/m/d - H:i', + 'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'F j, Y - H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'j F, Y - H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'Y, F j - H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'D, m/d/Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'D, d/m/Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'D, Y/m/d - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'F j, Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'j F Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'Y, F j - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'medium', + 'format' => 'j. F Y - G:i', + 'locales' => array(), + ); + + // Long date formats. + $formats[] = array( + 'type' => 'long', + 'format' => 'l, F j, Y - H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'long', + 'format' => 'l, j F, Y - H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'long', + 'format' => 'l, Y, F j - H:i', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'long', + 'format' => 'l, F j, Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'long', + 'format' => 'l, j F Y - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'long', + 'format' => 'l, Y, F j - g:ia', + 'locales' => array(), + ); + $formats[] = array( + 'type' => 'long', + 'format' => 'l, j. F Y - G:i', + 'locales' => array(), + ); + + return $formats; +} diff --git a/includes/locale.inc b/includes/locale.inc index cc95ef7f4d9f..8768be7dc157 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -3216,3 +3216,87 @@ function country_get_list() { return $countries; } +/** + * Save locale specific date formats to the database. + * + * @param $langcode + * Language code, can be 2 characters, e.g. 'en' or 5 characters, e.g. + * 'en-CA'. + * @param $type + * Date format type, e.g. 'short', 'medium'. + * @param $format + * The date format string. + */ +function locale_date_format_save($langcode, $type, $format) { + $locale_format = array(); + $locale_format['language'] = $langcode; + $locale_format['type'] = $type; + $locale_format['format'] = $format; + + $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE language = :langcode AND type = :type', 0, 1, array(':langcode' => $langcode, ':type' => $type))->fetchField(); + if ($is_existing) { + $keys = array('type', 'language'); + drupal_write_record('date_format_locale', $locale_format, $keys); + } + else { + drupal_write_record('date_format_locale', $locale_format); + } +} + +/** + * Select locale date format details from database. + * + * @param $languages + * An array of language codes. + * @return + * An array of date formats. + */ +function locale_get_localized_date_format($languages) { + $formats = array(); + + // Get list of different format types. + $format_types = system_get_date_types(); + $short_default = variable_get('date_format_short', 'm/d/Y - H:i'); + + // Loop through each language until we find one with some date formats + // configured. + foreach ($languages as $language) { + $date_formats = system_date_format_locale($language); + if (!empty($date_formats)) { + // We have locale-specific date formats, so check for their types. If + // we're missing a type, use the default setting instead. + foreach ($format_types as $type => $type_info) { + // If format exists for this language, use it. + if (!empty($date_formats[$type])) { + $formats['date_format_' . $type] = $date_formats[$type]; + } + // Otherwise get default variable setting. If this is not set, default + // to the short format. + else { + $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); + } + } + + // Return on the first match. + return $formats; + } + } + + // No locale specific formats found, so use defaults. + $system_types = array('short', 'medium', 'long'); + // Handle system types separately as they have defaults if no variable exists. + $formats['date_format_short'] = $short_default; + $formats['date_format_medium'] = variable_get('date_format_medium', 'D, m/d/Y - H:i'); + $formats['date_format_long'] = variable_get('date_format_long', 'l, F j, Y - H:i'); + + // For non-system types, get the default setting, otherwise use the short + // format. + foreach ($format_types as $type => $type_info) { + if (!in_array($type, $system_types)) { + $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); + } + } + + return $formats; +} + diff --git a/modules/locale/locale.module b/modules/locale/locale.module index 18dc0a5a1b12..a24198182f3c 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -224,9 +224,66 @@ function locale_menu() { 'file path' => 'includes', ); + // Localize date formats. + $items['admin/config/regional/date-time/locale'] = array( + 'title' => 'Localize', + 'description' => 'Configure date formats for each locale', + 'page callback' => 'locale_date_format_language_overview_page', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_LOCAL_TASK, + 'weight' => -8, + 'file' => 'locale.inc', + 'file path' => 'includes', + ); + $items['admin/config/regional/date-time/locale/%/edit'] = array( + 'title' => 'Localize date formats', + 'description' => 'Configure date formats for each locale', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('locale_date_format_form', 5), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK, + 'file' => 'locale.inc', + 'file path' => 'includes', + ); + $items['admin/config/regional/date-time/locale/%/reset'] = array( + 'title' => 'Reset date formats', + 'description' => 'Reset localized date formats to global defaults', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('locale_date_format_reset_form', 5), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_CALLBACK, + 'file' => 'locale.inc', + 'file path' => 'includes', + ); + return $items; } +/** + * Implements hook_init(). + * + * Initialize date formats according to the user's current locale. + */ +function locale_init() { + global $conf, $language; + include_once DRUPAL_ROOT . '/includes/locale.inc'; + + // For each date type (e.g. long, short), get the localized date format + // for the user's current language and override the default setting for it + // in $conf. This should happen on all pages except the date and time formats + // settings page, where we want to display the site default and not the + // localized version. + if (strpos($_GET['q'], 'admin/config/regional/date-time/formats') !== 0) { + $languages = array($language->language); + + // Setup appropriate date formats for this locale. + $formats = locale_get_localized_date_format($languages); + foreach ($formats as $format_type => $format) { + $conf[$format_type] = $format; + } + } +} + /** * Wrapper function to be able to set callbacks in locale.inc */ @@ -376,6 +433,9 @@ function locale_theme() { 'locale_translation_filters' => array( 'arguments' => array('form' => array()), ), + 'locale_date_format_form' => array( + 'arguments' => array('form' => array()), + ), ); } @@ -798,3 +858,160 @@ function theme_locale_translation_filters($variables) { $output .= '
'; return $output; } + +/** + * Theme locale date format form. + * + * @ingroup themeable + */ +function theme_locale_date_format_form($variables) { + $form = $variables['form']; + $header = array( + t('Date type'), + t('Format'), + ); + + foreach (element_children($form['date_formats']) as $key) { + $row = array(); + $row[] = $form['date_formats'][$key]['#title']; + unset($form['date_formats'][$key]['#title']); + $row[] = array('data' => drupal_render($form['date_formats'][$key])); + $rows[] = $row; + } + + $output = drupal_render($form['language']); + $output .= theme('table', array('header' => $header, 'rows' => $rows)); + $output .= drupal_render_children($form); + + return $output; +} + +/** + * Display edit date format links for each language. + */ +function locale_date_format_language_overview_page() { + $header = array( + t('Language'), + array('data' => t('Operations'), 'colspan' => '2'), + ); + + // Get list of languages. + $languages = locale_language_list('native'); + + foreach ($languages as $langcode => $info) { + $row = array(); + $row[] = $languages[$langcode]; + $row[] = l(t('edit'), 'admin/config/regional/date-time/locale/' . $langcode . '/edit'); + $row[] = l(t('reset'), 'admin/config/regional/date-time/locale/' . $langcode . '/reset'); + $rows[] = $row; + } + + return theme('table', array('header' => $header, 'rows' => $rows)); +} + +/** + * Provide date localization configuration options to users. + */ +function locale_date_format_form($form, &$form_state, $langcode) { + $languages = locale_language_list('native'); + $language_name = $languages[$langcode]; + + // Display the current language name. + $form['language'] = array( + '#type' => 'item', + '#title' => t('Language'), + '#markup' => check_plain($language_name), + '#weight' => -10, + ); + $form['langcode'] = array( + '#type' => 'value', + '#value' => $langcode, + ); + + // Get list of date format types. + $types = system_get_date_types(); + + // Get list of available formats. + $formats = system_get_date_formats(); + $choices = array(); + foreach ($formats as $type => $list) { + foreach ($list as $f => $format) { + $choices[$f] = format_date(REQUEST_TIME, 'custom', $f); + } + } + + // Get configured formats for each language. + $locale_formats = system_date_format_locale($langcode); + // Display a form field for each format type. + foreach ($types as $type => $type_info) { + if (!empty($locale_formats) && in_array($type, array_keys($locale_formats))) { + $default = $locale_formats[$type]; + } + else { + $default = variable_get('date_format_' . $type, array_shift(array_keys($formats))); + } + + // Show date format select list. + $form['date_formats']['date_format_' . $type] = array( + '#type' => 'select', + '#title' => check_plain($type_info['title']), + '#attributes' => array('class' => array('date-format')), + '#default_value' => (isset($choices[$default]) ? $default : 'custom'), + '#options' => $choices, + ); + } + + $form['buttons']['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + + return $form; +} + +/** + * Submit handler for configuring localized date formats on the locale_date_format_form. + */ +function locale_date_format_form_submit($form, &$form_state) { + include_once DRUPAL_ROOT . '/includes/locale.inc'; + $langcode = $form_state['values']['langcode']; + + // Get list of date format types. + $types = system_get_date_types(); + foreach ($types as $type => $type_info) { + $format = $form_state['values']['date_format_' . $type]; + if ($format == 'custom') { + $format = $form_state['values']['date_format_' . $type . '_custom']; + } + locale_date_format_save($langcode, $type, $format); + } + drupal_set_message(t('Configuration saved.')); + $form_state['redirect'] = 'admin/config/regional/date-time/locale'; +} + +/** + * Reset locale specific date formats to the global defaults. + * + * @param $langcode + * Language code, e.g. 'en'. + */ +function locale_date_format_reset_form($form, &$form_state, $langcode) { + $form['langcode'] = array('#type' => 'value', '#value' => $langcode); + $languages = language_list(); + return confirm_form($form, + t('Are you sure you want to reset the date formats for %language to the global defaults?', array('%language' => $languages[$langcode]->name)), + 'admin/config/regional/date-time/locale', + t('Resetting will remove all localized date formats for this language. This action cannot be undone.'), + t('Reset'), t('Cancel')); +} + +/** + * Reset date formats for a specific language to global defaults. + */ +function locale_date_format_reset_form_submit($form, &$form_state) { + db_delete('date_format_locale') + ->condition('language', $form_state['values']['langcode']) + ->execute(); + $form_state['redirect'] = 'admin/config/regional/date-time/locale'; +} + diff --git a/modules/locale/locale.test b/modules/locale/locale.test index b03b85d0d674..9a800303afec 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -1618,3 +1618,74 @@ class UILanguageNegotiationTest extends DrupalWebTestCase { $this->assertText($test['expect'], $test['message']); } } + +/** + * Functional tests for localizing date formats. + */ +class LocalizeDateFormatsFunctionalTest extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Localize date formats', + 'description' => 'Tests for the localization of date formats.', + 'group' => 'Locale', + ); + } + + function setUp() { + parent::setUp('locale'); + + // Create and login user. + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content')); + $this->drupalLogin($admin_user); + } + + /** + * Functional tests for localizing date formats. + */ + function testLocalizeDateFormats() { + // Add language. + $edit = array( + 'langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Set language negotiation. + $edit = array( + 'language[enabled][locale-url]' => TRUE, + 'language_interface[enabled][locale-url]' => TRUE, + ); + $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings')); + + // Configure date formats. + $this->drupalGet('admin/config/regional/date-time/locale'); + $this->assertText('Français', 'Configured languages appear.'); + $edit = array( + 'date_format_long' => 'd.m.Y - H:i', + 'date_format_medium' => 'd.m.Y - H:i', + 'date_format_short' => 'd.m.Y - H:i', + ); + $this->drupalPost('admin/config/regional/date-time/locale/fr/edit', $edit, t('Save configuration')); + $this->assertText(t('Configuration saved.'), 'French date formats updated.'); + $edit = array( + 'date_format_long' => 'j M Y - g:ia', + 'date_format_medium' => 'j M Y - g:ia', + 'date_format_short' => 'j M Y - g:ia', + ); + $this->drupalPost('admin/config/regional/date-time/locale/en/edit', $edit, t('Save configuration')); + $this->assertText(t('Configuration saved.'), 'English date formats updated.'); + + // Create node content. + $node = $this->drupalCreateNode(array('type' => 'article')); + + // Configure format for the node posted date changes with the language. + $this->drupalGet('node/' . $node->nid); + $english_date = format_date($node->created, 'custom', 'j M Y'); + $this->assertText($english_date, t('English date format appears')); + $this->drupalGet('fr/node/' . $node->nid); + $french_date = format_date($node->created, 'custom', 'd.m.Y'); + $this->assertText($french_date, t('French date format appears')); + } +} + + diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 190128b4ab92..dbc02b60786e 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1592,16 +1592,13 @@ function system_rss_feeds_settings() { } /** - * Form builder; Configure the site date and time settings. + * Form builder; Configure the site regional settings. * * @ingroup forms * @see system_settings_form() * @see system_regional_settings_submit() */ function system_regional_settings() { - drupal_add_js(drupal_get_path('module', 'system') . '/system.js'); - drupal_add_js(array('dateTime' => array('lookup' => url('admin/config/regional/settings/lookup'))), 'setting'); - include_once DRUPAL_ROOT . '/includes/locale.inc'; $countries = country_get_list(); // Add a 'No default country' option to the start of the list. @@ -1610,31 +1607,6 @@ function system_regional_settings() { // Date settings: $zones = system_time_zones(); - // Date settings: possible date formats - $date_short = array('Y-m-d H:i', 'm/d/Y - H:i', 'd/m/Y - H:i', 'Y/m/d - H:i', - 'd.m.Y - H:i', 'm/d/Y - g:ia', 'd/m/Y - g:ia', 'Y/m/d - g:ia', - 'M j Y - H:i', 'j M Y - H:i', 'Y M j - H:i', - 'M j Y - g:ia', 'j M Y - g:ia', 'Y M j - g:ia'); - $date_medium = array('D, Y-m-d H:i', 'D, m/d/Y - H:i', 'D, d/m/Y - H:i', - 'D, Y/m/d - H:i', 'F j, Y - H:i', 'j F, Y - H:i', 'Y, F j - H:i', - 'D, m/d/Y - g:ia', 'D, d/m/Y - g:ia', 'D, Y/m/d - g:ia', - 'F j, Y - g:ia', 'j F Y - g:ia', 'Y, F j - g:ia', 'j. F Y - G:i'); - $date_long = array('l, F j, Y - H:i', 'l, j F, Y - H:i', 'l, Y, F j - H:i', - 'l, F j, Y - g:ia', 'l, j F Y - g:ia', 'l, Y, F j - g:ia', 'l, j. F Y - G:i'); - - // Date settings: construct choices for user - foreach ($date_short as $f) { - $date_short_choices[$f] = format_date(REQUEST_TIME, 'custom', $f); - } - foreach ($date_medium as $f) { - $date_medium_choices[$f] = format_date(REQUEST_TIME, 'custom', $f); - } - foreach ($date_long as $f) { - $date_long_choices[$f] = format_date(REQUEST_TIME, 'custom', $f); - } - - $date_long_choices['custom'] = $date_medium_choices['custom'] = $date_short_choices['custom'] = t('Custom format'); - $form['locale'] = array( '#type' => 'fieldset', '#title' => t('Locale'), @@ -1699,97 +1671,214 @@ function system_regional_settings() { '#description' => t('Only applied if users may set their own time zone.') ); - $form['date_formats'] = array( - '#type' => 'fieldset', - '#title' => t('Date formats'), + return system_settings_form($form, FALSE); +} + +/** + * Form builder; Configure the site date and time settings. + * + * @ingroup forms + * @see system_settings_form() + */ +function system_date_time_settings() { + // Get list of all available date types. + drupal_static_reset('system_get_date_types'); + $format_types = system_get_date_types(); + + // Get list of all available date formats. + $all_formats = array(); + drupal_static_reset('system_get_date_formats'); + $date_formats = system_get_date_formats(); // Call this to rebuild the list, and to have default list. + foreach ($date_formats as $type => $format_info) { + $all_formats = array_merge($all_formats, $format_info); + } + $custom_formats = system_get_date_formats('custom'); + if (!empty($format_types)) { + foreach ($format_types as $type => $type_info) { + // If a system type, only show the available formats for that type and + // custom ones. + if ($type_info['locked'] == 1) { + $formats = system_get_date_formats($type); + if (empty($formats)) { + $formats = $all_formats; + } + elseif (!empty($custom_formats)) { + $formats = array_merge($formats, $custom_formats); + } + } + // If a user configured type, show all available date formats. + else { + $formats = $all_formats; + } + + $choices = array(); + foreach ($formats as $f => $format) { + $choices[$f] = format_date(REQUEST_TIME, 'custom', $f); + } + $default = variable_get('date_format_' . $type, array_shift(array_keys($formats))); + + // Get date type info for this date type. + $type_info = system_get_date_types($type); + $form['formats']['#theme'] = 'system_date_time_settings'; + + // Show date format select list. + $form['formats']['format']['date_format_' . $type] = array( + '#type' => 'select', + '#title' => check_plain($type_info['title']), + '#attributes' => array('class' => array('date-format')), + '#default_value' => (isset($choices[$default]) ? $default : 'custom'), + '#options' => $choices, + ); + + // If this isn't a system provided type, allow the user to remove it from + // the system. + if ($type_info['locked'] == 0) { + $form['formats']['delete']['date_format_' . $type . '_delete'] = array( + '#markup' => l(t('delete'), 'admin/config/regional/date-time/types/' . $type . '/delete'), + ); + } + } + } + + // Display a message if no date types configured. + $form['#empty_text'] = t('No date types available. Add date type.', array('@link' => url('admin/config/regional/date-time/types/add'))); + + return system_settings_form($form, FALSE); +} + +/** + * Theme function for date settings form. + * + * @ingroup themeable + */ +function theme_system_date_time_settings($variables) { + $form = $variables['form']; + $header = array( + t('Date type'), + t('Format'), + t('Operations'), ); - $date_format_short = variable_get('date_format_short', $date_short[1]); - $form['date_formats']['date_format_short'] = array( - '#prefix' => '' . t('For more information, see the online handbook entry for System module.', array('@system' => 'http://drupal.org/handbook/modules/system/')) . '
'; return $output; case 'admin/by-module': @@ -204,6 +204,10 @@ function system_theme() { 'system_run_cron_image' => array( 'arguments' => array('image_path' => NULL), ), + 'system_date_time_settings' => array( + 'arguments' => array('form' => NULL), + 'file' => 'system.admin.inc', + ), )); } @@ -781,6 +785,8 @@ function system_menu() { 'access arguments' => array('administer site configuration'), 'file' => 'system.admin.inc', ); + + // Regional and date settings. $items['admin/config/regional'] = array( 'title' => 'Regional and language', 'description' => 'Regional settings, localization and translation.', @@ -792,20 +798,96 @@ function system_menu() { ); $items['admin/config/regional/settings'] = array( 'title' => 'Regional settings', - 'description' => "Settings for how Drupal displays date and time, as well as the system's default time zone.", + 'description' => "Settings for the site's default time zone and country.", 'page callback' => 'drupal_get_form', 'page arguments' => array('system_regional_settings'), 'access arguments' => array('administer site configuration'), 'weight' => -10, 'file' => 'system.admin.inc', ); - $items['admin/config/regional/settings/lookup'] = array( + $items['admin/config/regional/date-time'] = array( + 'title' => 'Date and time', + 'description' => 'Configure display formats for date and time.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_date_time_settings'), + 'access arguments' => array('administer site configuration'), + 'weight' => -9, + 'file' => 'system.admin.inc', + ); + $items['admin/config/regional/date-time/types'] = array( + 'title' => 'Types', + 'description' => 'Configure display formats for date and time.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_date_time_settings'), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'weight' => -10, + 'file' => 'system.admin.inc', + ); + $items['admin/config/regional/date-time/types/add'] = array( + 'title' => 'Add date type', + 'description' => 'Add new date type.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_add_date_format_type_form'), + 'access arguments' => array('administer site configuration'), + 'type' => MENU_LOCAL_ACTION, + 'weight' => -10, + 'file' => 'system.admin.inc', + ); + $items['admin/config/regional/date-time/types/%/delete'] = array( + 'title' => 'Delete date type', + 'description' => 'Allow users to delete a configured date type.', + 'type' => MENU_CALLBACK, + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_delete_date_format_type_form', 5), + 'access arguments' => array('administer site configuration'), + 'file' => 'system.admin.inc', + ); + $items['admin/config/regional/date-time/formats'] = array( + 'title' => 'Formats', + 'description' => 'Configure display format strings for date and time.', + 'page callback' => 'system_date_time_formats', + 'access arguments' => array('administer site configuration'), + 'type' => MENU_LOCAL_TASK, + 'weight' => -9, + 'file' => 'system.admin.inc', + ); + $items['admin/config/regional/date-time/formats/add'] = array( + 'title' => 'Add format', + 'description' => 'Allow users to add additional date formats.', + 'type' => MENU_LOCAL_ACTION, + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_configure_date_formats_form'), + 'access arguments' => array('administer site configuration'), + 'weight' => -10, + 'file' => 'system.admin.inc', + ); + $items['admin/config/regional/date-time/formats/%/edit'] = array( + 'title' => 'Edit date format', + 'description' => 'Allow users to edit a configured date format.', + 'type' => MENU_CALLBACK, + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_configure_date_formats_form', 5), + 'access arguments' => array('administer site configuration'), + 'file' => 'system.admin.inc', + ); + $items['admin/config/regional/date-time/formats/%/delete'] = array( + 'title' => 'Delete date format', + 'description' => 'Allow users to delete a configured date format.', + 'type' => MENU_CALLBACK, + 'page callback' => 'drupal_get_form', + 'page arguments' => array('system_date_delete_format_form', 5), + 'access arguments' => array('administer site configuration'), + 'file' => 'system.admin.inc', + ); + $items['admin/config/regional/date-time/formats/lookup'] = array( 'title' => 'Date and time lookup', 'type' => MENU_CALLBACK, 'page callback' => 'system_date_time_lookup', 'access arguments' => array('administer site configuration'), 'file' => 'system.admin.inc', ); + $items['admin/config/search'] = array( 'title' => 'Search and metadata', 'description' => 'Local site search, metadata and SEO.', @@ -2453,6 +2535,14 @@ function system_cron() { ->execute(); } +/** + * Implements hook_flush_caches(). + */ +function system_flush_caches() { + // Rebuild list of date formats. + system_date_formats_rebuild(); +} + /** * Implement hook_action_info(). */ @@ -2921,3 +3011,353 @@ function theme_system_run_cron_image($variables) { return ''; } +/** + * Get the list of available date types and attributes. + * + * @param $type + * The date type, e.g. 'short', 'medium', 'long', 'custom'. If empty, then + * all attributes for that type will be returned. + * @return + * Array of date types. + */ +function system_get_date_types($type = NULL) { + $date_format_types = &drupal_static(__FUNCTION__); + + if (!isset($date_format_types)) { + $date_format_types = _system_date_format_types_build(); + } + + return $type ? (isset($date_format_types[$type]) ? $date_format_types[$type] : FALSE) : $date_format_types; +} + +/** + * Implements hook_date_format_types(). + */ +function system_date_format_types() { + return array( + 'long' => t('Long'), + 'medium' => t('Medium'), + 'short' => t('Short'), + ); +} + +/** + * Implements hook_date_formats(). + * + * @return + * An array of date formats with attributes 'type' (short, medium or long), + * 'format' (the format string) and 'locales'. The 'locales' attribute is an + * array of locales, which can include both 2 character language codes like + * 'en', 'fr', but also 5 character language codes like 'en-gb' and 'en-us'. + */ +function system_date_formats() { + include_once DRUPAL_ROOT . '/includes/date.inc'; + return system_default_date_formats(); +} + +/** + * Get the list of date formats for a particular format length. + * + * @param $type + * The date type: 'short', 'medium', 'long', 'custom'. If empty, then all + * available types will be returned. + * @return + * Array of date formats. + */ +function system_get_date_formats($type = NULL) { + $date_formats = &drupal_static(__FUNCTION__); + + if (!isset($date_formats)) { + $date_formats = _system_date_formats_build(); + } + + return $type ? (isset($date_formats[$type]) ? $date_formats[$type] : FALSE) : $date_formats; +} + +/** + * Get the format details for a particular id. + * + * @param $dfid + * Identifier of a date format string. + * @return + * Array of date format details. + */ +function system_get_date_format($dfid) { + return db_query('SELECT df.dfid, df.format, df.type, df.locked FROM {date_formats} df WHERE df.dfid = :dfid', array(':dfid' => $dfid))->fetch(); +} + +/** + * Resets the database cache of date formats and saves all new formats. + */ +function system_date_formats_rebuild() { + drupal_static_reset('system_get_date_formats'); + $date_formats = system_get_date_formats(NULL); + + foreach ($date_formats as $format_type => $formats) { + foreach ($formats as $format => $info) { + system_date_format_save($info); + } + } + + // Rebuild configured date formats locale list. + drupal_static_reset('system_date_format_locale'); + system_date_format_locale(); + + _system_date_formats_build(); +} + +/** + * Get the appropriate date format for a type and locale. + * + * @param $langcode + * Language code for the current locale. This can be a 2 character language + * code like 'en', 'fr', or a longer 5 character code like 'en-gb'. + * @param $type + * Date type: short, medium, long, custom. + * @return + * The format string, or NULL if no matching format found. + */ +function system_date_format_locale($langcode = NULL, $type = NULL) { + $formats = &drupal_static(__FUNCTION__); + + if (empty($formats)) { + $formats = array(); + $result = db_query("SELECT format, type, language FROM {date_format_locale}"); + foreach ($result as $record) { + if (!isset($formats[$record->language])) { + $formats[$record->language] = array(); + } + $formats[$record->language][$record->type] = $record->format; + } + } + + if ($type && $langcode && !empty($formats[$langcode][$type])) { + return $formats[$langcode][$type]; + } + elseif ($langcode && !empty($formats[$langcode])) { + return $formats[$langcode]; + } + + return FALSE; +} + +/** + * Builds and returns the list of available date types. + * + * @return + * Array of date types. + */ +function _system_date_format_types_build() { + $types = array(); + + // Get list of modules which implement hook_date_format_types(). + $modules = module_implements('date_format_types'); + + foreach ($modules as $module) { + $module_types = module_invoke($module, 'date_format_types'); + foreach ($module_types as $module_type => $type_title) { + $type = array(); + $type['module'] = $module; + $type['type'] = $module_type; + $type['title'] = $type_title; + $type['locked'] = 1; + $type['is_new'] = TRUE; // Will be over-ridden later if in the db. + $types[$module_type] = $type; + } + } + + // Get custom formats added to the database by the end user. + $result = db_query('SELECT dft.type, dft.title, dft.locked FROM {date_format_type} dft ORDER BY dft.title'); + foreach ($result as $record) { + if (!in_array($record->type, $types)) { + $type = array(); + $type['is_new'] = FALSE; + $type['module'] = ''; + $type['type'] = $record->type; + $type['title'] = $record->title; + $type['locked'] = $record->locked; + $types[$record->type] = $type; + } + else { + $type = array(); + $type['is_new'] = FALSE; // Over-riding previous setting. + $types[$record->type] = array_merge($types[$record->type], $type); + } + } + + // Allow other modules to modify these date types. + drupal_alter('date_format_types', $types); + + return $types; +} + +/** + * Builds and returns the list of available date formats. + * + * @return + * Array of date formats. + */ +function _system_date_formats_build() { + $date_formats = array(); + + // First handle hook_date_format_types(). + $types = _system_date_format_types_build(); + foreach ($types as $type => $info) { + system_date_format_type_save($info); + } + + // Get formats supplied by various contrib modules. + $module_formats = module_invoke_all('date_formats'); + + foreach ($module_formats as $module_format) { + $module_format['locked'] = 1; // System types are locked. + // If no date type is specified, assign 'custom'. + if (!isset($module_format['type'])) { + $module_format['type'] = 'custom'; + } + if (!in_array($module_format['type'], array_keys($types))) { + continue; + } + if (!isset($date_formats[$module_format['type']])) { + $date_formats[$module_format['type']] = array(); + } + + // If another module already set this format, merge in the new settings. + if (isset($date_formats[$module_format['type']][$module_format['format']])) { + $date_formats[$module_format['type']][$module_format['format']] = array_merge_recursive($date_formats[$module_format['type']][$module_format['format']], $format); + } + else { + // This setting will be overridden later if it already exists in the db. + $module_format['is_new'] = TRUE; + $date_formats[$module_format['type']][$module_format['format']] = $module_format; + } + } + + // Get custom formats added to the database by the end user. + $result = db_query('SELECT df.dfid, df.format, df.type, df.locked, dfl.language FROM {date_formats} df LEFT JOIN {date_format_type} dft ON df.type = dft.type LEFT JOIN {date_format_locale} dfl ON df.format = dfl.format AND df.type = dfl.type ORDER BY df.type, df.format'); + foreach ($result as $record) { + // If this date type isn't set, initialise the array. + if (!isset($date_formats[$record->type])) { + $date_formats[$record->type] = array(); + } + $format = (array) $record; + $format['is_new'] = FALSE; // It's in the db, so override this setting. + // If this format not already present, add it to the array. + if (!isset($date_formats[$record->type][$record->format])) { + $format['module'] = ''; + $format['locales'] = array($record->language); + $date_formats[$record->type][$record->format] = $format; + } + // Format already present, so merge in settings. + else { + if (!empty($record->language)) { + $format['locales'] = array_merge($date_formats[$record->type][$record->format]['locales'], array($record->language)); + } + $date_formats[$record->type][$record->format] = array_merge($date_formats[$record->type][$record->format], $format); + } + } + + // Allow other modules to modify these formats. + drupal_alter('date_formats', $date_formats); + + return $date_formats; +} + +/** + * Save a date type to the database. + * + * @param $date_format_type + * An array of attributes for a date type. + */ +function system_date_format_type_save($date_format_type) { + $type = array(); + $type['type'] = $date_format_type['type']; + $type['title'] = $date_format_type['title']; + $type['locked'] = $date_format_type['locked']; + + // Update date_format table. + if (!empty($date_format_type['is_new'])) { + drupal_write_record('date_format_type', $type); + } + else { + drupal_write_record('date_format_type', $type, 'type'); + } +} + +/** + * Delete a date type from the database. + * + * @param $date_format_type + * The date type name. + */ +function system_date_format_type_delete($date_format_type) { + db_delete('date_formats') + ->condition('type', $date_format_type) + ->execute(); + db_delete('date_format_type') + ->condition('type', $date_format_type) + ->execute(); + db_delete('date_format_locale') + ->condition('type', $date_format_type) + ->execute(); +} + +/** + * Save a date format to the database. + * + * @param $date_format + * An array of attributes for a date format. + * @param $dfid + * If set, replace an existing date format with a new string using this + * identifier. + */ +function system_date_format_save($date_format, $dfid = 0) { + $format = array(); + $format['dfid'] = $dfid; + $format['type'] = $date_format['type']; + $format['format'] = $date_format['format']; + $format['locked'] = $date_format['locked']; + + // Update date_format table. + if (!empty($date_format['is_new'])) { + drupal_write_record('date_formats', $format); + } + else { + $keys = ($dfid ? array('dfid') : array('format', 'type')); + drupal_write_record('date_formats', $format, $keys); + } + + $languages = language_list('enabled'); + $languages = $languages[1]; + + $locale_format = array(); + $locale_format['type'] = $date_format['type']; + $locale_format['format'] = $date_format['format']; + + // Check if the suggested language codes are configured and enabled. + if (!empty($date_format['locales'])) { + foreach ($date_format['locales'] as $langcode) { + // Only proceed if language is enabled. + if (in_array($langcode, $languages)) { + $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE type = :type AND language = :language', 0, 1, array(':type' => $date_format['type'], ':language' => $langcode))->fetchField(); + if (!$is_existing) { + $locale_format['language'] = $langcode; + drupal_write_record('date_format_locale', $locale_format); + } + } + } + } +} + +/** + * Delete a date format from the database. + * + * @param $date_format_id + * The date format string identifier. + */ +function system_date_format_delete($dfid) { + db_delete('date_formats') + ->condition('dfid', $dfid) + ->execute(); +} + diff --git a/modules/system/system.test b/modules/system/system.test index 78bece99a2f2..226adff0fdc0 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -718,6 +718,15 @@ class DateTimeFunctionalTest extends DrupalWebTestCase { ); } + function setUp() { + parent::setUp(); + + // Create admin user and log in admin user. + $this->admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($this->admin_user); + } + + /** * Test time zones and DST handling. */ @@ -748,6 +757,77 @@ class DateTimeFunctionalTest extends DrupalWebTestCase { $this->drupalGet("node/$node2->nid"); $this->assertText('2007-08-01 00:00:00 -0700', t('Date should be three hours ahead, with GMT offset of -7 hours.')); } + + /** + * Test date type configuration. + */ + function testDateTypeConfiguration() { + // Confirm system date types appear. + $this->drupalGet('admin/config/regional/date-time'); + $this->assertText(t('Medium'), 'System date types appear in date type list.'); + $this->assertNoRaw('href="/admin/config/regional/date-time/types/medium/delete"', 'No delete link appear for system date types.'); + + // Add custom date type. + $this->clickLink(t('Add date type')); + $date_type = $this->randomName(8); + $machine_name = 'machine_' . $date_type; + $date_format = 'd.m.Y - H:i'; + $edit = array( + 'date_type' => $date_type, + 'machine_name' => $machine_name, + 'date_format' => $date_format, + ); + $this->drupalPost('admin/config/regional/date-time/types/add', $edit, t('Add date type')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertText(t('New date type added successfully.'), 'Date type added confirmation message appears.'); + $this->assertText($date_type, 'Custom date type appears in the date type list.'); + $this->assertText(t('delete'), 'Delete link for custom date type appears.'); + + // Delete custom date type. + $this->clickLink(t('delete')); + $this->drupalPost('admin/config/regional/date-time/types/' . $machine_name . '/delete', array(), t('Remove')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertText(t('Removed date type ' . $date_type), 'Custom date type removed.'); + } + + /** + * Test date format configuration. + */ + function testDateFormatConfiguration() { + // Confirm 'no custom date formats available' message appears. + $this->drupalGet('admin/config/regional/date-time/formats'); + $this->assertText(t('No custom date formats available.'), 'No custom date formats message appears.'); + + // Add custom date format. + $this->clickLink(t('Add format')); + $edit = array( + 'date_format' => 'Y', + ); + $this->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/date-time/formats', array('absolute' => TRUE)), t('Correct page redirection.')); + $this->assertNoText(t('No custom date formats available.'), 'No custom date formats message does not appear.'); + $this->assertText(t('Custom date format added.'), 'Custom date format added.'); + + // Ensure custom date format appears in date type configuration options. + $this->drupalGet('admin/config/regional/date-time'); + $this->assertRaw('