date_repeat_field.module 26 KB

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