date_repeat_field.module

Tracking 7.x-2.x branch
  1. drupal
    1. 7 contributions/date/date_repeat_field/date_repeat_field.module

Creates the option of Repeating date fields and manages Date fields that use the Date Repeat API.

The Repeating functionality is pretty tightly intermingled with other code, so the process of pulling it out into this module will happen gradually.

The current implementation adds a repeat form to the date field so the user can select the repeat rules. That selection is built into an RRULE which is stored in the zero position of the field values. During widget validation, the rule is parsed to see what dates it will create, and multiple values are added to the field, one for each repeat date. That update only happens when the rule, the start date, or the end date change, no need to waste processing cycles for other changes to the node values.

Lots of possible TODOs, the biggest one is figuring out the best way to handle dates with no UNTIL date since we can't add an infinite number of values to the field. For now, we require the UNTIL date.

Functions & methods

NameDescription
date_is_repeat_field
date_repeat_after_buildImplements the form after_build().
date_repeat_build_datesHelper function to build repeating dates from a $node_field.
date_repeat_field_bundlesReturn an array of all entity types and bundles that have repeating date fields.
date_repeat_field_date_combo_pre_validate_alterImplements hook_date_combo_pre_validate_alter().
date_repeat_field_date_combo_process_alterImplements hook_date_combo_process_alter().
date_repeat_field_date_field_formatter_settings_form_alterImplements hook_date_field_foramatter_settings_form_alter().
date_repeat_field_date_field_formatter_settings_summary_alterImplements hook_date_field_foramatter_settings_summary_alter().
date_repeat_field_date_field_insert_alter
date_repeat_field_date_field_instance_settings_form_alterImplements hook_date_field_instance_settings_form_alter().
date_repeat_field_date_field_settings_form_alterImplements hook_date_field_settings_form_alter().
date_repeat_field_date_field_update_alter
date_repeat_field_date_field_widget_settings_form_alterImplements hook_date_field_widget_settings_form_alter().
date_repeat_field_field_formatter_info_alterImplements hook_field_formatter_info_alter().
date_repeat_field_field_info_alterImplements hook_field_info_alter().
date_repeat_field_field_widget_form_alterImplements hook_field_widget_form_alter().
date_repeat_field_field_widget_info_alterImplements hook_field_widget_info_alter().
date_repeat_field_form_field_ui_field_edit_form_alterImplements hook_form_FORM_ID_alter() for field_ui_field_edit_form().
date_repeat_field_menuImplements hook_menu().
date_repeat_field_pageA page to list all values for a repeating date.
date_repeat_field_permissionImplements hook_permission().
date_repeat_field_set_cardinalityEnsure the cardinality gets updated if the option to make a date repeating is checked.
date_repeat_field_showSee if the user can access repeat date info for this field.
date_repeat_field_themeImplements hook_theme().
date_repeat_field_widget_validateValidation for date repeat form element.
theme_date_repeat_displayTheme the human-readable description for a Date Repeat rule.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Creates the option of Repeating date fields and manages Date fields that use the Date Repeat API.
  5. *
  6. * The Repeating functionality is pretty tightly intermingled with other code,
  7. * so the process of pulling it out into this module will happen gradually.
  8. *
  9. * The current implementation adds a repeat form to the date field so the user
  10. * can select the repeat rules. That selection is built into an RRULE
  11. * which is stored in the zero position of the field values. During widget
  12. * validation, the rule is parsed to see what dates it will create,
  13. * and multiple values are added to the field, one for each repeat date.
  14. * That update only happens when the rule, the start date, or the end date
  15. * change, no need to waste processing cycles for other changes to the node
  16. * values.
  17. *
  18. * Lots of possible TODOs, the biggest one is figuring out the best
  19. * way to handle dates with no UNTIL date since we can't add an infinite
  20. * number of values to the field. For now, we require the UNTIL date.
  21. */
  22. /**
  23. * Implements hook_theme().
  24. */
  25. function date_repeat_field_theme() {
  26. $themes = array(
  27. 'date_repeat_display' => array(
  28. 'variables' => array(
  29. 'field' => NULL,
  30. 'item' => NULL,
  31. 'entity_type' => NULL,
  32. 'entity' => NULL,
  33. 'dates' => NULL
  34. ),
  35. 'function' => 'theme_date_repeat_display',
  36. ),
  37. );
  38. return $themes;
  39. }
  40. /**
  41. * Theme the human-readable description for a Date Repeat rule.
  42. *
  43. * TODO -
  44. * add in ways to store the description in the date so it isn't regenerated
  45. * over and over and find a way to allow description to be shown or hidden.
  46. */
  47. function theme_date_repeat_display($vars) {
  48. $field = $vars['field'];
  49. $item = $vars['item'];
  50. $entity = !empty($vars['node']) ? $vars['node'] : NULL;
  51. $output = '';
  52. if (!empty($item['rrule'])) {
  53. $output = date_repeat_rrule_description($item['rrule']);
  54. $output = '<div>' . $output . '</div>';
  55. }
  56. return $output;
  57. }
  58. /**
  59. * Implements hook_menu().
  60. *
  61. * Add menu tabs to display pages with details about repeating date values.
  62. */
  63. function date_repeat_field_menu() {
  64. $items = array();
  65. $values = date_repeat_field_bundles();
  66. foreach ($values as $entity_type => $bundles) {
  67. if (module_exists('field_collection') && $entity_type == 'field_collection_item') {
  68. foreach ($bundles as $bundle => $fields) {
  69. $field = field_info_field($bundle);
  70. if ($field['type'] == 'field_collection') {
  71. $path = field_collection_field_get_path($field);
  72. $count = count(explode('/', $path));
  73. $items[$path . '/%field_collection_item/repeats'] = array(
  74. 'title' => 'Repeats',
  75. 'page callback' => 'date_repeat_field_page',
  76. 'page arguments' => array($entity_type, $count),
  77. 'access callback' => 'date_repeat_field_show',
  78. 'access arguments' => array($entity_type, $count),
  79. 'type' => MENU_LOCAL_TASK,
  80. 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  81. );
  82. }
  83. }
  84. }
  85. else {
  86. $path = $entity_type . '/%' . $entity_type;
  87. $items[$path . '/repeats'] = array(
  88. 'title' => 'Repeats',
  89. 'page callback' => 'date_repeat_field_page',
  90. 'page arguments' => array($entity_type, 1),
  91. 'access callback' => 'date_repeat_field_show',
  92. 'access arguments' => array($entity_type, 1),
  93. 'type' => MENU_LOCAL_TASK,
  94. 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  95. );
  96. }
  97. }
  98. return $items;
  99. }
  100. /**
  101. * Implements hook_permission().
  102. */
  103. function date_repeat_field_permission() {
  104. return array('view date repeats' => array(
  105. 'title' => t('View Repeating Dates'),
  106. 'description' => t('Allow user to see a page with all the times a date repeats.'),
  107. ));
  108. }
  109. /**
  110. * See if the user can access repeat date info for this field.
  111. */
  112. function date_repeat_field_show($entity_type = 'node', $entity = NULL) {
  113. $bundle = date_get_entity_bundle($entity_type, $entity);
  114. foreach (field_info_fields() as $field_name => $field) {
  115. if (in_array($field['type'], array('date', 'datestamp', 'datetime'))
  116. && array_key_exists($entity_type, $field['bundles'])
  117. && in_array($bundle, $field['bundles'][$entity_type])
  118. && date_is_repeat_field($field)) {
  119. return user_access('view date repeats');
  120. }
  121. }
  122. return FALSE;
  123. }
  124. /**
  125. * A page to list all values for a repeating date.
  126. */
  127. function date_repeat_field_page($entity_type = 'node', $entity = NULL) {
  128. $bundle = date_get_entity_bundle($entity_type, $entity);
  129. $info = entity_get_info($entity_type);
  130. $key = $info['entity keys']['id'];
  131. drupal_set_title(t('Repeats'));
  132. $entity->date_repeat_show_all = TRUE;
  133. $entity->content = array();
  134. $output = '';
  135. foreach (field_info_fields() as $field_name => $field) {
  136. if (in_array($field['type'], array('date', 'datestamp', 'datetime')) && date_is_repeat_field($field)) {
  137. foreach ($field['bundles'] as $field_entity_type => $bundles) {
  138. foreach ($bundles as $field_bundle) {
  139. if ($entity_type == $field_entity_type && $bundle == $field_bundle) {
  140. $data = field_view_field($entity_type, $entity, $field_name);
  141. $output .= drupal_render($data);
  142. }
  143. }
  144. }
  145. }
  146. }
  147. return $output;
  148. }
  149. /**
  150. * Return an array of all entity types and bundles that have repeating date fields.
  151. */
  152. function date_repeat_field_bundles() {
  153. $values = array();
  154. foreach (field_info_fields() as $field_name => $field) {
  155. if (in_array($field['type'], array('date', 'datestamp', 'datetime')) && $field['settings']['repeat']) {
  156. foreach ($field['bundles'] as $entity_type => $bundles) {
  157. foreach ($bundles as $bundle) {
  158. $values[$entity_type][$bundle][] = $field_name;
  159. }
  160. }
  161. }
  162. }
  163. return $values;
  164. }
  165. function date_is_repeat_field($field, $instance = NULL) {
  166. if (is_string($field)) {
  167. $field = field_info_field($field);
  168. }
  169. if (!isset($field['settings']['repeat'])) {
  170. return FALSE;
  171. }
  172. $value = $field['settings']['repeat'];
  173. // This might be either a field form or a real field.
  174. if (is_array($value)) {
  175. return $value['#value'];
  176. }
  177. else {
  178. return $value;
  179. }
  180. }
  181. /*
  182. * Implements hook_date_field_insert_alter().
  183. */
  184. function date_repeat_field_date_field_insert_alter(&$items, $context) {
  185. $entity = $context['entity'];
  186. $field = $context['field'];
  187. $instance = $context['instance'];
  188. $langcode = $context['langcode'];
  189. // If an RRULE with a frequency of NONE made it this far, unset it.
  190. if (!empty($items[0]['rrule']) && strpos($items[0]['rrule'], 'FREQ=NONE')) {
  191. $items[0]['rrule'] = NULL;
  192. }
  193. // We can't use hook_devel_generate() because we need custom handling for
  194. // repeating date fields. So we wait until the entity is inserted, then
  195. // intervene here to fix it.
  196. if (!empty($entity->devel_generate) && !empty($field['settings']['repeat'])) {
  197. module_load_include('inc', 'date_repeat_field', 'date_repeat_field.devel_generate');
  198. date_repeat_field_date_field_insert($items, $context);
  199. }
  200. }
  201. /*
  202. * Implements hook_date_field_update_alter().
  203. */
  204. function date_repeat_field_date_field_update_alter(&$items, $context) {
  205. // If an RRULE with a frequency of NONE made it this far, unset it.
  206. if (!empty($items[0]['rrule']) && strpos($items[0]['rrule'], 'FREQ=NONE')) {
  207. $items[0]['rrule'] = NULL;
  208. }
  209. // If you have a repeating date field on a user and don't check the box to repeat it,
  210. // we end up with $items[0]['rrule'] = array('additions' => '', 'exceptions' => ''));
  211. // This will clean it up by getting rid of those bogus values.
  212. // @TODO Figure out where that's coming from. It doesn't happen on nodes.
  213. if (!empty($items[0]['rrule']) && is_array($items[0]['rrule'])) {
  214. $items[0]['rrule'] = NULL;
  215. }
  216. }
  217. /**
  218. * Implements hook_field_widget_form_alter().
  219. */
  220. function date_repeat_field_field_widget_form_alter(&$element, &$form_state, $context) {
  221. $field = $context['field'];
  222. $instance = $context['instance'];
  223. $items = $context['items'];
  224. $delta = $context['delta'];
  225. if (in_array($field['type'], array('date', 'datetime', 'datestamp'))) {
  226. if (!empty($field['settings']['repeat'])) {
  227. $element['#element_validate'][] = 'date_repeat_field_widget_validate';
  228. $element['show_repeat_settings'] = array(
  229. '#type' => 'checkbox',
  230. '#title' => t('Repeat'),
  231. '#weight' => $instance['widget']['weight'] + .3,
  232. '#prefix' => '<div class="date-clear">',
  233. '#suffix' => '</div>',
  234. '#default_value' => isset($items[$delta]['rrule']) && !empty($items[$delta]['rrule']) ? 1 : 0,
  235. );
  236. }
  237. }
  238. }
  239. /**
  240. * Validation for date repeat form element.
  241. *
  242. * Create multiple values from the RRULE results.
  243. * Lots more work needed here.
  244. */
  245. function date_repeat_field_widget_validate($element, &$form_state) {
  246. $field = field_widget_field($element, $form_state);
  247. if (empty($field['settings']['repeat'])) {
  248. return;
  249. }
  250. $field_name = $element['#field_name'];
  251. $delta = $element['#delta'];
  252. $langcode = $element['#language'];
  253. // If the widget has been hidden by #access, the RRULE will still be in its
  254. // original string form here. Nothing to process.
  255. if (date_hidden_element($element)) {
  256. // If this was a hidden repeating date, we lost all the repeating values in the widget processing.
  257. // Add them back here if that happened since we are skipping the re-creation of those values.
  258. if (!empty($form_state['storage']['date_items'][$field_name])) {
  259. array_pop($element['#parents']);
  260. form_set_value($element, $form_state['storage']['date_items'][$field_name][$langcode], $form_state);
  261. }
  262. return;
  263. }
  264. module_load_include('inc', 'date_repeat', 'date_repeat_form');
  265. $instance = field_widget_instance($element, $form_state);
  266. // Here 'values' returns an array of input values, which includes the original RRULE, as a string.
  267. // and 'input' returns an array of the form elements created by the repeating date widget, with
  268. // RRULE values as an array of the selected elements and their chosen values.
  269. $item = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
  270. $input = drupal_array_get_nested_value($form_state['input'], $element['#parents'], $input_exists);
  271. $rrule_values = date_repeat_merge($input['rrule'], $element['rrule']);
  272. // If no repeat information was set, treat this as a normal, non-repeating value.
  273. if ($rrule_values['FREQ'] == 'NONE' || empty($input['show_repeat_settings'])) {
  274. $item['rrule'] = NULL;
  275. form_set_value($element, $item, $form_state);
  276. return;
  277. }
  278. // If no start date was set, clean up the form and return.
  279. if (empty($item['value'])) {
  280. form_set_value($element, NULL, $form_state);
  281. return;
  282. }
  283. // Require the UNTIL date for now.
  284. // The RRULE has already been created by this point, so go back
  285. // to the posted values to see if this was filled out.
  286. $error_field_base = implode('][', $element['#parents']);
  287. $error_field_until = $error_field_base . '][rrule][until_child][datetime][';
  288. if (!empty($item['rrule']) && $rrule_values['range_of_repeat'] === 'UNTIL' && empty($rrule_values['UNTIL']['datetime'])) {
  289. switch ($instance['widget']['type']) {
  290. case 'date_text':
  291. case 'date_popup':
  292. form_set_error($error_field_until . 'date', t("Missing value in 'Range of repeat'. (UNTIL).", array(), array('context' => 'Date repeat')));
  293. break;
  294. case 'date_select':
  295. form_set_error($error_field_until . 'year', t("Missing value in 'Range of repeat': Year (UNTIL)", array(), array('context' => 'Date repeat')));
  296. form_set_error($error_field_until . 'month', t("Missing value in 'Range of repeat': Month (UNTIL)", array(), array('context' => 'Date repeat')));
  297. form_set_error($error_field_until . 'day', t("Missing value in 'Range of repeat': Day (UNTIL)", array(), array('context' => 'Date repeat')));
  298. break;
  299. }
  300. }
  301. $error_field_count = $error_field_base . '][rrule][count_child';
  302. if (!empty($item['rrule']) && $rrule_values['range_of_repeat'] === 'COUNT' && empty($rrule_values['COUNT'])) {
  303. form_set_error($error_field_count, t("Missing value in 'Range of repeat'. (COUNT).", array(), array('context' => 'Date repeat')));
  304. }
  305. if (form_get_errors()) {
  306. return;
  307. }
  308. // If the rule, the start date, or the end date have changed, re-calculate
  309. // the repeating dates, wipe out the previous values, and populate the
  310. // field with the new values.
  311. $rrule = $item['rrule'];
  312. if (!empty($rrule)) {
  313. // Avoid undefined index problems on dates that don't have all parts.
  314. $possible_items = array('value', 'value2', 'timezone', 'offset', 'offset2');
  315. foreach ($possible_items as $key) {
  316. if (empty($item[$key])) {
  317. $item[$key] = '';
  318. }
  319. }
  320. // We only collect a date for UNTIL, but we need it to be inclusive,
  321. // so force it to a full datetime element at the last possible second of the day.
  322. if (!empty($rrule_values['UNTIL'])) {
  323. $rrule_values['UNTIL']['datetime'] .= ' 23:59:59';
  324. $rrule_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second')));
  325. $rrule_values['UNTIL']['all_day'] = 0;
  326. }
  327. $value = date_repeat_build_dates($rrule, $rrule_values, $field, $item);
  328. // Unset the delta value of the parents.
  329. array_pop($element['#parents']);
  330. // Set the new delta values for this item to the array of values returned by the repeat rule.
  331. form_set_value($element, $value, $form_state);
  332. }
  333. }
  334. /**
  335. * Implements the form after_build().
  336. *
  337. * Remove the 'Add more' elements from a repeating date form.
  338. */
  339. function date_repeat_after_build(&$element, &$form_state) {
  340. foreach ($form_state['storage']['repeat_fields'] as $field_name => $parents) {
  341. // Remove unnecessary items in the form added by the Add more handling.
  342. $value = drupal_array_get_nested_value($element, $parents);
  343. $langcode = $value['#language'];
  344. unset($value[$langcode]['add_more'], $value[$langcode]['#suffix'], $value[$langcode]['#prefix'], $value[$langcode][0]['_weight']);
  345. $value[$langcode]['#cardinality'] = 1;
  346. $value[$langcode]['#max_delta'] = 1;
  347. drupal_array_set_nested_value($element, $parents, $value);
  348. }
  349. return $element;
  350. }
  351. /**
  352. * Helper function to build repeating dates from a $node_field.
  353. *
  354. * Pass in either the RRULE or the $form_values array for the RRULE,
  355. * whichever is missing will be created when needed.
  356. */
  357. function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $item) {
  358. include_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc');
  359. $field_name = $field['field_name'];
  360. if (empty($rrule)) {
  361. $rrule = date_api_ical_build_rrule($rrule_values);
  362. }
  363. elseif (empty($rrule_values)) {
  364. $rrule_values = date_ical_parse_rrule(NULL, $rrule);
  365. }
  366. // By the time we get here, the start and end dates have been
  367. // adjusted back to UTC, but we want localtime dates to do
  368. // things like '+1 Tuesday', so adjust back to localtime.
  369. $timezone = date_get_timezone($field['settings']['tz_handling'], $item['timezone']);
  370. $timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
  371. $start = new DateObject($item['value'], $timezone_db, date_type_format($field['type']));
  372. $start->limitGranularity($field['settings']['granularity']);
  373. if ($timezone != $timezone_db) {
  374. date_timezone_set($start, timezone_open($timezone));
  375. }
  376. if (!empty($item['value2']) && $item['value2'] != $item['value']) {
  377. $end = new DateObject($item['value2'], date_get_timezone_db($field['settings']['tz_handling']), date_type_format($field['type']));
  378. $end->limitGranularity($field['settings']['granularity']);
  379. date_timezone_set($end, timezone_open($timezone));
  380. }
  381. else {
  382. $end = $start;
  383. }
  384. $duration = $start->difference($end);
  385. $start_datetime = date_format($start, DATE_FORMAT_DATETIME);
  386. if (!empty($rrule_values['UNTIL']['datetime'])) {
  387. $end = date_ical_date($rrule_values['UNTIL'], $timezone);
  388. $end_datetime = date_format($end, DATE_FORMAT_DATETIME);
  389. }
  390. elseif (!empty($rrule_values['COUNT'])) {
  391. $end_datetime = NULL;
  392. }
  393. else {
  394. // No UNTIL and no COUNT?
  395. return array();
  396. }
  397. // Split the RRULE into RRULE, EXDATE, and RDATE parts.
  398. $parts = date_repeat_split_rrule($rrule);
  399. $parsed_exceptions = (array) $parts[1];
  400. $exceptions = array();
  401. foreach ($parsed_exceptions as $exception) {
  402. $date = date_ical_date($exception, $timezone);
  403. $exceptions[] = date_format($date, 'Y-m-d');
  404. }
  405. $parsed_rdates = (array) $parts[2];
  406. $additions = array();
  407. foreach ($parsed_rdates as $rdate) {
  408. $date = date_ical_date($rdate, $timezone);
  409. $additions[] = date_format($date, 'Y-m-d');
  410. }
  411. $dates = date_repeat_calc($rrule, $start_datetime, $end_datetime, $exceptions, $timezone, $additions);
  412. $value = array();
  413. foreach ($dates as $delta => $date) {
  414. // date_repeat_calc always returns DATE_DATETIME dates, which is
  415. // not necessarily $field['type'] dates.
  416. // Convert returned dates back to db timezone before storing.
  417. $date_start = new DateObject($date, $timezone, DATE_FORMAT_DATETIME);
  418. $date_start->limitGranularity($field['settings']['granularity']);
  419. date_timezone_set($date_start, timezone_open($timezone_db));
  420. $date_end = clone($date_start);
  421. date_modify($date_end, '+' . $duration . ' seconds');
  422. $value[$delta] = array(
  423. 'value' => date_format($date_start, date_type_format($field['type'])),
  424. 'value2' => date_format($date_end, date_type_format($field['type'])),
  425. 'offset' => date_offset_get($date_start),
  426. 'offset2' => date_offset_get($date_end),
  427. 'timezone' => $timezone,
  428. 'rrule' => $rrule,
  429. );
  430. }
  431. return $value;
  432. }
  433. /**
  434. * Implements hook_date_combo_process_alter().
  435. *
  436. * This hook lets us make changes to the date_combo element.
  437. */
  438. function date_repeat_field_date_combo_process_alter(&$element, &$form_state, $context) {
  439. $field = $context['field'];
  440. $instance = $context['instance'];
  441. $field_name = $element['#field_name'];
  442. $delta = $element['#delta'];
  443. // Add a date repeat form element, if needed.
  444. // We delayed until this point so we don't bother adding it to hidden fields.
  445. if (date_is_repeat_field($field, $instance)) {
  446. $item = $element['#value'];
  447. $element['rrule'] = array(
  448. '#type' => 'date_repeat_rrule',
  449. '#theme_wrappers' => array('date_repeat_rrule'),
  450. '#default_value' => isset($item['rrule']) ? $item['rrule'] : '',
  451. '#date_timezone' => $element['#date_timezone'],
  452. '#date_format' => date_limit_format(date_input_format($element, $field, $instance), $field['settings']['granularity']),
  453. '#date_text_parts' => (array) $instance['widget']['settings']['text_parts'],
  454. '#date_increment' => $instance['widget']['settings']['increment'],
  455. '#date_year_range' => $instance['widget']['settings']['year_range'],
  456. '#date_label_position' => $instance['widget']['settings']['label_position'],
  457. '#date_repeat_widget' => str_replace('_repeat', '', $instance['widget']['type']),
  458. '#date_repeat_collapsed' => $instance['widget']['settings']['repeat_collapsed'],
  459. '#date_flexible' => 0,
  460. '#weight' => $instance['widget']['weight'] + .4,
  461. );
  462. }
  463. }
  464. /**
  465. * Implements hook_date_combo_pre_validate_alter().
  466. *
  467. * This hook lets us alter the element or the form_state before the rest
  468. * of the date_combo validation gets fired.
  469. */
  470. function date_repeat_field_date_combo_pre_validate_alter(&$element, &$form_state, $context) {
  471. // Just a placeholder for now.
  472. }
  473. /**
  474. * Implements hook_field_info_alter().
  475. *
  476. * This Field API hook lets us add a new setting to the fields.
  477. */
  478. function date_repeat_field_field_info_alter(&$info) {
  479. $info['date']['settings'] += array(
  480. 'repeat' => 0,
  481. );
  482. $info['datetime']['settings'] += array(
  483. 'repeat' => 0,
  484. );
  485. $info['datestamp']['settings'] += array(
  486. 'repeat' => 0,
  487. );
  488. }
  489. /**
  490. * Implements hook_field_formatter_info_alter().
  491. *
  492. * This hook lets us add settings to the formatters.
  493. */
  494. function date_repeat_field_field_formatter_info_alter(&$info) {
  495. if (isset($info['date_default'])) {
  496. $info['date_default']['settings'] += array(
  497. 'show_repeat_rule' => 'show',
  498. );
  499. }
  500. }
  501. /**
  502. * Implements hook_field_widget_info_alter().
  503. *
  504. * This Field API hook lets us add a new setting to the widgets.
  505. */
  506. function date_repeat_field_field_widget_info_alter(&$info) {
  507. $info['date_text']['settings'] += array(
  508. 'repeat_collapsed' => 0,
  509. );
  510. $info['date_select']['settings'] += array(
  511. 'repeat_collapsed' => 0,
  512. );
  513. if (module_exists('date_popup')) {
  514. $info['date_popup']['settings'] += array(
  515. 'repeat_collapsed' => 0,
  516. );
  517. }
  518. }
  519. /**
  520. * Implements hook_date_field_settings_form_alter().
  521. *
  522. * This hook lets us alter the field settings form.
  523. */
  524. function date_repeat_field_date_field_settings_form_alter(&$form, $context) {
  525. $field = $context['field'];
  526. $instance = $context['instance'];
  527. $has_data = $context['has_data'];
  528. $form['repeat'] = array(
  529. '#type' => 'select',
  530. '#title' => t('Repeating date'),
  531. '#default_value' => $field['settings']['repeat'],
  532. '#options' => array(0 => t('No'), 1 => t('Yes')),
  533. '#attributes' => array('class' => array('container-inline')),
  534. '#description' => t("Repeating dates use an 'Unlimited' number of values. Instead of the 'Add more' button, they include a form to select when and how often the date should repeat."),
  535. '#disabled' => $has_data,
  536. );
  537. }
  538. /**
  539. * Implements hook_form_FORM_ID_alter() for field_ui_field_edit_form().
  540. */
  541. function date_repeat_field_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
  542. $field = $form['#field'];
  543. $instance = $form['#instance'];
  544. if (!in_array($field['type'], array('date', 'datetime', 'datestamp'))) {
  545. return;
  546. }
  547. // If using repeating dates, override the Field module's handling of the multiple values option.
  548. if (date_is_repeat_field($field, $instance)) {
  549. $form['field']['cardinality']['#disabled'] = TRUE;
  550. $form['field']['cardinality']['#value'] = FIELD_CARDINALITY_UNLIMITED;
  551. }
  552. // Repeating dates need unlimited values, confirm that in element_validate.
  553. $form['field']['#element_validate'] = array('date_repeat_field_set_cardinality');
  554. }
  555. /**
  556. * Ensure the cardinality gets updated if the option to make a date repeating is checked.
  557. */
  558. function date_repeat_field_set_cardinality($element, &$form_state) {
  559. if (!empty($form_state['values']['field']['settings']['repeat'])) {
  560. form_set_value($element['cardinality'], FIELD_CARDINALITY_UNLIMITED, $form_state);
  561. }
  562. }
  563. /**
  564. * Implements hook_date_field_instance_settings_form_alter().
  565. *
  566. * This hook lets us alter the field instance settings form.
  567. */
  568. function date_repeat_field_date_field_instance_settings_form_alter(&$form, $context) {
  569. // Just a placeholder for now.
  570. }
  571. /**
  572. * Implements hook_date_field_widget_settings_form_alter().
  573. *
  574. * This hook lets us alter the field widget settings form.
  575. */
  576. function date_repeat_field_date_field_widget_settings_form_alter(&$form, $context) {
  577. $field = $context['field'];
  578. $instance = $context['instance'];
  579. if (date_is_repeat_field($field, $instance)) {
  580. $form['repeat_collapsed'] = array(
  581. '#type' => 'value',
  582. '#default_value' => 1,
  583. '#options' => array(
  584. 0 => t('Expanded', array(), array('context' => 'Date repeat')),
  585. 1 => t('Collapsed', array(), array('context' => 'Date repeat'))
  586. ),
  587. '#title' => t('Repeat display', array(), array('context' => 'Date repeat')),
  588. '#description' => t("Should the repeat options form start out expanded or collapsed? Set to 'Collapsed' to make those options less obtrusive.", array(), array('context' => 'Date repeat')),
  589. '#fieldset' => 'date_format',
  590. );
  591. }
  592. }
  593. /**
  594. * Implements hook_date_field_foramatter_settings_form_alter().
  595. *
  596. * This hook lets us alter the field formatter settings form.
  597. */
  598. function date_repeat_field_date_field_formatter_settings_form_alter(&$form, &$form_state, $context) {
  599. $field = $context['field'];
  600. $instance = $context['instance'];
  601. $view_mode = $context['view_mode'];
  602. $display = $instance['display'][$view_mode];
  603. $formatter = $display['type'];
  604. $settings = $display['settings'];
  605. if ($formatter == 'date_default') {
  606. $form['show_repeat_rule'] = array(
  607. '#title' => t('Repeat rule:'),
  608. '#type' => 'select',
  609. '#options' => array(
  610. 'show' => t('Show repeat rule'),
  611. 'hide' => t('Hide repeat rule')),
  612. '#default_value' => $settings['show_repeat_rule'],
  613. '#access' => $field['settings']['repeat'],
  614. '#weight' => 5,
  615. );
  616. }
  617. }
  618. /**
  619. * Implements hook_date_field_foramatter_settings_summary_alter().
  620. *
  621. * This hook lets us alter the field formatter settings summary.
  622. */
  623. function date_repeat_field_date_field_formatter_settings_summary_alter(&$summary, $context) {
  624. $field = $context['field'];
  625. $instance = $context['instance'];
  626. $view_mode = $context['view_mode'];
  627. $display = $instance['display'][$view_mode];
  628. $formatter = $display['type'];
  629. $settings = $display['settings'];
  630. if (isset($settings['show_repeat_rule']) && !empty($field['settings']['repeat'])) {
  631. if ($settings['show_repeat_rule'] == 'show') {
  632. $summary[] = t('Show repeat rule');
  633. }
  634. else {
  635. $summary[] = t('Hide repeat rule');
  636. }
  637. }
  638. }