FINAL suepr merge step : added all modules to this super repos

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 deletions

View File

@@ -0,0 +1,616 @@
Date Module 7.x
=================
===================
Version 7.x-2.x-dev
===================
======================
Version 7.x-2.6
======================
- Issue #1423364 by catmat, Add file and path to hook_context_plugins().
- Issue #1713248, Remove incorrect use of date_popup() function in previous commit.
- Issue #1143680 by kristiaanvandeneynde, Make Date Popup widget able to accept custom settings.
- Issue #1596546 by applicity_sam, Make sure incorrect month name creates validation error.
- Issue #1432702, Fix some miscellaneous problems with date example dates and formats.
- Issue #1512464 by Liam Moreland, Allow for undefined formatter in Date upgrade handling.
- Issue #1659638 by bojanz, Hide install message if Drupal is not installed (i.e. in install profiles).
- Issue #1605158 by covenantd, Be sure value2 is initiated in Date Context handler.
- Issue #1355256 by barraponto develCuy and KarenS, set default value for date text widget increment to 1, and update existing fields.
- Issue #1561306 by Cyberwolf, Date pager replacement was not working correctly when used with multiple dates.
- Issue #1541740 by hass, Make Date requirements more easily translatable.
- Issue #1543524 by iamEAP, Add update hook to remove the D6 date_timezone field from users.
- Issue #1603640 by bevan, Don't return anything for empty date interval.
- Issue #1235508, Make sure that ISO strings with '00' in the month and/or day don't create dates for the previous month and day.
- Issue #1289270 by pdrake: Fixed date arguments and filters do not work with relationships.
- Issue #895760 by Reinette: Added RFC2447 option 'STATUS' to date_api_ical().inc.
- Issue #606658 by johnmunro and KarenS, Make sure ical import processes multi-day all-day events correctly.
- Issue #1408216 follow up, Need to be sure that NULL is the default state for the sql functions.
- Issue #1540410, Update the Date Tools calendar creation wizard to use the new calendar path.
======================
Version 7.x-2.5
======================
- Fix nasty errors on views that have no row indexes, like the frontage view.
======================
Version 7.x-2.4
======================
- Issue #1534342 by bass, Make name of clean pager option easier to translate.
- Add a hook to alter the default date for a Date argument.
- Issue #1531904, Make sure that multiple value dates that have the delta set only display the selected value.
- Revert Issue #1266688 by fago, This change broke Entity token support.
======================
Version 7.x-2.3
======================
- Issue #1444804, Remove html placeholder on text widget.
- Issue #1286230, Clean up date pager documentation, it contains deprecated information.
- Issue #1469020, Make sure page not found is working right for dates outside the specified range.
- Issue #1111626, Fix some problems caused by passing in a date string with an offset.
- Issue #1477860, Avoid undefined index errors in date_views_select_validate().
- Issue #1469038, Add some clarity to the BYDAY description options and make sure a valid value is selected.
- Issue #1405364 by snufkin, Add display options to format_interval to match Views interval options.
- Issue #1266688 by linclark, Add microdata support to Date metadata.
- Issue #1509434 by casey, Add timezone info in hook_field_presave() instead of hook_field_insert() and hook_field_update().
- Issue #1509012, Fix fatal error when viewing repeat tab.
- Issue #1037174 by dealancer, More work to allow ajax to work on other widgets.
- Issue #1473592 by zabelc, Make sure migrated date with missing end date still gets imported correctly.
- Issue #1442718 by jastraat and bones, Make sure show end date checkbox does not display when end date is empty.
- Issue #1504556, Fix invalid access permission name in Date API configuration menu.
- Issue #1484458, Vega timepicker code was incorrectly computing the default date because of the wrong format for the value passed to it.
- Issue #1459668, Add back Remove date_field_get_sql_handler() function temporarily since Signup module is still using it. Mark it deprecated.
- Issue #1478296, Display longer abbreviations for day names in date repeat widget so they can be translated.
- Issue #1488662 by martin.heidegger, Make error message more easily translatable.
- Issue #1478848 by onelittleant, Don't set default values on new translation entities.
- Issue #1360672, Fix limit format code to remove leftover non-ascii text as well as leftover ascii text.
- Issue #1464280 by geerlingguy Georgique and dealancer: Fix notice when using exposed between filter.
- Issue #1484640 by onelittleant, Some entities may not have base tables.
- Issue #1472966, Get rid of notice that appears for entities without repeats.
- Move pager css around. Some code in Calendar and Date API belongs in Date Views.
- Fix Date Popup default format which was ending up using 24 hour time no matter what format was used for short format.
- Remove outdated _repeat widget name from Date Tools.
- Date Tools is defaulting to creating a calendar even if Calendar module is not enabled.
- Issue #1472148 by arlinsandbulte: wvega jQuery time-picker doesn't show scrollbar.
- Issue #1392128 by kidrobot, tim.plunkett, willvincent: Fixed Repeats tab shows on nodes with non-repeating dates.
- Issue #1452882 by coredumperror: Fixed Date Migrate Example doesn't deregister it's migration when disabled.
- Issue #1452408 by reglogge: Fixed Trailing dots are removed from date formats even if they are needed for the format.
- Issue #1285260 by Steven Jones: Fixed 'Starting from' display option.
- Issue #1467244 by bluetegu: Fixed Recurring Sunday errors.
- Issue #1442718, Empty default values were getting populated by the code that tried to fix default values on hidden fields.
- Make sure date_handler->placeholders gets initialized even for queries that don't execute so the pager won't throw an exception if the value is missing.
======================
Version 7.x-2.2
======================
NOTES:
The iCal templates were removed and moved to the new Date iCal module (http://drupal.org/project/date_ical).
- Issue #1423364 by emptyvoid, Move Date context plugin into its own file.
- Issue #1447372 by yched, Memory friendlier version of date_repeat_field_bundles().
- Issue #1447140 by tim.plunkett: Remove iCal-related theme functions/files.
- Issue #1284170 by benjifisher, Lots of cleanup of the iCal export and its template to be sure all day items are exported correctly.
- Issue #1346424, The calendar title got left out of the latest ical template changes.
- Add more information to the date_views_base_tables() function about why we can't use the entity type from views_fetch_data().
- Additional handling is needed so that revisions are handled correctly in Views.
- Remove date_field_get_sql_handler() function which isn't being used anywhere.
- Rework the Date Views hooks that queue up the right fields and base tables so they work better across all types of entities.
- Fix the hooks for file tables, it is file_managed, not files.
- Add a helper function to map entity types to Views base tables to make it easier to know what type of entity is being used on a view.
- Add status information to Date help screen, hide the non-working change tab on the Date Tools screen, and remove some code that goes to the calendar module from Date Tools.
======================
Version 7.x-2.1
======================
- Issue #1437242 by zerbash, Remove extraneous leading slashes in module_load_include().
- Issue #1436722 by hefox: Fixed Undefined variable $form_set_error() used as function.
- Issue #1250626 by G<>bor Hojtsy, B-Prod, hefox: Added start date and end date labels.
- Issue #1253482, Make sure $argument->is_default gets reset by the Date pager when altering results.
- Adjust Date Tools to work with changes to use Views templates to create calendars.
- Issue #1398584 by dhalbert and , Make sure groupby times is initialized.
- Issue #1394248, Make sure all date parts are selected when using an exposed select filter.
- Issue #1425774 by sgabe, Fix error in exposed filter, array_filter() -- first argument should be an array in date_select_input_date().
- Issue #1432992 by bart.hanssens, Fix typo in php documentation.
- Issue #323852 by tim.plunkett and arlinsandbulte, Dropdowns shouldn't include a blank option when required.
- Issue #323852, Validation was broken for unlimited value select widgets with required dates, they were incorrectly getting their end dates cleared out.
- Issue #1424656 by tim.plunkett, Unify signature and alters of #process callbacks.
- Issue #1408014 by dasjo, We no longer need the $error_element value to display errors properly, second follow-up.
- Issue #1299030 by Vincent B: Ensure that 'To Date' is properly marked when required.
======================
Version 7.x-2.0-rc2
======================
New Features/Major Changes
- Issue #1358790 by tim.plunkett and redndahead, Store date objects in field_load to speed up processing, with a field setting option to control it.
Bugfixes
- Issue #1423598, Found a way to flag items that are new so we know when to add default values.
- Made some fixes to find and test the right entities when checking whether to use default values. More work is needed.
- Issue #1422600, Make sure end date cannot cause validation errors when show end date checkbox is not checked.
- Issue #1417872, Remove code to compute missing date parts from empty values now that the new validation prevents that from working.
- Issue #1417872, Make sure empty year field is validated in the same way other date parts are validated.
- Alter date field test to test with a complete end date instead of using empty elements.
- Fix broken logic in repeat additions.
- Fix test broken by date repeat clean up.
- Issue #1419106 by hanoii: Added more info to hook_date_text_process_alter().
- Issue #1209026, When date validation fails, Date Popup value is getting cleared.
- Issue #1017216 by tim.plunkett, arlinsandbulte: Added custom date format without time shows 'all day'.
- Issue #1408014 by dasjo, We no longer need the $error_element value to display errors properly.
- Issue #554546 by master-of-magic: Fix Timezone list translation
- Issue #1411038 by jhodgdon: Fixed Date formatting is not obeying granularity.
- Issue #1359464, Make sure that default values on hidden elements have the same construct as loaded values to avoid errors.
- Issue #1399744 by tim.plunkett, Rework the way filter groups are used to be sure the Date filter group does not clobber the Views filter group.
- Issue #1411862, Move the date_views_fields() function into date_view.module to be sure it is always available.
- Issue #1380350, Rework SQL query handling to pass in a comparison date for computing offsets to better handle dates affected by DST adjustments.
- Issue #1359464, Temporary fix for broken handling of repeating dates on users with a TODO to figure out where it's coming from.
- Issue #1404494 by byrond, Make sure hidden formatters don't get switched to date_default in update hook.
- Issue #1386012, Order weekdays in Date Repeat form to match the site first day of week settings.
- Issue #1376476 by pbfleetwood, Add commas to default formatting of week and day headers.
- Issue #1408430, Views pagination with exposed, unset, filters, results in invalid default values. Treat them as empty input.
- Issue #1408996, Exposed Date Popup widget with time not correctly initialized.
- Issue #1271726, Text widgets in exposed filters used with pagers produce unexpected results.
- Issue #1096000, Custom date formats not working right.
- Issue #1405364, Format interval settings were getting overridden by missing break.
- Issue #1387890, Make sure disabled fields are shown and retain their original values.
- Issue #1402232 by timplunkett, Switch 'Implementation of' to 'Implements'
- Issue #1402236 by timplunkett, All functions should be prefixed with your module name to avoid name clashes
- Issue #1402238 by timplumkett, Remove references to CCK
- Issue #1011624, Filters and arguments were not always using the right base table to select field options, causing 'column missing' errors.
- Issue #1397822, Fix 'Undefined index: field' error on line 207 of date_views_filter_handler_simple.inc.
- Issue #1396536, Repeating fields weren't getting created with Devel Generate because of the change in widgets. Add new hooks so Date Repeat Field can add the repeats.
- Issue #1397126 follow-up by arlinsandbulte, Fix broken tests.
- Issue #1397158, The default value for the end date was using the wrong timezone setting.
- Issue #1392128 by kidrobot and tim.plunkett, Don't show repeat tab unless the date is repeating.
- Issue #1397126, Add Date API section to administration menu and consolidate Date Popup and Date Tools settings there.
- Follow up to Issue #1302052 by benjifisher, More clean up of ical line endings.
- Issue #1388174 by travist, Remove redundant use of date_default_timezone().
- Issue #1388586 by Moloc, Don't use check_plain() on system messages with links.
- Issue #1302052 by benjifisher, penguin25, and helmo, Fix linespace ending problems in ical files.
- Issue #1357216, The Date Views field list was including the Date Views filter itself, creating errors about missing node.date_filter values.
- Issue #1259870, Reinstate the test for a missing date argument for the pager.
- The new option to hide the add_delta for simple fields needs to be adjusted to work right where there is no field.
- Tweak the all day formula to check for 59:59 even for items that use increments, to help fix problems when the calendar creates all day values.
- Our test for whether this is a Date argument or filter needs to include a check that the field was actually processed by Date Views. Some dates are not.
- Revert the change to 7.8. It breaks the modules page.
- Issue #1379172 by deviantpixel, Note that the Date Repeat form uses a function that was re-named in 7.8.
- Store the locale format in a static cache to avoid re-computing it dozens of times on calendar views.
- Sheesh. Fix syntax error in api.date.php.
======================
Version 7.x-2.0-rc1
======================
Notes:
The Date Browser has been removed. Please use the Date Pager instead. If you have existing views using the Date
Browser the navigation will just disappear from them. If you add a Date Pager to the view you should get it back.
Then delete the Date Browser attachment from the view, since it doesn't do anything any more.
The UNTIL date was not getting included in repeating results and that is now fixed. This is an API change of sorts
for anyone who worked around the issue by setting it ahead.
The All Day checkbox and All Day themes were moved into a separate module, using new hooks added to the date
processing. This module should serve as an example of how other modules can inject functionality into date fields.
A new module has been added for integrating the Date Repeat API into date fields. Some of this code has been
moved into the new module, more of it will be moved later as I figure out how to unwind it from the base
processing. An update hook has been added to enable this module by default for existing sites. If you don't
use Repeating dates you can disable it.
New Features/Major Changes
- Issue #1229378 by ksenzee, Sync the end date with the start date on new dates using select widgets.
- Issue #1236216, Move the repeating date integration out of the Date module and into a separate, Date Repeat Field module.
- Issue #874322, The All Day functionality has been moved into a separate module and hooks were added to make it easier for other modules to inject steps into date processing.
- Issue #1267046, Include the UNTIL date in repeating results..
- Issue #1354606 by temaruk, Rework the repeating date UI to make it more user-friendly.
- Issue #1362654, Remove Advanced Help integration, it needs a total rewrite.
- Issue #1357362, Remove Date Browser, use Date Pager instead.
- Issue #1354606, Make sure COUNT option without UNTIL date can be handled correctly by repeating date computations.
Bugfixes
- Fix to new default date handling, the default date has to set a date in the database timezone, not the display timezone.
- Issue #1245106 by G<>bor Hojtsy, Hide the option to add the delta into the view for single value fields.
- Issue #1370876, Make sure new Date All Day code does not try to set the popup values if Date Popup is disabled.
- Issue #874322, Add back the date_field_all_day() function to avoid breaking other modules that are using it.
- Fix Date text placeholder to display a formatted date instead of a format. Follow up to Date repeat UI changes.
- Issue #1248520 by fearlsgroove, Use attribute selector on all day and end day checkboxes.
- Issue #952446 and #1031690, Select dates with only year and month were not working.
- Issue #1292152 by pfrenssen, Remove debug functions.
- Issue #1338976 by Josh Benner, Adjust iCal unwrap to allow for leading spaces, per standard.
- Issue #1352486 by d.novikov, Rework date SQL formatting for Microsoft SQL.
- Issue #1266536, Keep timezone adjustments from altering the values for repeating date EXCEPTIONS and ADDITIONS.
- Issue #1353488, Some repeating date calculations using PHP 5.3.6+ were not computing correctly.
- Issue #1364026, Fix link to documentation, also move help to Date API module.
- Issue #1276270, Fix fatal error when using repeat date on user.
- Issue #1362758, Add empty file for the date_plugin_display_attachment.inc file that was removed, to avoid fatal errors if it is missing on a site that formerly used it.
- Issue #1363460, Make sure widget dates do not end up with the current date when they should be empty.
- Issue #1353488, Attempt to fix problem using date_date_set() in PHP 5.3.6+.
- Date Migrate tests needed some fixes to conform to latest code.
- Issue #1353790, Hide missing index notice if the value in the Date Popup does not match the expected value, it will still get trapped as a validation error.
- Issue #1353790, Make sure Date Popup properly translates the default value if it includes month or day names.
- Issue #1217796, Make sure that All Day formatting works correctly even if the All Day label is empty.
- The repeat description was lost by a recent change to the theme that sent the element through the theme twice.
- Remove date_get_nested_elements() function. Not being used and I don't want other code to start expecting it.
- Issue #1266144, The end date should get set to blank if it is optional and matches the start date, but only if it is not populated by default values.
- Issue #1349510, Make sure default values get populated before processing so they still get set on hidden fields.
======================
Version 7.x-2.0-alpha5
======================
Notes:
The date repeat widgets have been removed to keep users from trying to change repeating dates into non-repeating dates.
There are now just three widgets, Date Select, Date Text, and Date Popup. Whether or not a date is a repeating date
is now controlled by a field setting.
New Features/UX Changes/API Changes
- Issue #1304056 by DamienMcKenna, Add option to date_difference to indicate direction of difference.
- Issue #1238660, Add custom format option for the date format used in summary arguments.
- Issue #1038482 by somanyfish, iCal import failing due to colon instead of semi-colon
- Issue #1252952 by eosrei: Make "all day" checkbox configurable on a per field instance basis.
- Issue #1266144 by arlinsandbulte: Clarify Default End Date Setting
- Issue #1261478 by stevector and KarenS, Reconfigure the back/next buttons into item lists so Views ajax pager works right.
- Issue #1262960, Add a new module to work with the Context module to set a condition based on the value of a date field.
- Issue #1216878, Re-introduce 'repeat' as a field setting rather than a widget type so people can't try to switch back and forth between repeating and non-repeating dates. Eliminate repeat widgets.
Bugfixes
- Issue #1280658 and follow up to Issue #1238660 by KarenS and tockliasteroid, Rework the format control over the summary title and apply it to the title() callback.
- Issue #1276622 by ArtistConk, Make sure Except date contains no default value.
- Issue #1153766 by fago, More work on the entity property setters and getters.
- Issue #1331214, Make sure Date Migrate properly handles empty date values.
- Issue #1285224, Make sure Date Migrate works for importing repeating dates now that widgets have changed.
- Issue #1162290, Make sure date for example formats avoids confusing short and long month names.
- Re-organize handling of custom date formats.
- Issue #1122038, Make sure empty values are not passed to Views to be themed for repeating dates.
- Issue #1101284 by pfournier, Expand regex for Month names to catch more possible variations.
- Issue #1343406 by slashrsm, add back the caching of the views fields list.
- Issue #1302374 by miro_dietiker, Account for an inconsistency in core handling of non-existant date formats.
- Issue #1344014 by James Sharpe, Get rid of 'Repeats every 0 days' description.
- Issue #1302212, Change the way default dates using custom code are created.
- Issue #1292898, Check for all day checkbox in the basic date element validation so empty time for newly added elements still passes validation.
- Issue #1252952 follow up, Move checkbox setting to the same spot where time is set, and don't show option on dates without time.
- Issue #1335818 by joelpitter, Don't create a date for empty values in the date getters and setters.
- Issue #1271726, Keep default_value out of exposed form so it won't show up in pager.
- Issue #1017866, Fix miscellaneous problems where filter/argument incorrectly do timezone adjustment.
- Issue #1103032, Attempt to add setters to date entity metadata so Rules can use them.
- Issue #1153766, Adapt metadata functions to the pattern in the current Entity code, add $info to the getters.
- Issue #1278876 by basicmagic.net, Typo in date.css
- Issue #752550 by Fonant, Week number gets printed twice
- Issue #1052586 by jpsolero, Problem with Date API when using Calendar with argument set to "Week" granularity and "current date" default argument
- Issue #1266144, End date same as start date default not being respected.
- Issue #1239934 by casey, Fix javascript exception in date_year_range.js.
- Issue #1310558, Don't show extra label on exposed date filter.
- Fix bug in Date Popup that still uses the current date when the default date is empty.
- Issue #1335578 by aaronbauman, Make it possible to pass in +/- 1000 years to years back/forward.
- Issue #1292516 by mcarbone, Fix Uninitialized string offset in Date Migrate.
- Issue #1084980 by jwilson3, Set default value for $granularity to be array in date_formatter_format.
- Issue #1227350 by grendzy, Summary view should not be calling date_forbid().
- Issue #1286570, Fix undefined index error caused by using a remember value without checking if it exists.
- Issue #1337440 by phoenix, Fix syntax error in vcalendar tpl file.
- Issue #1103032, Add plain date formatter and set it to be the default token formatter.
- Issue #1177684 by tim.plunket, Fix typo in last commit.
- Issue #1177684, Wrong translation of short month name.
- Issue #1201342 by colinlee and alexprv, Comment out SQLSVR timezone adjustment until it gets fixed properly.
- Issue #1308274 by sneyerst, SQL Server DATEPART function cannot accept composed datetime formats.
- Issue #1308266 by sneyerst, Fix arithmetic overflow in SQLSVR.
- Issue #1333104 by tim.plunkett, Check that $field['settings'] exists in date_is_repeat_field().
- Error message for years back and forward doesn't match new labels, should be Starting year and Ending year.
- Issue #1179715, Switch drupal_array_get_nested_value() to use 'values' instead of 'input', where it makes sense, and simplify some of this code.
- Issue #1179715, Create a helper function for testing hidden/disabled dates and test each date element and validator to skip processing in that case.
- Issue #1179715, When hidden by #access=FALSE, repeating date fields were getting removed and not replaced.
- Issue #1179715, By pass date repeat widget processing and validation when element is hidden from user.
- Issue #1179715, Don't do timezone adjustments in the widget, wait for #process so we can skip it when the date field has been hidden by #access.
- Issue #1338194, Logic for creating end date wasn't taking into account the possibility that a field might have no value2.
- Issue #1179715, Default value callback for the timezone widget was not returning an array.
- Issue #1179716, Remove value_callback for date_repeat and date_combo forms, the default behavior works fine.
- Issue #1178716 by das-peter, Use drupal_array_get_nested_value() in Date Repeat instead of trying to find it manually.
- Issue #1178716 by das-peter and KarenS, Tweak the date repeat widget to identify empty input when used on nodes with translation.
- Issue #1178716 by das-peter and KarenS, Fix date repeat form values that are not arrays when hidden on a node that has translation.
- Issue #1178176 by das-peter, Fix date_combo_value_callback to return NULL to avoid data lost on untranslatable dates used with Entity Translation.
- Date Context module was making incorrect assumptions about the $language of the field.
- Issue #1237974, Schema module wants the datetime column to be lower case.
- Issue #1233084, fix a few places that were wrapping $instance['label'] with t().
- Issue #1188380 by Xen and jherencia, use #title instead of field['label'] in date display because that has the i18n translation.
- Issue #1330768 by das-peter, remove whitespace.
- Tweak date_pager_url to allow a way to create non-absolute urls.
- Make sure deleted displays won't create errors in Views by returning FALSE in date_forbid() if there is no date argument.
- Issue #1257830, Beef up the logic when creating the date repeat tabs to work for more kinds of entities.
- Issue #1260962, Repeating date fields should save the date even if there is no repeat information.
- Issue #1241836 by kzoli, Fix undefined cardinality indexes.
- Issue #1233722, Fix undefined index notice in pager when other filters are used.
======================
Version 7.x-2.0-alpha4
======================
Notes to themers:
Previous versions put dates with both From and To dates into a fieldset and other dates were not.
The new code adds additional floating elements that are hard to contain, so now all dates are
enclosed in fieldsets in the node form. There are also new elements on the form, an optional
checkbox for hiding/showing the To date and an optional checkbox for hiding/showing time.
Previously dates on the node form had 'From date' and 'To date' labels above them, this
has been changed to remove those labels, using the Google calendar date entry screen
as a model. This simplifies the node form and dates take up less space. A light grey border
has been added around each collection of dates (the From date and the To date). The display
of labels above the date parts (year, month, day, date, time, etc) is controlled in the
field settings. Previous versions did not always honor those settings, this one does.
New Features/UX Improvements
- Issue #1249724 by KarenS, G<>bor Hojtsy, David_Rothstein, Improve usability of date and time input configuration.
- Issue #1250784 by David_Rothstein, Add user-friendly labels for start and end date values in Views.
- Issue #742146, Add option to remove X-WR-CALNAME if VEVENT is not a feed.
- Add option to change method from PUBLISH to REQUEST in VCALENDAR.
- Issue #334435, Add a theme function for the 'Date' and 'Time' labels in the Popup widget.
- Issue #1239956 by David_Rothstein, Change the default separation between from and to dates to use 'to' instead of a hyphen.
- Issue #1240628 by David_Rothstein, Make the date increment default to 15 instead of 1.
- Issue #1249724 by David_Rothstein: Improve usability of date and time input configuration
- Issue #1177198 by tim.plunkett: Allow CTools to process #dependency for date elements.
- Issue #1245562 by David_Rothstein, Rename the default date display format to something friendlier
- Issue #1239934 by David_Rothstein and G<>bor Hojtsy, Reuse the "years back and forward" dropdown widget on the Views filter settings page.
- Issue #1239228 by G<>bor Hojtsy, Date Views filter form UI improvements, clarify the way absolute and relative dates work.
- Issue #233047 by ksenzee and David_Rothstein, Add the Vegas jQuery timepicker as a new time selector option.
- Issue #1145976 by tim.plunkett and KarenS, Add 'is date' identifier to all date handlers.
- Issue #1234140 by arlinsundbulte, Change terminology in user-facing text from 'From/To Date' to 'Start/End Date'.
- Issue #1233948 by KarenS, Add 'All Day' checkbox to hide/show the time parts of the date form. If All day is chosen, any time is replaced with 00:00:00 when the form is submitted.
- Issue #1233612 by KarenS, Add 'Collect end date' checkbox for dates with optional end dates to hide/show the end date.
- Issue #821200 by scor and KarenS, Add RDF support to the date field.
- Issue #1211744 by EclipseGC and KarenS, Add a Date pager plugin that is designed to add paging in conjunction with a Date argument.
- Issue #1180612 by mikeryan, Add support for Migrate module.
- Issue #1198320 by ksenzee, David_Rothstein, Noyz: Make UI improvements to field settings page.
- Issue #1215738 by ksenzee, Make granularity settings checkboxes horizontal.
- Issue #1215686 by ksenzee, Change name of date field types to be more intuitive.
- Issue #1216996 by ksenzee, Change the years back/forward setting into two drop down boxes.
- Issue #1222468 by ksenzee, Hide timezone options when using granularity without time.
- Issue #1229388 by ksenzee, Hide formatter from/to choices on fields without multiple values.
Removed deprecated functions
- Removed date_handler_fields(), only applicable to D6 code.
- Removed date_views_real_url() and date_views_page_url(), used by older calendar version.
- Remove unused date_handler_field_multiple.
Tests
- Issue #1251592 by David_Rothstein, Set reasonable default values if date formats have not been configured, and add tests for that.
- Issue #1242764 Add tests for every possible combination of field type, timezone handling, and granularity.
- More work on tests, add a foreach loop to run through all field_type/widget_type combinations.
- Rework tests to use a base class rather than copying the same functions everywhere.
- Issue #1209408 Make sure date_repeat_calc() returns empty array for FREQ=NONE and INTERVAL=0, also add a test for that.
- Issue #1161006 by justinrandall, Add tests to check that dates that should have time but do not are correctly caught in validation.
Bugfixes
- Issue #1256406 by q0rban, use variable_get() in hook_requirments().
- Follow up to Issue #1145976, Make sure 'is date' only gets applied to the date values, not delta, timezone, etc.
- Issue #1238364, Make sure the Date pager doesn't throw errors if the default date is missing.
- Issue #1246416, Add info to the Date Popup README.txt about how to download the WVega timepicker.
- Issue #1253230, Package name of Date Migrate was different than the other Date components.
- Issue #1251592, Add installation message and system requirements warnings about missing system date settings.
- Issue #1250784, Don't save psuedo field settings created by the new UI in the field itself.
- Issue #1239228 by David_Rothstein, Always add Date Views css to View UI edit forms.
- Issue #1254540 by David_Rothstein, Move borders off of the wrappers so they don't appear when the dates inside them are hidden.
- Issue #1253248 by David_Rothstein, Move RDF attributes handling to preprocess function.
- Issue #1087798 by anj, Fix X-WR-CALNAME in VCALENDAR.
- Issue #1254582 Repeat additions need to be adjusted to use the same time as the original date.
- Move vcalendar and vevent templates from Date Views to Date API modules.
- Follow up to Issue #1250344, We don't need extra space when there is a description, only when there is not.
- Issue #1239228 by G<>bor Hojtsy, More tweaks to filter css.
- Issue #1244924 by G<>bor Hojtsy, Minor text improvements in date filter configuration
- Issue #1245556 by David_Rothstein, Date granularity description incorrectly implies that it affects the date attributes that are displayed
- Issue #1247444 by G<>bor Hojtsy, Give a little breathing space to the date year range "other" field
- Issue #1250344 by jessebeach, Fix padding around date fields by adding clearfix class.
- Issue #1249116 by yched, Fix various glitches with D6 migration code.
- Issue #1243022 by fmosca and KarenS, Make sure all_day #states visibility is only set when there is a value for all_day.
- Issue #1236192 by loganfsmyth, Make sure #date_label_position has a default value in the Date Popup module.
- Issue #1246416, Test whether libraries_get_path() returns a valid path before using it.
- Issue #1235994, Don't display 'All Day' when using a format that has no time.
- Issue #1245690 by mikeryan, Migration plugin missing seconds from date formats
- Issue #1229406 by David Rothstein, G<>bor Hojtsy, and tim.plunkett Fix broken timepicker in Chrome and Safari.
- Issue #1239412 by keithm, Fix validation error when #access is false.
- Issue #1232522, Don't alter field_ui_field_edit form except on date fields.
- Issue #1243842, Make sure the All Day and Show End Date flags work correctly in unlimited value fields that use ajax.
- Follow up to Issue #1239956, Fix tests broken by change in date separator.
- Issue #1241576, Fix date combo validation for all-day dates so they don't fail validation because the format is unexpected.
- Take the guesswork out of examining Date info for Views, add is_field flag.
- Follow up to Issue #874322, Date popup field needs to accept date without time for the all day flag to work.
- Follow up to Issue #1130814, It looks like the modification is not needed in date_repeat_set_month_day and date_repeat_set_year_day.
- Issue #1160132 by seanbfuller and KarenS, Exposed filter widgets were not displaying the default values.
- Adjust the way widgets are styled in Views exposed filters after the latest style changes for form elements.
- Issue #1234114 by arlinsandbulte, Add more space between checkboxes.
- Issue #1234090, Fix undefined variable 'all_day'.
- Issue #1232570 by dboulet, Remove some unneeded duplication of core clearfix and other css cleanup.
- Issue #1232614 by dboulet, Date css should not be setting font family.
- Timezone and Date Popup weren't obeying the settings for displaying the date part label.
- Issue #1130814 by cdracars, mikeryan, mayobutter, Fix date_modify difference between PHP 5.3.5 and 5.3.6 so both work to compute repeating dates.
- Issue #1017866 by KarenS and Jason89s, Make sure views argument and filter don't do timezone adjustment for dates that don't have time.
- Issue #1204988 by paulsheldrake, Remove IE6 code from datepicker CSS.
- Issue #1231864 by Damien McKenna, Clean up line endings in vevent and vcalendar tpl files.
- Issue #1231382 by dboulet, Clean up date css, code style fixes.
- Date repeat additions and exceptions need to be reworked into full, timezone-adjusted, datetime values when passing them to FAPI as default values.
- Issue #1223034 by c4rl, Make sure repeating date additions and exceptions work correctly when there is more than one repeating date in the same form.
- Issue #1229362, Date Popup module needs date_is_date() function, which should not require Date module. Move that to Date API.
- Issue #1227208 by ksenzee, Minor text changes.
- Issue #1094408, Change method of identifying Field module filters, using the name of the group is not robust enough.
- Issue #1227264 by gapa, Fix wrong class in date-pager.tpl.php.
- Issue #1207540, Summary grouping won't work right unless the formula alias doesn't match an actual field value.
- Issue #1227350 Summary query still needs the formula, add it back.
- Fix 'variables cannot be passed by reference' notice on repeats page.
- Issue #1222736 Fix export errors caused when previous export fix of using export plugins got broken by changes in Views.
- Issue #1077490 Fix notices about missing #date_flexible.
- Move the 'top' date pager to below the header instead of below attachment_before so you can add header text above it.
- The value for variable_get('date_first_day') should default to zero to match core default.
- Issue #1147620 by KarenS with an assist from tim.plunkett, Fix the query so it will locate dates that span days or months by checking the intersection of the date range and the query range. Also add an option to the argument so you can do a simple query for either the from or to date when checking the whole range isn't the right solution.
- Issue #307274 by ksenzee, Fix broken validation for absolute value in years back/forward.
- Issue #1173374 by fietserwin, Remove translation of the jQuery datepicker day and month names, now handled by core.
- Issue #1192020 by tim.plunkett: Fixed date granularity is too fragile in date_field_all_day().
- Issue #1110012 Remove 'parent' items from Views plugins, no longer needed? See if this fixes the issue.
- Issue #1183892, Initialize $identifier in date_views_filter_handler_simple.
- Issue #1103290 by stickywes and ingaro, Change postgres 'FMYYYY-FMMM-FMDDTFMHH:FMMI:FMSS' to 'FMYYYY-FMMM-FMDDTFMHH24:FMMI:FMSS'.
- Issue #1186528 by jox, Make date field combo label translatable.
- Issue #1123186 by KingJoshi, Fix misnamed date_part_hour_prefix in hook_theme().
- Issue #1032942 by fietserwin, Rename date popup functions that are getting picked up by theme('date').
- Issue #1206756 More tweaks to the validation changes. They were blocking zero times and causing some test failures.
- Issue #1197352 Don't display language about from and to dates when there is no todate.
- Issue #1201288 by rafa0961, fix broken references to SQLSERVER.
- More work on cleaning up validation, add in some ideas from fearlsgrove about checking the granularity of the input array against the expected granularity.
- Issue #1161006 Dates that should have time but do not were not correctly caught in validation.
- Get rid of overlapping formatter functionality. There should be a default formatter with the option to choose a date format as a setting, not a formatter for each format. This was a leftover from the D6 functionality.
- Issue #1159404 by mikeryan, Fix incorrect call to parse an rrule in date_repeat_build_dates().
- Issue #1160656 by jjs, Replace missing break in date_api.sql.inc that breaks PostgreSQL.
- Issue #1150454, Fix undefined index notices for repeat_collapsed value.
- Issue #1150462, Put length limit on content type names created by Date Tools so block delta won't overflow the allowed size.
- Issue #1136734, When migrating date format data from D6 to D7, don't try to overwrite existing custom values.
- Issue #1161042 by Ollie222, Date filters using time were inconsistently formatted.
- Issue #1130884, Bad logic in 'between' filter SQL, should always join with AND.
- Issue #1139418 by ankur, Bad logic in week argument SQL, should always join with AND.
- Issue #1118356, Disabling the Timepicker was having no effect.
- Issue #408770 by vkareh, Make sure dates with #access = FALSE get passed through date_combo_element_process().
- Issue #1037174 by das-peter, add ajax support to date popup.
- Issue #1160614 by joelstein, Make sure date repeat rule gets split correctly no matter which line endings were used.
- Issue #1110708 by mr.baileys, Fix problem combining date filter with other exposed filters.
- Switch some references to $node to use $entity instead.
- Issue #1112206, add a dummy field to the date navigation bar query to keep it from trying to create invalid sql.
- Issue #1112206, $this->view->query->clear_fields() is still need for date_browser to keep Views from trying to use missing field values.
- Date browser only works with date_argument, should work with any argument derived from date.
======================
Version 7.x-2.0-alpha3
======================
- Issue #1138700, missed a couple references to the construct() function.
======================
Version 7.x-2.0-alpha2
======================
- Follow up to Issue #1103290, constructor was not set up correctly and did not get triggered, so none of the date handlers had a db_type.
- Issue #1138622, preliminary pass at adding support for SQL Server.
- Issue #1136618 by ksenzee, Fix broken hide/show capability for date filter values.
- Issue #1059078 Add preliminary support for SQLite dates.
- Issue #1103290 by kevintheday, Use db_driver() to determine database engine.
- The 'now' values got broken again somewhere along the line. Now we need to switch the ISO format used by our SQL queries back to the datetime format the widgets use.
- Looks like Views changed ['expose']['operator'] to ['expose']['operator_id'].
- Issue #1115770, Make sure filters values are switched back to ISO format so time comparisons work correctly.
- Issue #1132916 by znerol, Fix a couple more usages of date_default_timezone_name().
- Issue #1131308 Don't try to do timzone conversion when there is no localzone for a field.
- Issue #1093222 Fix broken function to remove calendar views.
- Issue #820670 Add update to move D6 date format data to D7 data.
- Issue 1074344 Fix problem with date select widget that keeps resetting pm back to am.
- Issue #1001186 Make sure that a 2 digit year is flagged as an error.
- Issue #1117420 by threewestwinds, Make timepicker formats more useful.
- Issue #1126408 by thekevinday, Add more sql formats to date api.
- Issue #1129326 by robertom, add missing value to element in date_combo.
- Issue #998220 by jamsilver and yched, fix handling of validation in date_combo. This also fixes errors when using a date in Profile 2 and Field Collections.
- Issue #1022592 by andypost and tim.plunkett optimize hook_form_alter into hook_form FORMID_alter.
- Issue 1021424 by joostvdl and wiizzard, fix context in translations to use core values.
- Remove extra fields from the Date field. I think the filter and argument now get the right tables joined in without that. We still need the extra fields on calendar, but now we will only add them in the calendar view.
- Fix some notices in vcalendar theme.
- Fix the mini calendar querystring.
- Fix logic for day argument formula.
- Fix logic for week argument formula.
- Issue #1086582 by bojanz, summary options are in a different place now, fix the method of removing the summary option on multiple-date arguments.
- Views renamed 'wildcard' to 'exception'.
- Apparently the handler->argument value is not always populated.
- Fix fallback value for date_group.
- Issue #1103032 by tim.plunkett, Remove token integration code until Token module is fixed.
- Now that there is no group of 'Fields' we need different tests to tell if this is a field filter.
- Views changed the group name of fields from 'Fields' to 'Content'. Blech, broke everything.
- Fix potential error if handler is broken.
- Remove reference to a function that no longer exists in Views.
- Issue #1116962 by jpontani, mordonez: Parse Error - date_views_fields.inc on line 119.
- Fix the logic for year and month only dates.
- Add helper function to test if handler is a date handler.
- Fix broken handling when creating dates from timestamps.
- Set some defaults for dates without month or day.
- Issue ##1094408 by Boobaa: Date field not showing up in views arguments in localized site
- Issue #1018412 by omerida: get_class() called without object.
- There were some problems when creating a year-only date using the select filter caused by trying to create a date with zeros in the month and day. Fix the date building logic to force a valid month and day into them.
- Complex filters were not using the date handler of the individual fields.
- Issue #1100934, Replace deprecated date_array() function.
- #1096246 Date argument also was not applying the group method in the right place in the query, causing that method to be applied to all filters.
- Missed a couple places when re-naming the get_value() function in the filter.
- Fix more notices.
- #1096246 Date filter was not apply the group method in the right place in the query, causing that method to be applied to all filters.
- Fix undefined index notice.
- Remove non-used function.
- Issue #1082654 by arlinsandbulte: Remove master branch files and explain in readme.txt
- Issue #906622 by bfroehle, Start cleanup of Date token code.
- Fix typo in form processing.
- Clean up the admin summary for the date filter.
- Fix another use of get_value() in the filter.
- Fix some strict errors in the views filter.
- #1094408 by Boobaa: Date field not showing up in views arguments in localized site
- #1041428, by tim.plunkett: Fix logic errors in 'All Day' computations.
- #1054458: Move date_increment_round() function into Date API module so it is always available.
- #745074, by rsevero: Don't test for valid timezone-adjusted date for dates that don't use time.
- #1076992: The timezone element never got completely updated to D7.
- #1013662 by developer-x: Remove check for start and end date - this prevented multiday-all day events from showing 'All Day'
======================
Version 7.x-2.0-alpha1
======================
- #1082658: Saving the options as arrays breaks other things. Add a custom export plugin instead.
- #1082658, Views options need to be declared as arrays or they are not saved in the export in Views 3.
- #1075896 Break out the code into the simple argument and a complex argument that combines date fields.
- #1075890 Break out the code into the simple filter and a complex filter that combines date fields.
======================
Version 7.x-1.0-dev
======================
Abandoned the 7.x-1.x branch.
Start a new 7.x-2.x branch that will contain a complete re-work of the Views handling.
The 7.x-2.x branch will totally re-work and simplify the Views filters. These changes may/will
break some views and will require that you check views that use the date filter or
argument to be sure they are still configured correctly.
There is a new date_filter_handler_simple for an individual date field that simplifies
the Views handling by using most of the core Views field handling but adds in the
possibility to set a default date using options like 'now', along with a choice of a
month/day/year drop-down selector or popup calendar selector.
The date_filter_handler is an extension of the simple handler that allows you to combine
multiple dates into a single filter so that you can control all of them with the same
filter widget, allowing you to either 'OR' or 'AND' them together.
The arguments will be rewritten in the same way, with a simple argument handler for
individual dates and another handler that extends that to control multiple dates with
a single argument.
- Add some error trapping in case the parent dateObject is unable to make a date out of a string to avoid showing the user errors.
- #1027752 by B-Prod and dboulet, Fix missing table join on argument summary view.
- #1047412 by das-peter - Using date filter in February causes "The day is invalid" error.
- #1014162 Now that the datefield processing expands to date & time, the form_value is corrupted.
- Changes to the data migration code to work with latest iteration of Content Migrate.
======================
Version 7.x-1.0-alpha2
======================
- Views made lots of changes to field handling just before the new release, which broke lots of things here.
- Adding some work-arounds to get things working again and waiting until the dust settles on how the new core fields will be handled in Views before doing much more with the Views integration. These changes should get things working with latest Views release.
======================
Version 7.x-1.0-alpha1
======================
- Initial release, probably still buggy.
- Remove Date PHP4, no longer needed because PHP4 is not supported any more.
- Remove Date Timezone, now handled by core.

View File

@@ -0,0 +1,47 @@
================================================================================
Date API Installation instructions:
================================================================================
1) If you have an earlier version of the Date module on your system, empty the
date folder out completely. The files in later versions have different names
and are located in different places.
2) Download the whole package of files from http://drupal.org/project/date.
3) Upload the date files to the modules directory. The package includes files
needed by the Date API, and optional modules to to create date fields.
4) Go to admin/modules and enable the needed modules from the Date/Time group.
You should end up with a structure like:
/drupal/sites/all/modules/date/date.info
/drupal/sites/all/modules/date/date.install
/drupal/sites/all/modules/date/date.module
...
/drupal/sites/all/modules/date/date_api/date_api.info
/drupal/sites/all/modules/date/date_api/date_api.install
/drupal/sites/all/modules/date/date_api/date_api.module
...
================================================================================
Install Date Fields:
================================================================================
1) The date field type is included in the Date files at
http://drupal.org/project/date.
2) Go to admin/modules and enable the Date module. Be sure that the Date API
module, the Date Timezone module, and the core Field module are also enabled.
3) Go to admin/structure/types to view content types and edit a content type.
4) Make sure the default timezone name has been set at
admin/config/regional/settings.
5) While viewing a content type, select the option to add a new field from the
tabs at the top of the page. Several options for date fields will be visible.
================================================================================
More documentation is available at http://drupal.org/node/92460.
================================================================================

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@@ -0,0 +1,223 @@
INFORMATION FOR DEVELOPERS
Once the Date API is installed, all functions in the API are available to be
used anywhere by any module.
The API uses the PHP 5.2 date functions to create and manipulate dates.
Example, the following will create a date for the local value in one
timezone, adjust it to a different timezone, then return the offset in seconds
in the new timezone for the input date; The offset will be adjusted for both
the timezone difference and daylight savings time, if necessary:
$date = date_create('2007-03-11 02:00:00', timezone_open('America/Chicago'));
$chicago_time = date_format($date, 'Y-m-d H:i');
print 'At '. $chicago_time .' in Chicago, the timezone offset in seconds
was '. date_offset_get($date);
date_timezone_set($date, timezone_open('Europe/Berlin');
$berlin_time = date_format($date, 'Y-m-d H:i');
print 'It was '. $berlin_time .' in Berlin when it
was '. $chicago_time .' in Chicago.';
print 'At that time in Berlin, the timezone offset in seconds was
'. date_offset_get($date);
A helper class is available, new DateObject($string, $timezone, $format), where
$string is a unixtimestamp, an ISO date, or a string like YYYY-MM-DD HH:MM:SS,
$timezone is the name of the timezone this date is in, and $format is the format
of date it is (DATE_FORMAT_UNIX, DATE_FORMAT_ISO, or DATE_FORMAT_DATETIME). It
creates and return a date object set to the right date and timezone.
Simpletest tests for these functions are included in the package.
Available functions include the following (more documentation is provided in
the files):
============================================================================
Preconfigured arrays
============================================================================
Both translated and untranslated values are available. The
date_week_days_ordered() function will shift an array of week day names so it
starts with the site's first day of the week, otherwise the weekday names start
with Sunday as the first value, which is the expected order for many php and sql
functions.
date_month_names();
date_month_names_abbr();
date_month_names_untranslated();
date_week_days();
date_week_days_abbr();
date_week_days_untranslated();
date_week_days_ordered();
date_years();
date_hours();
date_minutes();
date_seconds();
date_timezone_names();
date_ampm();
============================================================================
Miscellaneous date manipulation functions
============================================================================
Pre-defined constants and functions that will handle pre-1970 and post-2038
dates in both PHP 4 and PHP 5, in any OS. Dates can be converted from one
type to another and date parts can be extracted from any date type.
DATE_DATETIME
DATE_ISO
DATE_UNIX
DATE_ARRAY
DATE_OBJECT
DATE_ICAL
date_convert()
date_is_valid();
date_part_is_valid();
date_part_extract();
============================================================================
Date calculation and navigation
============================================================================
date_difference() will find the time difference between any two days, measured
in seconds, minutes, hours, days, months, weeks, or years.
date_days_in_month();
date_days_in_year();
date_weeks_in_year();
date_last_day_of_month();
date_day_of_week();
date_day_of_week_name();
date_difference();
============================================================================
Date regex and format helpers
============================================================================
Pre-defined constants, an array of date format strings and their
equivalent regex strings.
DATE_REGEX_LOOSE is a very loose regex that will pull date parts out
of an ISO date with or without separators, using either 'T' or a space
to separate date and time, and with or without time.
date_format_date() is similar to format_date(), except it takes a
date object instead of a timestamp as the first parameter.
DATE_FORMAT_ISO
DATE_FORMAT_DATETIME
DATE_FORMAT_UNIX
DATE_FORMAT_ICAL
DATE_REGEX_ISO
DATE_REGEX_DATETIME
DATE_REGEX_LOOSE
date_format_date();
date_short_formats();
date_medium_formats();
date_long_formats();
date_format_patterns();
============================================================================
Standardized ical parser and creator
============================================================================
The iCal parser is found in date_api_ical.inc, which is not included by default.
Include that file if you want to use these functions:
Complete rewrite of ical imports to parse vevents, vlocations, valarms,
and all kinds of timezone options and repeat rules for ical imports.
The function now sticks to parsing the ical into an array that can be used
in various ways. It no longer trys to convert timezones while parsing,
instead a date_ical_date_format() function is provided that can be used to
convert from the ical timezone to whatever timezone is desired in the
results. Repeat rules are parsed into an array which other modules can
manipulate however they like to create additional events from the results.
date_ical_export();
date_ical_import();
date_ical_date_format();
============================================================================
Helpers for portable date SQL
============================================================================
The SQL functions are found in date_api_sql.inc, which is not included by
default. Include that file if you want to use these functions:
date_sql();
date_server_zone_adj();
date_sql_concat();
date_sql_pad();
============================================================================
Date forms and validators
============================================================================
Reusable, configurable, self-validating FAPI date elements are found in
date_api_elements.inc, which is not included by default. Include it
if you want to use these elements. To use them, create a form element
and set the '#type' to one of the following:
date_select
The date_select element will create a collection of form elements, with a
separate select or textfield for each date part. The whole collection will
get reformatted back into a date value of the requested type during validation.
date_text
The date_text element will create a textfield that can contain a whole
date or any part of a date as text. The user input value will be re-formatted
back into a date value of the requested type during validation.
date_timezone
The date_timezone element will create a drop-down selector to pick a
timezone name.
The custom date elements require a few other pieces of information to work
correctly, like #date_format and #date_type. See the internal documentation
for more information.
============================================================================
Date Popup Module
============================================================================
A new module is included in the package that will enable a popup jQuery
calendar date picker and timepicker in date and time fields.
It is implemented as a custom form element, so set '#type' to 'date_popup'
to use this element. See the internal documentation for more information.
============================================================================
Date Repeat API
============================================================================
An API for repeating dates is available if installed. It can be used by
other modules to create a form element that will allow users to select
repeat rules and store those selections in an iCal RRULE string, and a
calculation function that will parse the RRULE and return an array of dates
that match those rules. The API is implemented in the Date module as a
new date widget if the Date Repeat API is installed.
============================================================================
RDF Integration
============================================================================
To make RDF easier to use, the base date themes (date_display_single and
date_display_range) have been expanded so they pass attributes and
RDF mappings for the field, if any, to the theme. If RDF is installed
and no other mappings are provided, the theme adds RDF information
to mark both the Start and End dates as 'xsd:dateTime' datatypes with the
property of 'dc:date'. This occurs in the theme preprocess layer, in
particular via the functions template_preprocess_date_display_single() and
template_preprocess_date_display_range().
To mark these as events instead, you could install the schemaorg
module, which will load the schema.org vocabulary. The mark the content type
that contains events as an 'Event', using the UI exposed by that
module and set the event start date field with the 'dateStart'
property and tag other fields in the content type with the appropriate
property types. The Date module theme will wrap the start and end
date output with appropriate markup.
If the result is not quite what you need, you should be able to implement your
own theme preprocess functions, e.g. MYTHEME_preprocess_date_display_single()
or MYTHEME_preprocess_date_display_range() and alter the attributes to use the
values you want.

View File

@@ -0,0 +1,529 @@
<?php
/**
* @file
* Hooks provided by the Date module.
*/
/**
* Alter the default value for a date argument.
*
* @param object $argument
* The argument object.
* @param string $value
* The default value created by the argument handler.
*/
function hook_date_default_argument_alter(&$argument, &$value) {
$style_options = $style_options = $argument->view->display_handler->get_option('style_options');
if (!empty($style_options['track_date'])) {
$default_date = date_now();
$value = $default_date->format($argument->arg_format);
}
}
/**
* Alter the entity before formatting it.
*
* @param object $entity
* The entity object being viewed.
* @param array $variables
* The variables passed to the formatter.
* - entity: The $entity object.
* - entity_type: The $entity_type.
* - field: The $field array.
* - instance: The $instance array.
* - langcode: The $langcode.
* - items: The $items array.
* - display: The $display array.
* - dates: The processed dates array, empty at this point.
* - attributes: The attributes array, empty at this point.
* - rdf_mapping: The RDF mapping array.
* - add_rdf: If module_exists('rdf').
*/
function hook_date_formatter_pre_view_alter(&$entity, &$variables) {
if (!empty($entity->view)) {
$field = $variables['field'];
$date_id = 'date_id_' . $field['field_name'];
$date_delta = 'date_delta_' . $field['field_name'];
$date_item = $entity->view->result[$entity->view->row_index];
if (!empty($date_item->$date_id)) {
$entity->date_id = 'date.' . $date_item->$date_id . '.' . $field['field_name'] . '.' . $date_item->$date_delta . '.0';
}
}
}
/**
* Alter the dates array created by date_formatter_process().
*
* @param array $dates
* The $dates array created by the Date module.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
* - format: The string $format.
* - entity_type: The $entity_type.
* - entity: The $entity object.
* - langcode: The string $langcode.
* - item: The $item array.
* - display: The $display array.
*/
function hook_date_formatter_dates_alter(&$dates, $context) {
$field = $context['field'];
$instance = $context['instance'];
$format = $context['format'];
$entity_type = $context['entity_type'];
$entity = $context['entity'];
$date1 = $dates['value']['local']['object'];
$date2 = $dates['value2']['local']['object'];
$is_all_day = date_all_day_field($field, $instance, $date1, $date2);
$all_day1 = '';
$all_day2 = '';
if ($format != 'format_interval' && $is_all_day) {
$all_day1 = theme('date_all_day', array(
'field' => $field,
'instance' => $instance,
'which' => 'date1',
'date1' => $date1,
'date2' => $date2,
'format' => $format,
'entity_type' => $entity_type,
'entity' => $entity));
$all_day2 = theme('date_all_day', array(
'field' => $field,
'instance' => $instance,
'which' => 'date2',
'date1' => $date1,
'date2' => $date2,
'format' => $format,
'entity_type' => $entity_type,
'entity' => $entity));
$dates['value']['formatted_time'] = theme('date_all_day_label');
$dates['value2']['formatted_time'] = theme('date_all_day_label');
$dates['value']['formatted'] = $all_day1;
$dates['value2']['formatted'] = $all_day2;
}
}
/**
* Alter the date_text element before the rest of the validation is run.
*
* @param array $element
* The $element array.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $input
* The array of input values to be validated.
*/
function hook_date_text_pre_validate_alter(&$element, &$form_state, &$input) {
// Let Date module massage the format for all day values so they will pass
// validation. The All day flag, if used, actually exists on the parent
// element.
date_all_day_value($element, $form_state);
}
/**
* Alter the date_select element before the rest of the validation is run.
*
* @param array $element
* The $element array.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $input
* The array of input values to be validated.
*/
function hook_date_select_pre_validate_alter(&$element, &$form_state, &$input) {
// Let Date module massage the format for all day values so they will pass
// validation. The All day flag, if used, actually exists on the parent
// element.
date_all_day_value($element, $form_state);
}
/**
* Alter the date_popup element before the rest of the validation is run.
*
* @param array $element
* The $element array.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $input
* The array of input values to be validated.
*/
function hook_date_popup_pre_validate_alter(&$element, &$form_state, &$input) {
// Let Date module massage the format for all day values so they will pass
// validation. The All day flag, if used, actually exists on the parent
// element.
date_all_day_value($element, $form_state);
}
/**
* Alter the date_combo element before the rest of the validation is run.
*
* @param array $element
* The $element array.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
* - item: The $item array.
*
* @see date_combo_element_process()
*/
function hook_date_combo_pre_validate_alter(&$element, &$form_state, $context) {
if (!empty($context['item']['all_day'])) {
$field = $context['field'];
// If we have an all day flag on this date and the time is empty, change the
// format to match the input value so we don't get validation errors.
$element['#date_is_all_day'] = TRUE;
$element['value']['#date_format'] = date_part_format('date', $element['value']['#date_format']);
if (!empty($field['settings']['todate'])) {
$element['value2']['#date_format'] = date_part_format('date', $element['value2']['#date_format']);
}
}
}
/**
* Alter the local start date objects created by the date_combo validation.
*
* This is called before the objects are converted back to the database timezone
* and stored.
*
* @param object $date
* The $date object.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
* - item: The $item array.
* - element: The $element array.
*/
function hook_date_combo_validate_date_start_alter(&$date, &$form_state, $context) {
// If this is an 'All day' value, set the time to midnight.
if (!empty($context['element']['#date_is_all_day'])) {
$date->setTime(0, 0, 0);
}
}
/**
* Alter the local end date objects created by the date_combo validation.
*
* This is called before the objects are converted back to the database timezone
* and stored.
*
* @param object $date
* The $date object.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
* - item: The $item array.
* - element: The $element array.
*/
function hook_date_combo_validate_date_end_alter(&$date, &$form_state, $context) {
// If this is an 'All day' value, set the time to midnight.
if (!empty($context['element']['#date_is_all_day'])) {
$date->setTime(0, 0, 0);
}
}
/**
* Alter the date_text widget element.
*
* @param array $element
* An associative array containing the properties of the date_text element.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - form: Nested array of form elements that comprise the form.
*
* @see date_text_element_process()
*/
function hook_date_text_process_alter(&$element, &$form_state, $context) {
$all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : '';
if ($all_day_id != '') {
// All Day handling on text dates works only if the user leaves the time out
// of the input value. There is no element to hide or show.
}
}
/**
* Alter the date_select widget element.
*
* @param array $element
* An associative array containing the properties of the date_select element.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - form: Nested array of form elements that comprise the form.
*
* @see date_select_element_process()
*/
function hook_date_select_process_alter(&$element, &$form_state, $context) {
// Hide or show the element in reaction to the all_day status for the element.
$all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : '';
if ($all_day_id != '') {
foreach (array('hour', 'minute', 'second', 'ampm') as $field) {
if (array_key_exists($field, $element)) {
$element[$field]['#states'] = array(
'visible' => array(
'input[name="' . $all_day_id . '"]' => array('checked' => FALSE),
),
);
}
}
}
}
/**
* Alter the date_popup widget element.
*
* @param array $element
* An associative array containing the properties of the date_popup element.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - form: Nested array of form elements that comprise the form.
*
* @see date_popup_element_process()
*/
function hook_date_popup_process_alter(&$element, &$form_state, $context) {
// Hide or show the element in reaction to the all_day status for the element.
$all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : '';
if ($all_day_id != '' && array_key_exists('time', $element)) {
$element['time']['#states'] = array(
'visible' => array(
'input[name="' . $all_day_id . '"]' => array('checked' => FALSE),
),
);
}
}
/**
* Alter the date_combo element after the Date module is finished with it.
*
* @param array $element
* The $element array.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
* - form: Nested array of form elements that comprise the form.
*/
function hook_date_combo_process_alter(&$element, &$form_state, $context) {
$field = $context['field'];
$instance = $context['instance'];
$field_name = $element['#field_name'];
$delta = $element['#delta'];
// Add a date repeat form element, if needed.
// We delayed until this point so we don't bother adding it to hidden fields.
if (date_is_repeat_field($field, $instance)) {
$item = $element['#value'];
$element['rrule'] = array(
'#type' => 'date_repeat_rrule',
'#theme_wrappers' => array('date_repeat_rrule'),
'#default_value' => isset($item['rrule']) ? $item['rrule'] : '',
'#date_timezone' => $element['#date_timezone'],
'#date_format' => date_limit_format(date_input_format($element, $field, $instance), $field['settings']['granularity']),
'#date_text_parts' => (array) $instance['widget']['settings']['text_parts'],
'#date_increment' => $instance['widget']['settings']['increment'],
'#date_year_range' => $instance['widget']['settings']['year_range'],
'#date_label_position' => $instance['widget']['settings']['label_position'],
'#prev_value' => isset($item['value']) ? $item['value'] : '',
'#prev_value2' => isset($item['value2']) ? $item['value2'] : '',
'#prev_rrule' => isset($item['rrule']) ? $item['rrule'] : '',
'#date_repeat_widget' => str_replace('_repeat', '', $instance['widget']['type']),
'#date_repeat_collapsed' => $instance['widget']['settings']['repeat_collapsed'],
'#date_flexible' => 0,
'#weight' => $instance['widget']['weight'] + .4,
);
}
}
/**
* Alter the date_timezone widget element.
*
* @param array $element
* An associative array containing the properties of the date_select element.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - form: Nested array of form elements that comprise the form.
*
* @see date_timezone_element_process()
*/
function hook_date_timezone_process_alter(&$element, &$form_state, $context) {
// @todo.
}
/**
* Alter the date_year_range widget element.
*
* @param array $element
* An associative array containing the properties of the date_select element.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - form: Nested array of form elements that comprise the form.
*
* @see date_year_range_element_process()
*/
function hook_date_year_range_process_alter(&$element, &$form_state, $context) {
// @todo.
}
/**
* Alter a date field settings form.
*
* @param array $form
* Nested array of form elements that comprise the form.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
* - has_data: The value of $has_data.
*
* @see hook_field_settings_form()
*/
function hook_date_field_settings_form_alter(&$form, $context) {
$field = $context['field'];
$instance = $context['instance'];
$has_data = $context['has_data'];
$form['repeat'] = array(
'#type' => 'select',
'#title' => t('Repeating date'),
'#default_value' => $field['settings']['repeat'],
'#options' => array(0 => t('No'), 1 => t('Yes')),
'#attributes' => array('class' => array('container-inline')),
'#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."),
'#disabled' => $has_data,
);
}
/**
* Alter a date field instance settings form.
*
* @param array $form
* Nested array of form elements that comprise the form.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
*
* @see hook_field_instance_settings_form()
*/
function hook_date_field_instance_settings_form_alter(&$form, $context) {
$field = $context['field'];
$instance = $context['instance'];
$form['new_setting'] = array(
'#type' => 'textfield',
'#default_value' => '',
'#title' => t('My new setting'),
);
}
/**
* Alter a date field widget settings form.
*
* @param array $form
* Nested array of form elements that comprise the form.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
*
* @see hook_field_widget_settings_form()
*/
function hook_date_field_widget_settings_form_alter(&$form, $context) {
$field = $context['field'];
$instance = $context['instance'];
$form['new_setting'] = array(
'#type' => 'textfield',
'#default_value' => '',
'#title' => t('My new setting'),
);
}
/**
* Alter a date field formatter settings form.
*
* @param array $form
* Nested array of form elements that comprise the form.
* @param array $form_state
* A keyed array containing the current state of the form.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
* - view_mode: The formatter view mode.
*
* @see hook_field_formatter_settings_form()
*/
function hook_date_field_formatter_settings_form_alter(&$form, &$form_state, $context) {
$field = $context['field'];
$instance = $context['instance'];
$view_mode = $context['view_mode'];
$display = $instance['display'][$view_mode];
$formatter = $display['type'];
if ($formatter == 'date_default') {
$form['show_repeat_rule'] = array(
'#title' => t('Repeat rule:'),
'#type' => 'select',
'#options' => array(
'show' => t('Show repeat rule'),
'hide' => t('Hide repeat rule')),
'#default_value' => $settings['show_repeat_rule'],
'#access' => $field['settings']['repeat'],
'#weight' => 5,
);
}
}
/**
* Alter a date field formatter settings summary.
*
* @param array $summary
* An array of strings to be concatenated into a short summary of the
* formatter settings.
* @param array $context
* An associative array containing the following keys:
* - field: The $field array.
* - instance: The $instance array.
* - view_mode: The formatter view mode.
*
* @see hook_field_formatter_settings_summary()
*/
function hook_date_field_formatter_settings_summary_alter(&$summary, $context) {
$field = $context['field'];
$instance = $context['instance'];
$view_mode = $context['view_mode'];
$display = $instance['display'][$view_mode];
$formatter = $display['type'];
$settings = $display['settings'];
if (isset($settings['show_repeat_rule']) && !empty($field['settings']['repeat'])) {
if ($settings['show_repeat_rule'] == 'show') {
$summary[] = t('Show repeat rule');
}
else {
$summary[] = t('Hide repeat rule');
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* @file
* Date Devel Generate code.
*/
/**
* Implements hook_devel_generate().
*
* Included only when needed.
*/
function date_devel_generate($entity, $field, $instance, $bundle) {
$entity_field = array();
if (isset($instance['widget']['settings']['year_range'])) {
$split = explode(':', $instance['widget']['settings']['year_range']);
$back = str_replace('-', '', $split[0]);
$forward = str_replace('+', '', $split[1]);
}
else {
$back = 2;
$forward = 2;
}
// Pick a random year within the time range,
// and a random second within that year.
$year = date_format(date_now(), 'Y') - $back + mt_rand(0, ($forward + $back));
$start = new DateObject($year . '-01-01 00:00:00', date_get_timezone_db($field['settings']['tz_handling']));
$leap = date_format($start, 'L');
$max_days = $leap ? 366 : 365;
$seconds = mt_rand(0, ($max_days * 86400));
date_modify($start, "+$seconds seconds");
$increment = $instance['widget']['settings']['increment'];
date_increment_round($start, $increment);
// Modify End date by 1 hour to 3 days, shorter for repeating dates
// longer for others.
$start2 = clone($start);
$max = !empty($field['settings']['repeat']) ? 720 : 4320;
$max = 240;
date_modify($start2, '+' . mt_rand(60, $max) . ' minutes');
date_increment_round($start2, $increment);
if ($field['settings']['tz_handling'] == 'date') {
// Choose a random timezone.
// Not all keys exist, so we have to check.
$timezones = array_keys(date_timezone_names(TRUE));
$key = mt_rand(0, count($timezones) - 1);
if (!array_key_exists($key, $timezones)) {
$timezone = date_default_timezone();
}
else {
$timezone = $timezones[$key];
}
}
else {
$timezone = date_get_timezone($field['settings']['tz_handling']);
}
switch ($field['type']) {
case 'date':
$format = DATE_FORMAT_ISO;
break;
case 'datestamp':
$format = DATE_FORMAT_UNIX;
break;
case 'datetime':
$format = DATE_FORMAT_DATETIME;
break;
}
$entity_field['value'] = date_format($start, $format);
if ($field['settings']['todate']) {
$entity_field['value2'] = date_format($start2, $format);
}
date_timezone_set($start, timezone_open($timezone));
$entity_field['timezone'] = $timezone;
$entity_field['offset'] = date_offset_get($start);
date_timezone_set($start2, timezone_open($timezone));
$entity_field['offset2'] = date_offset_get($start2);
return $entity_field;
}

View File

@@ -0,0 +1,537 @@
<?php
/**
* @file
* Field hooks to implement a date field.
*/
/**
* Implements hook_field_formatter_info().
*/
function date_field_formatter_info() {
$formatters = array(
'date_default' => array(
'label' => t('Date and time'),
'field types' => array('date', 'datestamp', 'datetime'),
'settings' => array(
'format_type' => 'long',
'multiple_number' => '',
'multiple_from' => '',
'multiple_to' => '',
'fromto' => 'both',
),
),
'format_interval' => array(
'label' => t('Time ago'),
'field types' => array('date', 'datestamp', 'datetime'),
'settings' => array(
'interval' => 2,
'interval_display' => 'time ago',
),
),
'date_plain' => array(
'label' => t('Plain'),
'field types' => array('date', 'datestamp', 'datetime'),
),
);
return $formatters;
}
/**
* Implements hook_field_formatter_settings_form().
*/
function date_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$formatter = $display['type'];
module_load_include('inc', 'date', 'date_admin');
switch ($formatter) {
case 'format_interval':
$form = date_interval_formatter_settings_form($field, $instance, $view_mode, $form, $form_state);
break;
default:
$form = date_default_formatter_settings_form($field, $instance, $view_mode, $form, $form_state);
break;
}
$context = array(
'field' => $field,
'instance' => $instance,
'view_mode' => $view_mode,
);
drupal_alter('date_field_formatter_settings_form', $form, $form_state, $context);
return $form;
}
/**
* Implements hook_field_formatter_settings_summary().
*/
function date_field_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$formatter = $display['type'];
module_load_include('inc', 'date', 'date_admin');
switch ($formatter) {
case 'format_interval':
$summary = date_interval_formatter_settings_summary($field, $instance, $view_mode);
break;
default:
$summary = date_default_formatter_settings_summary($field, $instance, $view_mode);
break;
}
$context = array(
'field' => $field,
'instance' => $instance,
'view_mode' => $view_mode,
);
drupal_alter('date_field_formatter_settings_summary', $summary, $context);
return implode('<br />', $summary);
}
/**
* Implements hook_field_formatter_view().
*
* Useful values:
*
* $entity->date_id
* If set, this will show only an individual date on a field with
* multiple dates. The value should be a string that contains
* the following values, separated with periods:
* - module name of the module adding the item
* - node nid
* - field name
* - delta value of the field to be displayed
* - other information the module's custom theme might need
*
* Used by the calendar module and available for other uses.
* example: 'date:217:field_date:3:test'
*
* $entity->date_repeat_show
* If true, tells the theme to show all the computed values
* of a repeating date. If not true or not set, only the
* start date and the repeat rule will be displayed.
*/
function date_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
$element = array();
$settings = $display['settings'];
$formatter = $display['type'];
$variables = array(
'entity' => $entity,
'entity_type' => $entity_type,
'field' => $field,
'instance' => $instance,
'langcode' => $langcode,
'items' => $items,
'display' => $display,
'dates' => array(),
'attributes' => array(),
'rdf_mapping' => array(),
'add_rdf' => module_exists('rdf'),
);
// If there is an RDf mapping for this date field, pass it down to the theme.
$rdf_mapping = array();
if (!empty($entity->rdf_mapping) && function_exists('rdf_rdfa_attributes')) {
if (!empty($entity->rdf_mapping[$field['field_name']])) {
$variables['rdf_mapping'] = $rdf_mapping = $entity->rdf_mapping[$field['field_name']];
}
}
// Give other modules a chance to prepare the entity before formatting it.
drupal_alter('date_formatter_pre_view', $entity, $variables);
// See if we are only supposed to display a selected
// item from multiple value date fields.
$selected_deltas = array();
if (!empty($entity->date_id)) {
foreach ((array) $entity->date_id as $key => $id) {
list($module, $nid, $field_name, $selected_delta, $other) = explode('.', $id . '.');
if ($field_name == $field['field_name']) {
$selected_deltas[] = $selected_delta;
}
}
}
switch ($display['type']) {
case 'date_plain':
foreach ($items as $delta => $item) {
if (!empty($entity->date_id) && !in_array($delta, $selected_deltas)) {
continue;
}
else {
if (empty($item['value2']) || $item['value'] == $item['value2']) {
$element[$delta] = array('#markup' => $item['value']);
}
else {
$element[$delta] = array('#markup' => t('!start-date to !end-date', array('!start-date' => $item['value'], '!end-date' => $item['value2'])));
}
}
}
break;
case 'format_interval':
foreach ($items as $delta => $item) {
if (!empty($entity->date_id) && !in_array($delta, $selected_deltas)) {
continue;
}
else {
$variables['delta'] = $delta;
$variables['item'] = $item;
$variables['dates'] = date_formatter_process($formatter, $entity_type, $entity, $field, $instance, $langcode, $item, $display);
$variables['attributes'] = !empty($rdf_mapping) ? rdf_rdfa_attributes($rdf_mapping, $item['value']) : array();
$element[$delta] = array('#markup' => theme('date_display_interval', $variables));
}
}
break;
default:
foreach ($items as $delta => $item) {
if (!empty($entity->date_id) && !in_array($delta, $selected_deltas)) {
continue;
}
else {
$variables['delta'] = $delta;
$variables['item'] = $item;
$variables['dates'] = date_formatter_process($formatter, $entity_type, $entity, $field, $instance, $langcode, $item, $display);
$variables['attributes'] = !empty($rdf_mapping) ? rdf_rdfa_attributes($rdf_mapping, $item['value']) : array();
$output = theme('date_display_combination', $variables);
if (!empty($output)) {
$element[$delta] = array('#markup' => $output);
}
}
}
break;
}
return $element;
}
/**
* Implements hook_field_is_empty().
*/
function date_field_is_empty($item, $field) {
// Sometimes a $item is a date object.
// Coming from repeating dates. Why??
if (!is_array($item)) {
return FALSE;
}
if (empty($item['value'])) {
return TRUE;
}
elseif ($field['settings']['todate'] == 'required' && empty($item['value2'])) {
return TRUE;
}
return FALSE;
}
/**
* Implements hook_field_info().
*/
function date_field_info() {
$settings = array(
'settings' => array(
'todate' => '',
'granularity' => drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute')),
'tz_handling' => 'site',
'timezone_db' => 'UTC',
),
'instance_settings' => array(
'default_value' => 'now',
'default_value_code' => '',
'default_value2' => 'same',
'default_value_code2' => '',
),
// Integrate with the Entity Metadata module.
'property_type' => 'date',
'property_callbacks' => array('date_entity_metadata_property_info_alter'),
);
return array(
'datetime' => array(
'label' => 'Date',
'description' => t('Store a date in the database as a datetime field, recommended for complete dates and times that may need timezone conversion.'),
'default_widget' => 'date_select',
'default_formatter' => 'date_default',
'default_token_formatter' => 'date_plain',
) + $settings,
'date' => array(
'label' => 'Date (ISO format)',
'description' => t('Store a date in the database as an ISO date, recommended for historical or partial dates.'),
'default_widget' => 'date_select',
'default_formatter' => 'date_default',
'default_token_formatter' => 'date_plain',
) + $settings,
'datestamp' => array(
'label' => 'Date (Unix timestamp)',
'description' => t('Store a date in the database as a timestamp, deprecated format to support legacy data.'),
'default_widget' => 'date_select',
'default_formatter' => 'date_default',
'default_token_formatter' => 'date_plain',
) + $settings,
);
}
/**
* Implements hook_field_widget_info().
*/
function date_field_widget_info() {
$settings = array(
'settings' => array(
'input_format' => date_default_format('date_select'),
'input_format_custom' => '',
'increment' => 15,
'text_parts' => array(),
'year_range' => '-3:+3',
'label_position' => 'above',
),
'behaviors' => array(
'multiple values' => FIELD_BEHAVIOR_DEFAULT,
'default value' => FIELD_BEHAVIOR_NONE,
),
);
$info = array(
'date_select' => array(
'label' => t('Select list'),
'field types' => array('date', 'datestamp', 'datetime'),
) + $settings,
'date_text' => array(
'label' => t('Text field'),
'field types' => array('date', 'datestamp', 'datetime'),
) + $settings,
);
if (module_exists('date_popup')) {
$info['date_popup'] = array(
'label' => t('Pop-up calendar'),
'field types' => array('date', 'datestamp', 'datetime'),
) + $settings;
}
// The date text widget should use an increment of 1.
$info['date_text']['increment'] = 1;
return $info;
}
/**
* Implements hook_field_load().
*/
function date_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) {
$timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
$db_format = date_type_format($field['type']);
$process = date_process_values($field);
foreach ($entities as $id => $entity) {
foreach ($items[$id] as $delta => &$item) {
// If the file does not exist, mark the entire item as empty.
if (is_array($item)) {
$timezone = isset($item['timezone']) ? $item['timezone'] : '';
$item['timezone'] = date_get_timezone($field['settings']['tz_handling'], $timezone);
$item['timezone_db'] = $timezone_db;
$item['date_type'] = $field['type'];
if (!empty($field['settings']['cache_enabled']) && ($delta < $field['settings']['cache_count'] || $field['settings']['cache_count'] == 0)) {
foreach ($process as $processed) {
if (!empty($item[$processed])) {
$date = new DateObject($item[$processed], $item['timezone_db'], $db_format);
$date->limitGranularity($field['settings']['granularity']);
$item['db'][$processed] = $date;
}
}
if (!empty($item['db']['value']) && empty($item['db']['value2'])) {
$item['db']['value2'] = $item['db']['value'];
}
}
}
}
}
}
/**
* Implements hook_field_validate().
*/
function date_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
$field_name = $field['field_name'];
$flexible = 0;
// Don't try to validate if there were any errors before this point
// since the element won't have been munged back into a date.
if (!form_get_errors()) {
foreach ($items as $delta => $item) {
if (is_array($item) && isset($item['value'])) {
$process = date_process_values($field, $instance);
$date1 = new DateObject($item['value'], $item['timezone'], date_type_format($field['type']));
if (count($process) == 1 || (empty($item['value2']) && $item['value2'] !== 0)) {
$date2 = clone($date1);
}
else {
$date2 = new DateObject($item['value2'], $item['timezone'], date_type_format($field['type']));
}
$valid1 = $date1->validGranularity($field['settings']['granularity'], $flexible);
$valid2 = $date2->validGranularity($field['settings']['granularity'], $flexible);
foreach ($process as $processed) {
if ($processed == 'value' && $field['settings']['todate'] && !$valid1 && $valid2) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'value',
'message' => t("A 'Start date' date is required for field %field #%delta.", array('%delta' => $field['cardinality'] ? intval($delta + 1) : '', '%field' => $instance['label'])),
);
}
if ($processed == 'value2' && $field['settings']['todate'] == 'required' && ($instance['required'] && $valid1 && !$valid2)) {
$errors[$field['field_name']][$langcode][$delta][] = array(
'error' => 'value2',
'message' => t("An 'End date' is required for field %field #%delta.", array('%delta' => $field['cardinality'] ? intval($delta + 1) : '', '%field' => $instance['label'])),
);
}
}
}
}
}
}
/**
* Implements hook_field_insert().
*/
function date_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
$field_name = $field['field_name'];
if (empty($items)) {
return;
}
// Add some information needed to interpret token values.
$values = $items;
foreach ($values as $delta => $item) {
$timezone = isset($item['timezone']) ? $item['timezone'] : '';
if (is_array($item)) {
$items[$delta]['timezone'] = date_get_timezone($field['settings']['tz_handling'], $timezone);
$items[$delta]['timezone_db'] = date_get_timezone_db($field['settings']['tz_handling']);
$items[$delta]['date_type'] = $field['type'];
}
}
$entity->{$field['field_name']}[$langcode] = $items;
}
/**
* Implements hook_field_insert().
*/
function date_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) {
$context = array(
'entity_type' => $entity_type,
'entity' => $entity,
'field' => $field,
'instance' => $instance,
'langcode' => $langcode,
);
drupal_alter('date_field_insert', $items, $context);
}
/**
* Implements hook_field_update().
*/
function date_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) {
$context = array(
'entity_type' => $entity_type,
'entity' => $entity,
'field' => $field,
'instance' => $instance,
'langcode' => $langcode,
);
drupal_alter('date_field_update', $items, $context);
}
/**
* Implements hook_field_instance_settings_form().
*
* Wrapper functions for date administration, included only when processing
* field settings.
*/
function date_field_instance_settings_form($field, $instance) {
module_load_include('inc', 'date', 'date_admin');
return _date_field_instance_settings_form($field, $instance);
}
/**
* Implements hook_field_widget_settings_form().
*/
function date_field_widget_settings_form($field, $instance) {
module_load_include('inc', 'date', 'date_admin');
return _date_field_widget_settings_form($field, $instance);
}
/**
* Implements hook_field_settings_form().
*/
function date_field_settings_form($field, $instance, $has_data) {
module_load_include('inc', 'date', 'date_admin');
return _date_field_settings_form($field, $instance, $has_data);
}
/**
* Implements hook_content_migrate_field_alter().
*
* Use this to tweak the conversion of field settings from the D6 style to the
* D7 style for specific situations not handled by basic conversion, as when
* field types or settings are changed.
*
* $field_value['widget_type'] is available to see what widget type was
* originally used.
*/
function date_content_migrate_field_alter(&$field_value, $instance_value) {
switch ($field_value['module']) {
case 'date':
// Those settings do not exist anymore, or have been moved to the instance
// level.
unset($field_value['settings']['default_format']);
unset($field_value['settings']['repeat_collapsed']);
break;
}
}
/**
* Implements hook_content_migrate_instance_alter().
*
* Use this to tweak the conversion of instance or widget settings from the D6
* style to the D7 style for specific situations not handled by basic
* conversion, as when formatter or widget names or settings are changed.
*/
function date_content_migrate_instance_alter(&$instance_value, $field_value) {
switch ($instance_value['widget']['module']) {
case 'date':
// Some settings have been moved from field to instance.
$instance_value['widget']['settings']['repeat_collapsed'] = $field_value['settings']['repeat_collapsed'];
// Some settings were moved from widget settings to instance settings.
$instance_value['settings']['default_value'] = $instance_value['default_value'];
unset($instance_value['default_value']);
$instance_value['settings']['default_value_code'] = $instance_value['widget']['settings']['default_value_code'];
unset($instance_value['widget']['settings']['default_value_code']);
$instance_value['settings']['default_value2'] = $instance_value['widget']['settings']['default_value2'];
unset($instance_value['widget']['settings']['default_value2']);
$instance_value['settings']['default_value_code2'] = $instance_value['widget']['settings']['default_value_code2'];
unset($instance_value['widget']['settings']['default_value_code2']);
// We need to retrieve formatter settings from the variables and store
// them in the instance.
foreach ($instance_value['display'] as $context => &$display) {
if ($display['type'] != 'format_interval') {
$old_settings = date_old_formatter_get_settings($instance_value['field_name'], $instance_value['bundle'], $context);
$display['settings'] = array_merge($display['settings'], $old_settings);
// If the formatter was the 'default', then use the old
// 'default_format' field property.
$format = ($display['type'] == 'default') ? $field_value['settings']['default_format'] : $display['type'];
$display['settings']['format_type'] = $format;
$display['type'] = 'date_default';
}
}
break;
}
}
/**
* Constructs an array of old formatter settings.
*/
function date_old_formatter_get_settings($field_name, $type_name, $context) {
$options = array();
$value = 'date:' . $type_name . ':' . $context . ':' . $field_name;
$options['show_repeat_rule'] = variable_get($value . '_show_repeat_rule', 'show');
$options['multiple_number'] = variable_get($value . '_multiple_number', '');
$options['multiple_from'] = variable_get($value . '_multiple_from', '');
$options['multiple_to'] = variable_get($value . '_multiple_to', '');
$options['fromto'] = variable_get($value . '_fromto', 'both');
return $options;
}

View File

@@ -0,0 +1,18 @@
name = Date
description = Makes date/time fields available.
dependencies[] = date_api
package = Date/Time
core = 7.x
php = 5.2
files[] = tests/date_api.test
files[] = tests/date.test
files[] = tests/date_field.test
files[] = tests/date_validation.test
files[] = tests/date_timezone.test
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,194 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Date module.
*/
/**
* Implements hook_field_schema().
*/
function date_field_schema($field) {
$db_columns = array();
switch ($field['type']) {
case 'datestamp':
$db_columns['value'] = array(
'type' => 'int',
'not null' => FALSE,
'sortable' => TRUE,
'views' => TRUE,
);
break;
case 'datetime':
$db_columns['value'] = array(
'type' => 'datetime',
'mysql_type' => 'datetime',
'pgsql_type' => 'timestamp without time zone',
'sqlite_type' => 'varchar',
'sqlsrv_type' => 'smalldatetime',
'not null' => FALSE,
'sortable' => TRUE,
'views' => TRUE,
);
break;
default:
$db_columns['value'] = array(
'type' => 'varchar',
'length' => 20,
'not null' => FALSE,
'sortable' => TRUE,
'views' => TRUE,
);
break;
}
// If a second date is needed for 'End date', make a copy of the first one.
if (!empty($field['settings']['todate'])) {
$db_columns['value2'] = $db_columns['value'];
// We don't want Field API to create additional columns, just the first.
// We modify them our own way in views data.
$db_columns['value2']['views'] = FALSE;
}
// Timezone and offset columns are used only if date-specific dates are used.
if (isset($field['settings']['tz_handling']) && $field['settings']['tz_handling'] == 'date') {
$db_columns['timezone'] = array(
'type' => 'varchar',
'length' => 50,
'not null' => FALSE,
'sortable' => TRUE,
'views' => FALSE,
);
$db_columns['offset'] = array(
'type' => 'int',
'not null' => FALSE,
'sortable' => TRUE,
'views' => FALSE,
);
if (!empty($field['settings']['todate'])) {
$db_columns['offset2'] = array('type' => 'int', 'not null' => FALSE, 'sortable' => TRUE, 'views' => FALSE);
}
}
if (isset($field['settings']['repeat']) && $field['settings']['repeat'] == 1) {
$db_columns['rrule'] = array(
'type' => 'text',
'not null' => FALSE,
'sortable' => FALSE,
'views' => FALSE,
);
}
return array('columns' => $db_columns);
}
/**
* Implements hook_update_last_removed().
*/
function date_update_last_removed() {
return 6005;
}
/**
* Get rid of the individual formatters for each format type,
* these are now settings in the default formatter.
*/
function date_update_7000() {
$instances = field_info_instances();
foreach ($instances as $entity_type => $entities) {
foreach ($entities as $bundle => $fields) {
foreach ($fields as $field_name => $instance) {
if (in_array($instance['widget']['type'], array('date_popup'))) {
$changed = FALSE;
foreach ($instance['display'] as $context => $display) {
if ($display['type'] != 'date_default' && $display['type'] != 'date_interval' && $display['type'] != 'hidden') {
$instance['display'][$context]['type'] = 'date_default';
$instance['display'][$context]['settings']['format_type'] = str_replace('date_', '', $display['type']);
$changed = TRUE;
}
}
if ($changed) {
field_update_instance($instance);
}
}
}
}
}
}
/**
* Get rid of the separate widgets for repeating dates. The code now handles
* repeating dates correctly using the regular widgets.
*/
function date_update_7001() {
$query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC));
$query->join('field_config', 'fc', 'fc.id = fci.field_id');
$query->fields('fci');
$query->condition(db_or()->condition('fc.type', 'date')->condition('fc.type', 'datestamp')->condition('fc.type', 'datetime'));
$results = $query->execute();
foreach ($results as $record) {
$instance = unserialize($record['data']);
if (in_array($instance['widget']['type'], array('date_popup_repeat', 'date_text_repeat', 'date_select_repeat'))) {
$instance['widget']['type'] = str_replace('_repeat', '', $instance['widget']['type']);
db_update('field_config_instance')
->fields(array(
'data' => serialize($instance),
))
->condition('field_name', $record['field_name'])
->condition('entity_type', $record['entity_type'])
->condition('bundle', $record['bundle'])
->execute();
}
}
field_cache_clear();
drupal_set_message(t('The widgets for repeating dates have changed. Please check the Display Fields page for each content type that has repeating date fields and confirm that the right widget has been selected.'), 'warning');
}
/**
* Add a notification about the new Date All Day module, and enable it.
*/
function date_update_7002() {
drupal_set_message(t("The <em>All Day</em> functionality has been moved into a separate module. This new module provides the option to add an <em>All Day</em> checkbox to toggle time on and off for date fields. It also contains the theme that displays the <em>All Day</em> text on fields that have no time. For consistency with prior code, it has been automatically enabled. If you don't want the <em>All Day</em> functionality you can disable this module."));
module_enable(array('date_all_day'));
}
/**
* Adds a notification about the new Date Repeat Field module, and enable it.
*/
function date_update_7003() {
drupal_set_message(t("The <em>Date Repeat</em> integration for Date fields is being moved into a separate module. For consistency with prior code, it has been automatically enabled if the Date Repeat API module is enabled. If you don't use <em>Date Repeat</em> functionality in your fields, you can disable this module."));
if (module_exists('date_repeat')) {
module_enable(array('date_repeat_field'));
}
}
/**
* Date text widgets should always use an increment of 1.
*/
function date_update_7004() {
// Select date fields.
$query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC));
$query->join('field_config', 'fc', 'fc.id = fci.field_id');
$query->fields('fci');
$query->condition(db_or()->condition('fc.type', 'date')->condition('fc.type', 'datestamp')->condition('fc.type', 'datetime'));
$results = $query->execute();
// Find the ones that use the date_text widget.
foreach ($results as $record) {
$instance = unserialize($record['data']);
if (in_array($instance['widget']['type'], array('date_text'))) {
$instance['widget']['settings']['increment'] = 1;
db_update('field_config_instance')
->fields(array(
'data' => serialize($instance),
))
->condition('field_name', $record['field_name'])
->condition('entity_type', $record['entity_type'])
->condition('bundle', $record['bundle'])
->execute();
}
}
field_cache_clear();
drupal_set_message(t('Date text widgets have been updated to use an increment of 1.'));
}

View File

@@ -0,0 +1,159 @@
(function ($) {
Drupal.behaviors.dateSelect = {};
Drupal.behaviors.dateSelect.attach = function (context, settings) {
var $widget = $('.form-type-date-select').parents('fieldset').once('date');
var i;
for (i = 0; i < $widget.length; i++) {
new Drupal.date.EndDateHandler($widget[i]);
}
};
Drupal.date = Drupal.date || {};
/**
* Constructor for the EndDateHandler object.
*
* The EndDateHandler is responsible for synchronizing a date select widget's
* end date with its start date. This behavior lasts until the user
* interacts with the end date widget.
*
* @param widget
* The fieldset DOM element containing the from and to dates.
*/
Drupal.date.EndDateHandler = function (widget) {
this.$widget = $(widget);
this.$start = this.$widget.find('.form-type-date-select[class$=value]');
this.$end = this.$widget.find('.form-type-date-select[class$=value2]');
if (this.$end.length == 0) {
return;
}
this.initializeSelects();
// Only act on date fields where the end date is completely blank or already
// the same as the start date. Otherwise, we do not want to override whatever
// the default value was.
if (this.endDateIsBlank() || this.endDateIsSame()) {
this.bindClickHandlers();
// Start out with identical start and end dates.
this.syncEndDate();
}
};
/**
* Store all the select dropdowns in an array on the object, for later use.
*/
Drupal.date.EndDateHandler.prototype.initializeSelects = function () {
var $starts = this.$start.find('select');
var $end, $start, endId, i, id;
this.selects = {};
for (i = 0; i < $starts.length; i++) {
$start = $($starts[i]);
id = $start.attr('id');
endId = id.replace('-value-', '-value2-');
$end = $('#' + endId);
this.selects[id] = {
'id': id,
'start': $start,
'end': $end
};
}
};
/**
* Returns true if all dropdowns in the end date widget are blank.
*/
Drupal.date.EndDateHandler.prototype.endDateIsBlank = function () {
var id;
for (id in this.selects) {
if (this.selects.hasOwnProperty(id)) {
if (this.selects[id].end.val() != '') {
return false;
}
}
}
return true;
};
/**
* Returns true if the end date widget has the same value as the start date.
*/
Drupal.date.EndDateHandler.prototype.endDateIsSame = function () {
var id;
for (id in this.selects) {
if (this.selects.hasOwnProperty(id)) {
if (this.selects[id].end.val() != this.selects[id].start.val()) {
return false;
}
}
}
return true;
};
/**
* Add a click handler to each of the start date's select dropdowns.
*/
Drupal.date.EndDateHandler.prototype.bindClickHandlers = function () {
var id;
for (id in this.selects) {
if (this.selects.hasOwnProperty(id)) {
this.selects[id].start.bind('click.endDateHandler', this.startClickHandler.bind(this));
this.selects[id].end.bind('focus', this.endFocusHandler.bind(this));
}
}
};
/**
* Click event handler for each of the start date's select dropdowns.
*/
Drupal.date.EndDateHandler.prototype.startClickHandler = function (event) {
this.syncEndDate();
};
/**
* Focus event handler for each of the end date's select dropdowns.
*/
Drupal.date.EndDateHandler.prototype.endFocusHandler = function (event) {
var id;
for (id in this.selects) {
if (this.selects.hasOwnProperty(id)) {
this.selects[id].start.unbind('click.endDateHandler');
}
}
$(event.target).unbind('focus', this.endFocusHandler);
};
Drupal.date.EndDateHandler.prototype.syncEndDate = function () {
var id;
for (id in this.selects) {
if (this.selects.hasOwnProperty(id)) {
this.selects[id].end.val(this.selects[id].start.val());
}
}
};
}(jQuery));
/**
* Function.prototype.bind polyfill for older browsers.
* https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
*/
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") // closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be fBound is not callable");
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP ? this : oThis || window, aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}

View File

@@ -0,0 +1,743 @@
<?php
/**
* @file
* Defines date/time field types.
*/
module_load_include('theme', 'date', 'date');
module_load_include('inc', 'date', 'date.field');
module_load_include('inc', 'date', 'date_elements');
/**
* Helper function to figure out the bundle name for an entity.
*/
function date_get_entity_bundle($entity_type, $entity) {
switch ($entity_type) {
case 'field_collection_item':
$bundle = $entity->field_name;
break;
default:
$bundle = field_extract_bundle($entity_type, $entity);
break;
}
// If there is no bundle name, field_info() uses the entity name as the bundle
// name in its arrays.
if (empty($bundle)) {
$bundle = $entity_type;
}
return $bundle;
}
/**
* Gets the default date format for the given field widget.
*/
function date_default_format($type) {
// Example input formats must show all possible date parts, so add seconds.
$default_format = str_replace('i', 'i:s', variable_get('date_format_short', 'm/d/Y - H:i'));
return $default_format;
}
/**
* Wrapper function around each of the widget types for creating a date object.
*/
function date_input_date($field, $instance, $element, $input) {
switch ($instance['widget']['type']) {
case 'date_text':
$function = 'date_text_input_date';
break;
case 'date_popup':
$function = 'date_popup_input_date';
break;
default:
$function = 'date_select_input_date';
}
return $function($element, $input);
}
/**
* Implements hook_theme().
*/
function date_theme() {
$path = drupal_get_path('module', 'date');
module_load_include('theme', 'date', 'date');
$base = array(
'file' => 'date.theme',
'path' => "$path",
);
$themes = array(
'date_combo' => $base + array('render element' => 'element'),
'date_text_parts' => $base + array('render element' => 'element'),
'date' => $base + array('render element' => 'element'),
'date_display_single' => $base + array(
'variables' => array(
'date' => NULL,
'timezone' => NULL,
'dates' => NULL,
'attributes' => array(),
'rdf_mapping' => NULL,
'add_rdf' => NULL,
),
),
'date_display_range' => $base + array(
'variables' => array(
'date1' => NULL,
'date2' => NULL,
'timezone' => NULL,
'dates' => NULL,
// HTML attributes that will be applied to both the start and end dates
// (unless overridden).
'attributes' => array(),
// HTML attributes that will be applied to the start date only.
'attributes_start' => array(),
// HTML attributes that will be applied to the end date only.
'attributes_end' => array(),
'rdf_mapping' => NULL,
'add_rdf' => NULL,
)),
'date_display_combination' => $base + array(
'variables' => array(
'entity_type' => NULL,
'entity' => NULL,
'field' => NULL,
'instance' => NULL,
'langcode' => NULL,
'item' => NULL,
'delta' => NULL,
'display' => NULL,
'dates' => NULL,
'attributes' => array(),
'rdf_mapping' => NULL,
'add_rdf' => NULL,
),
),
'date_display_interval' => $base + array(
'variables' => array(
'entity_type' => NULL,
'entity' => NULL,
'field' => NULL,
'instance' => NULL,
'langcode' => NULL,
'item' => NULL,
'delta' => NULL,
'display' => NULL,
'dates' => NULL,
'attributes' => array(),
'rdf_mapping' => NULL,
'add_rdf' => NULL,
),
),
);
return $themes;
}
/**
* Implements hook_element_info().
*
* date_combo will create a 'start' and optional 'end' date, along with
* an optional 'timezone' column for date-specific timezones. Each
* 'start' and 'end' date will be constructed from date_select or date_text.
*/
function date_element_info() {
$type = array();
$type['date_combo'] = array(
'#input' => TRUE,
'#delta' => 0,
'#columns' => array('value', 'value2', 'timezone', 'offset', 'offset2'),
'#process' => array('date_combo_element_process'),
'#element_validate' => array('date_combo_validate'),
'#theme_wrappers' => array('date_combo'),
);
if (module_exists('ctools')) {
$type['date_combo']['#pre_render'] = array('ctools_dependent_pre_render');
}
return $type;
}
/**
* Helper function for creating formatted date arrays from a formatter.
*
* Use the Date API to get an object representation of a date field.
*
* @param string $formatter
* The date formatter.
* @param string $entity_type
* The entity_type for the instance
* @param object $entity
* The entity object.
* @param array $field
* The field info array.
* @param array $instance
* The field instance array.
* @param string $langcode
* The language code used by this field.
* @param array $item
* An entity field item, like $entity->myfield[0].
* @param array $display
* The instance display settings.
*
* @return array
* An array that holds the Start and End date objects.
* Each date object looks like:
* date [value] => array (
* [db] => array ( // the value stored in the database
* [object] => the datetime object
* [datetime] => 2007-02-15 20:00:00
* )
* [local] => array ( // the local representation of that value
* [object] => the datetime object
* [datetime] => 2007-02-15 14:00:00
* [timezone] => US/Central
* [offset] => -21600
* )
* )
*/
function date_formatter_process($formatter, $entity_type, $entity, $field, $instance, $langcode, $item, $display) {
$dates = array();
$timezone = date_default_timezone();
if (empty($timezone)) {
return $dates;
}
$granularity = date_granularity($field);
$settings = $display['settings'];
$field_name = $field['field_name'];
$format = date_formatter_format($formatter, $settings, $granularity, $langcode);
$timezone = isset($item['timezone']) ? $item['timezone'] : '';
$timezone = date_get_timezone($field['settings']['tz_handling'], $timezone);
$timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
$db_format = date_type_format($field['type']);
$process = date_process_values($field);
foreach ($process as $processed) {
if (empty($item[$processed])) {
$dates[$processed] = NULL;
}
else {
// Create a date object with a GMT timezone from the database value.
$dates[$processed] = array();
// Check to see if this date was already created by date_field_load().
if (isset($item['db'][$processed])) {
$date = $item['db'][$processed];
}
else {
$date = new DateObject($item[$processed], $timezone_db, $db_format);
$date->limitGranularity($field['settings']['granularity']);
}
$dates[$processed]['db']['object'] = $date;
$dates[$processed]['db']['datetime'] = date_format($date, DATE_FORMAT_DATETIME);
date_timezone_set($date, timezone_open($timezone));
$dates[$processed]['local']['object'] = $date;
$dates[$processed]['local']['datetime'] = date_format($date, DATE_FORMAT_DATETIME);
$dates[$processed]['local']['timezone'] = $timezone;
$dates[$processed]['local']['offset'] = date_offset_get($date);
// Format the date, special casing the 'interval' format which doesn't
// need to be processed.
$dates[$processed]['formatted'] = '';
$dates[$processed]['formatted_iso'] = date_format_date($date, 'custom', 'c');
if (is_object($date)) {
if ($format == 'format_interval') {
$dates[$processed]['interval'] = date_format_interval($date);
}
elseif ($format == 'format_calendar_day') {
$dates[$processed]['calendar_day'] = date_format_calendar_day($date);
}
elseif ($format == 'U') {
$dates[$processed]['formatted'] = date_format_date($date, 'custom', $format);
$dates[$processed]['formatted_date'] = date_format_date($date, 'custom', $format);
$dates[$processed]['formatted_time'] = '';
$dates[$processed]['formatted_timezone'] = '';
}
elseif (!empty($format)) {
$dates[$processed]['formatted'] = date_format_date($date, 'custom', $format);
$dates[$processed]['formatted_date'] = date_format_date($date, 'custom', date_limit_format($format, array('year', 'month', 'day')));
$dates[$processed]['formatted_time'] = date_format_date($date, 'custom', date_limit_format($format, array('hour', 'minute', 'second')));
$dates[$processed]['formatted_timezone'] = date_format_date($date, 'custom', date_limit_format($format, array('timezone')));
}
}
}
}
if (empty($dates['value2'])) {
$dates['value2'] = $dates['value'];
}
// Allow other modules to alter the date values.
$context = array(
'field' => $field,
'instance' => $instance,
'format' => $format,
'entity_type' => $entity_type,
'entity' => $entity,
'langcode' => $langcode,
'item' => $item,
'display' => $display,
);
drupal_alter('date_formatter_dates', $dates, $context);
$dates['format'] = $format;
return $dates;
}
/**
* Retrieves the granularity for a field.
*
* $field['settings']['granularity'] will contain an array like
* ('hour' => 'hour', 'month' => 0) where the values turned on return their own
* names and the values turned off return a zero need to reconfigure this into
* simple array of the turned on values
*
* @param array $field
* The field array.
*/
function date_granularity($field) {
if (!is_array($field) || !is_array($field['settings']['granularity'])) {
$field['settings']['granularity'] = drupal_map_assoc(array('year', 'month', 'day'));
}
return array_values(array_filter($field['settings']['granularity']));
}
/**
* Helper function to create an array of the date values in a
* field that need to be processed.
*/
function date_process_values($field) {
return $field['settings']['todate'] ? array('value', 'value2') : array('value');
}
/**
* Implements hook_form_FORM_ID_alter() for field_ui_field_edit_form().
*/
function date_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
$field = $form['#field'];
$instance = $form['#instance'];
if (!in_array($field['type'], array('date', 'datetime', 'datestamp'))) {
return;
}
// Reorganize the instance settings and widget settings sections into a more
// intuitive combined fieldset.
$form['instance']['defaults'] = array(
'#type' => 'fieldset',
'#title' => t('More settings and values'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['instance']['date_format'] = array(
'#type' => 'fieldset',
'#title' => t('Date entry'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['instance']['default_values'] = array(
'#type' => 'fieldset',
'#title' => t('Default values'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['instance']['years_back_and_forward'] = array(
'#type' => 'fieldset',
'#title' => t('Starting and ending year'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['instance']['#pre_render'][] = 'date_field_ui_field_edit_form_pre_render';
}
/**
* Rearrange form elements into fieldsets for presentation only.
*/
function date_field_ui_field_edit_form_pre_render($form) {
foreach ($form as $name => $element) {
if (is_array($element) && isset($element['#fieldset'])) {
$fieldset = $element['#fieldset'];
$form[$fieldset][$name] = $element;
unset($form[$name]);
}
}
foreach (array('date_format', 'default_values', 'years_back_and_forward') as $name) {
if (element_children($form[$name])) {
// Force the items in the fieldset to be resorted now that the instance
// and widget settings are combined.
$form[$name]['#sorted'] = FALSE;
$form['defaults'][$name] = $form[$name];
}
unset($form[$name]);
}
return $form;
}
/**
* Implements hook_field_widget_error().
*/
function date_field_widget_error($element, $error, $form, &$form_state) {
form_error($element[$error['error']], $error['message']);
}
/**
* Retrieve a date format string from formatter settings.
*/
function date_formatter_format($formatter, $settings, $granularity = array(), $langcode = NULL) {
$format_type = !empty($settings['format_type']) ? $settings['format_type'] : 'format_interval';
switch ($formatter) {
case 'format_interval':
return 'format_interval';
break;
case 'date_plain':
return 'date_plain';
break;
default:
$format = date_format_type_format($format_type, $langcode);
break;
}
// A selected format might include timezone information.
array_push($granularity, 'timezone');
return date_limit_format($format, $granularity);
}
/**
* Helper function to get the right format for a format type.
* Checks for locale-based format first.
*/
function date_format_type_format($format_type, $langcode = NULL) {
$static = &drupal_static(__FUNCTION__);
if (!isset($static[$langcode][$format_type])) {
$format = system_date_format_locale($langcode, $format_type);
// If locale enabled and $format_type inexistent in {date_format_locale}
// we receive (due to inconsistency of core api) an array of all (other)
// formats available for $langcode in locale table.
// However there's no guarantee that the key $format_type exists.
// See http://drupal.org/node/1302358.
if (!is_string($format)) {
// If the configuration page at admin/config/regional/date-time was
// never saved, the default core date format variables
// ('date_format_short', 'date_format_medium', and 'date_format_long')
// will not be stored in the database, so we have to define their
// expected defaults here.
switch ($format_type) {
case 'short':
$default = 'm/d/Y - H:i';
break;
case 'long':
$default = 'l, F j, Y - H:i';
break;
// If it's not one of the core date types and isn't stored in the
// database, we'll fall back on using the same default format as the
// 'medium' type.
case 'medium':
default:
// @todo: If a non-core module provides a date type and does not
// variable_set() a default for it, the default assumed here may
// not be correct (since the default format used by 'medium' may
// not even be one of the allowed formats for the date type in
// question). To fix this properly, we should really call
// system_get_date_formats($format_type) and take the first
// format from that list as the default. However, this function
// is called often (on many different page requests), so calling
// system_get_date_formats() from here would be a performance hit
// since that function writes several records to the database
// during each page request that calls it.
$default = 'D, m/d/Y - H:i';
break;
}
$format = variable_get('date_format_' . $format_type, $default);
}
$static[$langcode][$format_type] = $format;
}
return $static[$langcode][$format_type];
}
/**
* Helper function to adapt entity date fields to formatter settings.
*/
function date_prepare_entity($formatter, $entity_type, $entity, $field, $instance, $langcode, $item, $display) {
// If there are options to limit multiple values,
// alter the entity values to match.
$field_name = $field['field_name'];
$options = $display['settings'];
$max_count = $options['multiple_number'];
// If no results should be shown, empty the values and return.
if (is_numeric($max_count) && $max_count == 0) {
$entity->{$field_name} = array();
return $entity;
}
// Otherwise removed values that should not be displayed.
if (!empty($options['multiple_from']) || !empty($options['multiple_to']) || !empty($max_count)) {
$format = date_type_format($field['type']);
include_once drupal_get_path('module', 'date_api') . '/date_api_sql.inc';
$date_handler = new date_sql_handler($field);
$arg0 = !empty($options['multiple_from']) ? $date_handler->arg_replace($options['multiple_from']) : variable_get('date_min_year', 100) . '-01-01T00:00:00';
$arg1 = !empty($options['multiple_to']) ? $date_handler->arg_replace($options['multiple_to']) : variable_get('date_max_year', 4000) . '-12-31T23:59:59';
if (!empty($arg0) && !empty($arg1)) {
$arg = $arg0 . '--' . $arg1;
}
elseif (!empty($arg0)) {
$arg = $arg0;
}
elseif (!empty($arg1)) {
$arg = $arg1;
}
if (!empty($arg)) {
$range = $date_handler->arg_range($arg);
$start = date_format($range[0], $format);
$end = date_format($range[1], $format);
// Empty out values we don't want to see.
$count = 0;
foreach ($entity->{$field_name}[$langcode] as $delta => $value) {
if (!empty($entity->date_repeat_show_all)) {
break;
}
elseif ((!empty($max_count) && is_numeric($max_count) && $count >= $max_count) ||
(!empty($value['value']) && $value['value'] < $start) ||
(!empty($value['value2']) && $value['value2'] > $end)) {
unset($entity->{$field_name}[$langcode][$delta]);
}
else {
$count++;
}
}
}
}
return $entity;
}
/**
* Callback to alter the property info of date fields.
*
* @see date_field_info()
*/
function date_entity_metadata_property_info_alter(&$info, $entity_type, $field, $instance, $field_type) {
$name = $field['field_name'];
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
if ($field['type'] != 'datestamp' || $field['settings']['timezone_db'] != 'UTC') {
// Add a getter callback to convert the date into the right format.
$property['getter callback'] = 'date_entity_metadata_field_getter';
$property['setter callback'] = 'date_entity_metadata_field_setter';
unset($property['query callback']);
}
if (!empty($field['settings']['todate'])) {
// Define a simple data structure containing both dates.
$property['type'] = ($field['cardinality'] != 1) ? 'list<struct>' : 'struct';
$property['getter callback'] = 'entity_metadata_field_verbatim_get';
$property['setter callback'] = 'entity_metadata_field_verbatim_set';
$property['property info'] = array(
'value' => array(
'type' => 'date',
'label' => t('Start date'),
'getter callback' => 'date_entity_metadata_struct_getter',
'setter callback' => 'date_entity_metadata_struct_setter',
// The getter and setter callbacks for 'value' and 'value2'
// will not provide the field name as $name, we'll add it to $info.
'field_name' => $field['field_name'],
),
'value2' => array(
'type' => 'date',
'label' => t('End date'),
'getter callback' => 'date_entity_metadata_struct_getter',
'setter callback' => 'date_entity_metadata_struct_setter',
// The getter and setter callbacks for 'value' and 'value2'
// will not provide the field name as $name, we'll add it to $info.
'field_name' => $field['field_name'],
),
'duration' => array(
'type' => 'duration',
'label' => t('Duration'),
'desription' => t('The duration of the time period given by the dates.'),
'getter callback' => 'date_entity_metadata_duration_getter',
// No setter callback for duration.
// The getter callback for duration will not provide the field name
// as $name, we'll add it to $info.
'field_name' => $field['field_name'],
),
);
unset($property['query callback']);
}
}
/**
* Getter callback to return date values as datestamp in UTC from the field.
*/
function date_entity_metadata_field_getter($entity, array $options, $name, $entity_type, &$context) {
$return = entity_metadata_field_verbatim_get($entity, $options, $name, $entity_type, $context);
$items = ($context['field']['cardinality'] == 1) ? array($return) : $return;
foreach ($items as $key => $item) {
$items[$key] = date_entity_metadata_struct_getter($item, $options, 'value', 'struct', $context);
}
return ($context['field']['cardinality'] == 1) ? $items[0] : $items;
}
/**
* Getter callback to return date values as datestamp in UTC.
*/
function date_entity_metadata_struct_getter($item, array $options, $name, $type, $info) {
$value = trim($item[$name]);
if (empty($value)) {
return NULL;
}
$timezone_db = !empty($item['timezone_db']) ? $item['timezone_db'] : 'UTC';
$date = new DateObject($value, $timezone_db);
return !empty($date) ? date_format_date($date, 'custom', 'U') : NULL;
}
/**
* Getter callback to return the duration of the time period given by the dates.
*/
function date_entity_metadata_duration_getter($item, array $options, $name, $type, $info) {
$value = date_entity_metadata_struct_getter($item, $options, 'value', 'struct', $info);
$value2 = date_entity_metadata_struct_getter($item, $options, 'value2', 'struct', $info);
if ($value && $value2) {
return $value2 - $value;
}
}
/**
* Callback for setting field property values.
*
* Based on entity_metadata_field_property_set(), the original property setter,
* adapted to transform non-timestamp date values to timestamps.
*/
function date_entity_metadata_field_setter(&$entity, $name, $value, $langcode, $entity_type, $info) {
$field = field_info_field($name);
if (!isset($langcode)) {
// Try to figure out the default language used by the entity.
// @todo: Update once http://drupal.org/node/1260640 has been fixed.
$langcode = isset($entity->language) ? $entity->language : LANGUAGE_NONE;
}
$values = $field['cardinality'] == 1 ? array($value) : (array) $value;
$items = array();
foreach ($values as $delta => $value) {
// Make use of the struct setter to convert the date back to a timestamp.
$info['field_name'] = $name;
date_entity_metadata_struct_setter($items[$delta], 'value', $value, $langcode, 'struct', $info);
}
$entity->{$name}[$langcode] = $items;
// Empty the static field language cache, so the field system picks up any
// possible new languages.
drupal_static_reset('field_language');
}
/**
* Callback for setting an individual field value if a to-date may be there too.
* Based on entity_property_verbatim_set().
*
* The passed in unix timestamp (UTC) is converted to the right value and
* format dependent on the field.
*
* $name is either 'value' or 'value2'.
*/
function date_entity_metadata_struct_setter(&$item, $name, $value, $langcode, $type, $info) {
if (!isset($value)) {
$item[$name] = NULL;
}
else {
$field = field_info_field($info['field_name']);
$format = date_type_format($field['type']);
$timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
$date = new DateObject($value, 'UTC');
if ($timezone_db != 'UTC') {
date_timezone_set($date, timezone_open($timezone_db));
}
$item[$name] = $date->format($format);
}
}
/**
* Duplicate functionality of what is now date_all_day_field() in
* the Date All Day module. Copy left here to avoid breaking other
* modules that use this function.
*
* DEPRECATED!, will be removed at some time in the future.
*/
function date_field_all_day($field, $instance, $date1, $date2 = NULL) {
if (empty($date1) || !is_object($date1)) {
return FALSE;
}
elseif (!date_has_time($field['settings']['granularity'])) {
return TRUE;
}
if (empty($date2)) {
$date2 = $date1;
}
$granularity = date_granularity_precision($field['settings']['granularity']);
$increment = isset($instance['widget']['settings']['increment']) ? $instance['widget']['settings']['increment'] : 1;
return date_is_all_day(date_format($date1, DATE_FORMAT_DATETIME), date_format($date2, DATE_FORMAT_DATETIME), $granularity, $increment);
}
/**
* Generates a Date API SQL handler for the given date field.
*
* The handler will be set up to make the correct timezone adjustments
* for the field settings.
*
* @param array $field
* The $field array.
* @param string $compare_tz
* The timezone used for comparison values in the SQL.
*
* DEPRECATED!, will be removed at some time in the future.
*/
function date_field_get_sql_handler($field, $compare_tz = NULL) {
module_load_include('inc', 'date_api', 'date_api_sql');
$db_info = date_api_database_info($field);
// Create a DateAPI SQL handler class for this field type.
$handler = new date_sql_handler($field['type']);
// If this date field stores a timezone in the DB, tell the handler about it.
if ($field['settings']['tz_handling'] == 'date') {
$handler->db_timezone_field = $db_info['columns']['timezone']['column'];
}
else {
$handler->db_timezone = date_get_timezone_db($field['settings']['tz_handling']);
}
if (empty($compare_tz)) {
$compare_tz = date_get_timezone($field['settings']['tz_handling']);
}
$handler->local_timezone = $compare_tz;
// Now that the handler is properly initialized, force the DB
// to use UTC so no timezone conversions get added to things like
// NOW() or FROM_UNIXTIME().
$handler->set_db_timezone();
return $handler;
}
/**
* Implements hook_field_widget_properties_alter().
*
* Alters the widget properties of a field instance before it gets displayed.
* Used here to flag new entities so we can later tell if they need default values.
*/
function date_field_widget_properties_alter(&$widget, $context) {
if (in_array($widget['type'], array('date_select', 'date_text', 'date_popup'))) {
$entity_type = $context['entity_type'];
$entity = $context['entity'];
$info = entity_get_info($entity_type);
$id = $info['entity keys']['id'];
$widget['is_new']= FALSE;
if (empty($entity->$id)) {
$widget['is_new'] = TRUE;
}
}
}

View File

@@ -0,0 +1,355 @@
<?php
/**
* @file
* Theme functions.
*/
/**
* @addtogroup themeable
* @{
*
* Formatter themes
*/
/**
* Returns HTML for a date element formatted as a Start/End combination.
*
* $entity->date_id
* If set, this will show only an individual date on a field with
* multiple dates. The value should be a string that contains
* the following values, separated with periods:
* - module name of the module adding the item
* - node nid
* - field name
* - delta value of the field to be displayed
* - other information the module's custom theme might need
*
* Used by the calendar module and available for other uses.
* example: 'date.217.field_date.3.test'
*
* $entity->date_repeat_show
* If true, tells the theme to show all the computed values of a repeating
* date. If not true or not set, only the start date and the repeat rule
* will be displayed.
*
* $dates['format']
* The format string used on these dates
* $dates['value']['local']['object']
* The local date object for the Start date
* $dates['value2']['local']['object']
* The local date object for the End date
* $dates['value']['local']['datetime']
* The datetime value of the Start date database (GMT) value
* $dates['value2']['local']['datetime']
* The datetime value of the End date database (GMT) value
* $dates['value']['formatted']
* Formatted Start date, i.e. 'February 15, 2007 2:00 pm';
* $dates['value']['formatted_date']
* Only the date part of the formatted Start date
* $dates['value']['formatted_time']
* Only the time part of the formatted Start date
* $dates['value2']['formatted']
* Formatted End date, i.e. 'February 15, 2007 6:00 pm';
* $dates['value2']['formatted_date']
* Only the date part of the formatted End date
* $dates['value2']['formatted_time']
* Only the time part of the formatted End date
*/
function theme_date_display_combination($variables) {
static $repeating_ids = array();
$entity_type = $variables['entity_type'];
$entity = $variables['entity'];
$field = $variables['field'];
$instance = $variables['instance'];
$langcode = $variables['langcode'];
$item = $variables['item'];
$delta = $variables['delta'];
$display = $variables['display'];
$field_name = $field['field_name'];
$formatter = $display['type'];
$options = $display['settings'];
$dates = $variables['dates'];
$attributes = $variables['attributes'];
$rdf_mapping = $variables['rdf_mapping'];
$add_rdf = $variables['add_rdf'];
$precision = date_granularity_precision($field['settings']['granularity']);
$output = '';
// If date_id is set for this field and delta doesn't match, don't display it.
if (!empty($entity->date_id)) {
foreach ((array) $entity->date_id as $key => $id) {
list($module, $nid, $field_name, $item_delta, $other) = explode('.', $id . '.');
if ($field_name == $field['field_name'] && isset($delta) && $item_delta != $delta) {
return $output;
}
}
}
// Check the formatter settings to see if the repeat rule should be displayed.
// Show it only with the first multiple value date.
list($id) = entity_extract_ids($entity_type, $entity);
if (!in_array($id, $repeating_ids) && module_exists('date_repeat_field') && !empty($item['rrule']) && $options['show_repeat_rule'] == 'show') {
$repeat_vars = array(
'field' => $field,
'item' => $item,
'entity_type' => $entity_type,
'entity' => $entity,
);
$output .= theme('date_repeat_display', $repeat_vars);
$repeating_ids[] = $id;
}
// If this is a full node or a pseudo node created by grouping multiple
// values, see exactly which values are supposed to be visible.
if (isset($entity->$field_name)) {
$entity = date_prepare_entity($formatter, $entity_type, $entity, $field, $instance, $langcode, $item, $display);
// Did the current value get removed by formatter settings?
if (empty($entity->{$field_name}[$langcode][$delta])) {
return $output;
}
// Adjust the $element values to match the changes.
$element['#entity'] = $entity;
}
switch ($options['fromto']) {
case 'value':
$date1 = $dates['value']['formatted'];
$date2 = $date1;
break;
case 'value2':
$date2 = $dates['value2']['formatted'];
$date1 = $date2;
break;
default:
$date1 = $dates['value']['formatted'];
$date2 = $dates['value2']['formatted'];
break;
}
// Pull the timezone, if any, out of the formatted result and tack it back on
// at the end, if it is in the current formatted date.
$timezone = $dates['value']['formatted_timezone'];
if ($timezone) {
$timezone = ' ' . $timezone;
}
$date1 = str_replace($timezone, '', $date1);
$date2 = str_replace($timezone, '', $date2);
$time1 = preg_replace('`^([\(\[])`', '', $dates['value']['formatted_time']);
$time1 = preg_replace('([\)\]]$)', '', $time1);
$time2 = preg_replace('`^([\(\[])`', '', $dates['value2']['formatted_time']);
$time2 = preg_replace('([\)\]]$)', '', $time2);
// A date with a granularity of 'hour' has a time string that is an integer
// value. We can't use that to replace time strings in formatted dates.
$has_time_string = date_has_time($field['settings']['granularity']);
if ($precision == 'hour') {
$has_time_string = FALSE;
}
// No date values, display nothing.
if (empty($date1) && empty($date2)) {
$output .= '';
}
// Start and End dates match or there is no End date, display a complete
// single date.
elseif ($date1 == $date2 || empty($date2)) {
$output .= theme('date_display_single', array(
'date' => $date1,
'timezone' => $timezone,
'attributes' => $attributes,
'rdf_mapping' => $rdf_mapping,
'add_rdf' => $add_rdf,
'dates' => $dates,
));
}
// Same day, different times, don't repeat the date but show both Start and
// End times. We can NOT do this if the replacement value is an integer
// instead of a time string.
elseif ($has_time_string && $dates['value']['formatted_date'] == $dates['value2']['formatted_date']) {
// Replace the original time with the start/end time in the formatted start
// date. Make sure that parentheses or brackets wrapping the time will be
// retained in the final result.
$time = theme('date_display_range', array(
'date1' => $time1,
'date2' => $time2,
'timezone' => $timezone,
'attributes' => $attributes,
'rdf_mapping' => $rdf_mapping,
'add_rdf' => $add_rdf,
'dates' => $dates,
));
$replaced = str_replace($time1, $time, $date1);
$output .= theme('date_display_single', array(
'date' => $replaced,
'timezone' => $timezone,
'attributes' => array(),
'rdf_mapping' => array(),
'add_rdf' => FALSE,
'dates' => $dates,
));
}
// Different days, display both in their entirety.
else {
$output .= theme('date_display_range', array(
'date1' => $date1,
'date2' => $date2,
'timezone' => $timezone,
'attributes' => $attributes,
'rdf_mapping' => $rdf_mapping,
'add_rdf' => $add_rdf,
'dates' => $dates,
));
}
return $output;
}
/**
* Template preprocess function for displaying a single date.
*/
function template_preprocess_date_display_single(&$variables) {
if ($variables['add_rdf']) {
// Pass along the rdf mapping for this field, if any. Add some default rdf
// attributes that will be used if not overridden by attributes passed in.
$rdf_mapping = $variables['rdf_mapping'];
$base_attributes = array(
'property' => array('dc:date'),
'datatype' => 'xsd:dateTime',
'content' => $variables['dates']['value']['formatted_iso'],
);
$variables['attributes'] = $variables['attributes'] + $base_attributes;
}
}
/**
* Returns HTML for a date element formatted as a single date.
*/
function theme_date_display_single($variables) {
$date = $variables['date'];
$timezone = $variables['timezone'];
$attributes = $variables['attributes'];
// Wrap the result with the attributes.
return '<span class="date-display-single"' . drupal_attributes($attributes) . '>' . $date . $timezone . '</span>';
}
/**
* Template preprocess function for displaying a range of dates.
*/
function template_preprocess_date_display_range(&$variables) {
// Merge in the shared attributes for themes to use.
$variables['attributes_start'] += $variables['attributes'];
$variables['attributes_end'] += $variables['attributes'];
if ($variables['add_rdf']) {
// Pass along the rdf mapping for this field, if any. Add some default rdf
// attributes that will be used if not overridden by attributes passed in.
$rdf_mapping = $variables['rdf_mapping'];
$dates = $variables['dates'];
$base_attributes = array(
'property' => array('dc:date'),
'datatype' => 'xsd:dateTime',
'content' => $dates['value']['formatted_iso'],
);
$variables['attributes_start'] += $base_attributes;
$variables['attributes_end'] += $base_attributes;
$variables['attributes_end']['content'] = $dates['value2']['formatted_iso'];
foreach ($variables['attributes_end']['property'] as $delta => $property) {
$variables['attributes_end']['property'][$delta] = str_replace('start', 'end', $property);
}
}
}
/**
* Returns HTML for a date element formatted as a range.
*/
function theme_date_display_range($variables) {
$date1 = $variables['date1'];
$date2 = $variables['date2'];
$timezone = $variables['timezone'];
$attributes_start = $variables['attributes_start'];
$attributes_end = $variables['attributes_end'];
// Wrap the result with the attributes.
return t('!start-date to !end-date', array(
'!start-date' => '<span class="date-display-start"' . drupal_attributes($attributes_start) . '>' . $date1 . '</span>',
'!end-date' => '<span class="date-display-end"' . drupal_attributes($attributes_end) . '>' . $date2 . $timezone . '</span>',
));
}
/**
* Returns HTML for a date element formatted as an interval.
*/
function theme_date_display_interval($variables) {
$entity = $variables['entity'];
$options = $variables['display']['settings'];
$dates = $variables['dates'];
$attributes = $variables['attributes'];
// Get the formatter settings, either the default settings for this node type
// or the View settings stored in $entity->date_info.
if (!empty($entity->date_info) && !empty($entity->date_info->formatter_settings)) {
$options = $entity->date_info->formatter_settings;
}
$time_ago_vars = array(
'start_date' => $dates['value']['local']['object'],
'end_date' => $dates['value2']['local']['object'],
'interval' => $options['interval'],
'interval_display' => $options['interval_display'],
);
if ($return = theme('date_time_ago', $time_ago_vars)) {
return '<span class="date-display-interval"' . drupal_attributes($attributes) . ">$return</span>";
}
else {
return '';
}
}
/**
* Returns HTML for a start/end date combination on form.
*/
function theme_date_combo($variables) {
$element = $variables['element'];
$field = field_info_field($element['#field_name']);
$instance = field_info_instance($element['#entity_type'], $element['#field_name'], $element['#bundle']);
// Group start/end items together in fieldset.
$fieldset = array(
'#title' => t($element['#title']) . ' ' . ($element['#delta'] > 0 ? intval($element['#delta'] + 1) : ''),
'#value' => '',
'#description' => !empty($element['#fieldset_description']) ? $element['#fieldset_description'] : '',
'#attributes' => array(),
'#children' => $element['#children'],
);
return theme('fieldset', array('element' => $fieldset));
}
/**
* Returns HTML for the text/select options for date parts in a table.
*/
function theme_date_text_parts($variables) {
$element = $variables['element'];
$rows = array();
foreach (date_granularity_names() as $key => $part) {
if ($element[$key]['#type'] == 'hidden') {
$rows[] = drupal_render($element[$key]);
}
else {
$rows[] = array($part, drupal_render($element[$key][0]), drupal_render($element[$key][1]));
}
}
if ($element['year']['#type'] == 'hidden') {
return implode($rows) . drupal_render_children($element);
}
else {
$header = array(t('Date part'), t('Select list'), t('Text field'));
return theme('table', array('header' => $header, 'rows' => $rows)) . drupal_render_children($element);
}
}
/** @} End of addtogroup themeable */

View File

@@ -0,0 +1,72 @@
<?php
/**
* @file
* Token module integration.
*/
/**
* Implements hook_token_info().
*/
function date_token_info() {
// All date types can share the same date value type.
$info['types']['date-field-value'] = array(
'name' => t('Date field values'),
'description' => t('Tokens related to date field values.'),
'needs-data' => 'date-field-value',
'field-value' => TRUE,
);
// Provide two tokens: 'date' (the date or start-date), and 'end-date'.
$info['tokens']['date-field-value']['date'] = array(
'name' => t('Date'),
'description' => t('The date value.'),
'type' => 'date',
);
$info['tokens']['date-field-value']['to-date'] = array(
'name' => t('End Date'),
'description' => t('The End date value.'),
'type' => 'date',
);
return $info;
}
/**
* Implements hook_tokens().
*/
function date_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
$language_code = isset($options['language']) ? $options['language']->language : NULL;
if (($type == 'date-field-value') && !empty($data['item'])) {
$item = $data['item'];
// Create tokens for the field "Date" or "Start date".
if (($date_tokens = token_find_with_prefix($tokens, 'date')) && !empty($item['value'])) {
// Load the Start date and convert to a unix timestamp.
$date = new DateObject($item['value'], $item['timezone_db'], date_type_format($item['date_type']));
if (!empty($date) && $item['timezone_db'] != $item['timezone']) {
date_timezone_set($date, timezone_open($item['timezone']));
}
$timestamp = !empty($date) ? date_format_date($date, 'custom', 'U') : '';
// Generate the token replacements, using the date token type provided
// by system.module.
$replacements += token_generate('date', $date_tokens, array('date' => $timestamp), $options);
}
// Create tokens for the field "End date".
if (($date_tokens = token_find_with_prefix($tokens, 'end-date')) && !empty($item['value2'])) {
// Load the to date and convert to a unix timestamp.
$date = new DateObject($item['value2'], $item['timezone_db'], date_type_format($item['date_type']));
if (!empty($date) && $item['timezone_db'] != $item['timezone']) {
date_timezone_set($date, timezone_open($item['timezone']));
}
$timestamp = !empty($date) ? date_format_date($date, 'custom', 'U') : '';
// Generate the token replacements, using the date token type provided
// by system.module.
$replacements += token_generate('date', $date_tokens, array('date' => $timestamp), $options);
}
}
return $replacements;
}

View File

@@ -0,0 +1,605 @@
<?php
/**
* @file
* Date administration code.
*/
/**
* Settings for the default formatter.
*/
function date_default_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$formatter = $display['type'];
$form = array();
$form['format_type'] = array(
'#title' => t('Choose how users view dates and times:'),
'#type' => 'select',
'#options' => date_format_type_options(),
'#default_value' => $settings['format_type'],
'#description' => t('To add or edit options, visit <a href="@date-time-page">Date and time settings</a>.', array('@date-time-page' => url('admin/config/regional/date-time'))),
'#weight' => 0,
);
$form['fromto'] = array(
'#title' => t('Display:'),
'#type' => 'select',
'#options' => array(
'both' => t('Both Start and End dates'),
'value' => t('Start date only'),
'value2' => t('End date only'),
),
'#access' => $field['settings']['todate'],
'#default_value' => $settings['fromto'],
'#weight' => 1,
);
// Make the string translatable by keeping it as a whole rather than
// translating prefix and suffix separately.
list($prefix, $suffix) = explode('@count', t('Show @count value(s)'));
$form['multiple_number'] = array(
'#type' => 'textfield',
'#title' => t('Multiple values:'),
'#size' => 5,
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
'#default_value' => $settings['multiple_number'],
'#weight' => 2,
'#access' => ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) || ($field['cardinality'] > 1),
'#description' => t('Identify a specific number of values to display, or leave blank to show all values.'),
);
list($prefix, $suffix) = explode('@isodate', t('starting from @isodate'));
$form['multiple_from'] = array(
'#type' => 'textfield',
'#size' => 15,
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
'#default_value' => $settings['multiple_from'],
'#weight' => 3,
'#access' => ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) || ($field['cardinality'] > 1),
);
list($prefix, $suffix) = explode('@isodate', t('ending with @isodate'));
$form['multiple_to'] = array(
'#type' => 'textfield',
'#size' => 15,
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
'#default_value' => $settings['multiple_to'],
'#weight' => 4,
'#access' => ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED) || ($field['cardinality'] > 1),
'#description' => t('Identify specific start and/or end dates in the format YYYY-MM-DDTHH:MM:SS, or leave blank for all available dates.'),
);
return $form;
}
/**
* Settings for the interval formatter.
*/
function date_interval_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$form = array();
$form['interval'] = array(
'#title' => t('Interval'),
'#description' => t("How many time units should be shown in the 'time ago' string."),
'#type' => 'select',
'#options' => drupal_map_assoc(range(1, 6)),
'#default_value' => $settings['interval'],
'#weight' => 0,
);
// Uses the same options used by Views format_interval.
$options = array(
'raw time ago' => t('Time ago'),
'time ago' => t('Time ago (with "ago" appended)'),
'raw time hence' => t('Time hence'),
'time hence' => t('Time hence (with "hence" appended)'),
'raw time span' => t('Time span (future dates have "-" prepended)'),
'inverse time span' => t('Time span (past dates have "-" prepended)'),
'time span' => t('Time span (with "ago/hence" appended)'),
);
$form['interval_display'] = array(
'#title' => t('Display'),
'#description' => t("How to display the time ago or time hence for this field."),
'#type' => 'select',
'#options' => $options,
'#default_value' => $settings['interval_display'],
'#weight' => 0,
);
return $form;
}
/**
* Settings summary for the default formatter.
*/
function date_default_formatter_settings_summary($field, $instance, $view_mode) {
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$formatter = $display['type'];
$format_types = date_format_type_options();
$summary = array();
$format = FALSE;
switch ($formatter) {
case 'date_plain':
$format = t('Plain');
break;
case 'format_interval':
$format = t('Interval');
break;
default:
if (!empty($format_types[$settings['format_type']])) {
$format = $format_types[$settings['format_type']];
}
}
if ($format) {
$summary[] = t('Display dates using the @format format', array('@format' => $format));
}
else {
$summary[] = t('Display dates using the default format because the specified format (@format) is not defined', array('@format' => $settings['format_type']));
}
if (array_key_exists('fromto', $settings) && $field['settings']['todate']) {
$options = array(
'both' => t('Display both Start and End dates'),
'value' => t('Display Start date only'),
'value2' => t('Display End date only'),
);
$summary[] = $options[$settings['fromto']];
}
if (array_key_exists('multiple_number', $settings) && !empty($field['cardinality'])) {
$summary[] = t('Show @count value(s) starting with @date1, ending with @date2', array(
'@count' => !empty($settings['multiple_number']) ? $settings['multiple_number'] : t('all'),
'@date1' => !empty($settings['multiple_from']) ? $settings['multiple_from'] : t('earliest'),
'@date2' => !empty($settings['multiple_to']) ? $settings['multiple_to'] : t('latest'),
));
}
return $summary;
}
/**
* Settings summary for the interval formatter.
*
* @TODO Add settings later.
*/
function date_interval_formatter_settings_summary($field, $instance, $view_mode) {
$summary = array();
$display = $instance['display'][$view_mode];
$settings = $display['settings'];
$formatter = $display['type'];
$summary[] = t('Display time ago, showing @interval units.', array('@interval' => $settings['interval']));
return $summary;
}
/**
* Helper function for date_field_instance_settings_form().
*
* @see date_field_instance_settings_form_validate()
*/
function _date_field_instance_settings_form($field, $instance) {
$widget = $instance['widget'];
$settings = $instance['settings'];
$widget_settings = $instance['widget']['settings'];
$form['default_value'] = array(
'#type' => 'select',
'#title' => t('Default date'),
'#default_value' => $settings['default_value'],
'#options' => array('blank' => t('No default value'), 'now' => t('Now'), 'strtotime' => t('Relative')),
'#weight' => 1,
'#fieldset' => 'default_values',
);
$description = t("Describe a time by reference to the current day, like '+90 days' (90 days from the day the field is created) or '+1 Saturday' (the next Saturday). See !strtotime for more details.", array('!strtotime' => l(t('strtotime'), 'http://www.php.net/manual/en/function.strtotime.php')));
$form['default_value_code'] = array(
'#type' => 'textfield',
'#title' => t('Relative default value'),
'#description' => $description,
'#default_value' => $settings['default_value_code'],
'#states' => array(
'visible' => array(
':input[name="instance[settings][default_value]"]' => array('value' => 'strtotime')),
),
'#weight' => 1.1,
'#fieldset' => 'default_values',
);
$form['default_value2'] = array(
'#type' => !empty($field['settings']['todate']) ? 'select' : 'hidden',
'#title' => t('Default end date'),
'#default_value' => $settings['default_value2'],
'#options' => array('same' => t('Same as Default date'), 'blank' => t('No default value'), 'now' => t('Now'), 'strtotime' => t('Relative')),
'#weight' => 2,
'#fieldset' => 'default_values',
);
$form['default_value_code2'] = array(
'#type' => !empty($field['settings']['todate']) ? 'textfield' : 'hidden',
'#title' => t('Relative default value for end date'),
'#description' => $description,
'#default_value' => $settings['default_value_code2'],
'#states' => array(
'visible' => array(
':input[name="instance[settings][default_value2]"]' => array('value' => 'strtotime')),
),
'#weight' => 2.1,
'#fieldset' => 'default_values',
);
$form['#element_validate'] = array('date_field_instance_settings_form_validate');
$context = array(
'field' => $field,
'instance' => $instance,
);
drupal_alter('date_field_instance_settings_form', $form, $context);
return $form;
}
/**
* Form validation handler for _date_field_instance_settings_form().
*/
function date_field_instance_settings_form_validate(&$form, &$form_state) {
$settings = $form_state['values']['instance']['settings'];
if ($settings['default_value'] == 'strtotime') {
$is_strtotime = @strtotime($settings['default_value_code']);
if (!$is_strtotime) {
form_set_error('instance][settings][default_value_code', t('The Strtotime default value is invalid.'));
}
}
if (isset($settings['default_value2']) && $settings['default_value2'] == 'strtotime') {
$is_strtotime = @strtotime($settings['default_value_code2']);
if (!$is_strtotime) {
form_set_error('instance][settings][default_value_code2', t('The Strtotime default value for the End Date is invalid.'));
}
}
}
/**
* Helper function for date_field_widget_settings_form().
*
* @see date_field_widget_settings_form_validate()
*/
function _date_field_widget_settings_form($field, $instance) {
$widget = $instance['widget'];
$settings = $widget['settings'];
$form = array(
'#element_validate' => array('date_field_widget_settings_form_validate'),
);
$options = array();
if ($widget['type'] == 'date_popup' && module_exists('date_popup')) {
$formats = date_popup_formats();
}
else {
// Example input formats must show all possible date parts, so add seconds.
$formats = str_replace('i', 'i:s', array_keys(system_get_date_formats('short')));
$formats = drupal_map_assoc($formats);
}
$now = date_example_date();
foreach ($formats as $f) {
$options[$f] = date_format_date($now, 'custom', $f);
}
$form['input_format'] = array(
'#type' => 'select',
'#title' => t('Date entry options'),
'#default_value' => $settings['input_format'],
'#options' => $options,
'#description' => t('Control the order and format of the options users see.'),
'#weight' => 3,
'#fieldset' => 'date_format',
);
// Only a limited set of formats is available for the Date Popup module.
if ($widget['type'] != 'date_popup') {
$form['input_format']['#options']['custom'] = t('Custom format');
$form['input_format_custom'] = array(
'#type' => 'textfield',
'#title' => t('Custom input format'),
'#default_value' => $settings['input_format_custom'],
'#description' => t("Override the input format selected above. Define a php date format string like 'm-d-Y H:i' (see <a href=\"@link\">http://php.net/date</a> for more details).", array('@link' => 'http://php.net/date')),
'#weight' => 5,
'#fieldset' => 'date_format',
'#attributes' => array('class' => array('indent')),
'#states' => array(
'visible' => array(
':input[name="instance[widget][settings][input_format]"]' => array('value' => 'custom'),
),
),
);
}
else {
$form['input_format_custom'] = array(
'#type' => 'hidden',
'#value' => '',
);
}
if (in_array($widget['type'], array('date_select', 'date_popup'))) {
$form['year_range'] = array(
'#type' => 'date_year_range',
'#default_value' => $settings['year_range'],
'#fieldset' => 'date_format',
'#weight' => 6,
);
$form['increment'] = array(
'#type' => 'select', '#title' => t('Time increments'),
'#default_value' => $settings['increment'],
'#options' => array(
1 => t('1 minute'),
5 => t('5 minute'),
10 => t('10 minute'),
15 => t('15 minute'),
30 => t('30 minute')),
'#weight' => 7,
'#fieldset' => 'date_format',
);
}
else {
$form['year_range'] = array(
'#type' => 'hidden',
'#value' => $settings['year_range'],
);
$form['increment'] = array(
'#type' => 'hidden',
'#value' => $settings['increment'],
);
}
$form['label_position'] = array(
'#type' => 'value',
'#value' => $settings['label_position'],
);
$form['text_parts'] = array(
'#type' => 'value',
'#value' => $settings['text_parts'],
);
$form['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#fieldset' => 'date_format',
'#weight' => 9,
);
if (in_array($widget['type'], array('date_select'))) {
$options = array('above' => t('Above'), 'within' => t('Within'), 'none' => t('None'));
$description = t("The location of date part labels, like 'Year', 'Month', or 'Day' . 'Above' displays the label as titles above each date part. 'Within' inserts the label as the first option in the select list and in blank textfields. 'None' doesn't label any of the date parts. Theme functions like 'date_part_label_year' and 'date_part_label_month' control label text.");
}
else {
$options = array('above' => t('Above'), 'none' => t('None'));
$description = t("The location of date part labels, like 'Year', 'Month', or 'Day' . 'Above' displays the label as titles above each date part. 'None' doesn't label any of the date parts. Theme functions like 'date_part_label_year' and 'date_part_label_month' control label text.");
}
$form['advanced']['label_position'] = array(
'#type' => 'radios',
'#options' => $options,
'#default_value' => $settings['label_position'],
'#title' => t('Position of date part labels'),
'#description' => $description,
);
$form['advanced']['text_parts'] = array(
'#theme' => $widget['type'] == 'date_select' ? 'date_text_parts' : '',
);
$text_parts = (array) $settings['text_parts'];
foreach (date_granularity_names() as $key => $value) {
if ($widget['type'] == 'date_select') {
$form['advanced']['text_parts'][$key] = array(
'#type' => 'radios',
'#default_value' => in_array($key, $text_parts) ? 1 : 0,
'#options' => array(0 => '', 1 => ''),
);
}
else {
$form['advanced']['text_parts'][$key] = array(
'#type' => 'value',
'#value' => (int) in_array($key, (array) $settings['text_parts']),
);
}
}
$context = array(
'field' => $field,
'instance' => $instance,
);
drupal_alter('date_field_widget_settings_form', $form, $context);
return $form;
}
/**
* Form validation handler for _date_field_widget_settings_form().
*/
function date_field_widget_settings_form_validate(&$form, &$form_state) {
// The widget settings are in the wrong place in the form because of #tree on
// the top level.
$settings = $form_state['values']['instance']['widget']['settings'];
$settings = array_merge($settings, $settings['advanced']);
unset($settings['advanced']);
form_set_value(array('#parents' => array('instance', 'widget', 'settings')), $settings, $form_state);
$widget = &$form_state['values']['instance']['widget'];
// Munge the table display for text parts back into an array of text parts.
if (is_array($widget['settings']['text_parts'])) {
form_set_value($form['text_parts'], array_keys(array_filter($widget['settings']['text_parts'])), $form_state);
}
if ($widget['settings']['input_format'] === 'custom' && empty($widget['settings']['input_format_custom'])) {
form_set_error('instance][widget][settings][input_format_custom', t('Please enter a custom date format, or choose one of the preset formats.'));
}
}
/**
* Helper function for date_field_settings_form().
*
* @see date_field_settings_validate()
*/
function _date_field_settings_form($field, $instance, $has_data) {
$settings = $field['settings'];
$form = array(
'#element_validate' => array('date_field_settings_validate'),
);
// Make sure granularity is in the right format and has no empty values.
if (!empty($settings['granularity']) && is_array($settings['granularity'])) {
$granularity = array_filter($settings['granularity']);
}
$tz_handling = $settings['tz_handling'];
$description = t('Select the date attributes to collect and store.');
$options = date_granularity_names();
$checkbox_year = array(
'#type' => 'checkbox',
'#title' => check_plain($options['year']),
'#value' => 'year',
'#return_value' => 'year',
'#disabled' => TRUE,
);
unset($options['year']);
$form['granularity'] = array(
'#type' => 'checkboxes',
'#title' => t('Date attributes to collect'),
'#default_value' => $granularity,
'#options' => $options,
'#attributes' => array('class' => array('container-inline')),
'#description' => $description,
'#disabled' => $has_data,
'year' => $checkbox_year,
);
$description = t('End dates are used to collect duration. E.g., allow an event to start on September 15, and end on September 16.');
$form['enddate_get'] = array(
'#type' => 'checkbox',
'#title' => t('Collect an end date'),
'#description' => $description,
'#default_value' => (empty($settings['todate']) ? FALSE : TRUE),
'#disabled' => $has_data,
);
$form['enddate_required'] = array(
'#type' => 'checkbox',
'#title' => t('Required'),
'#default_value' => ((isset($settings['todate']) && $settings['todate'] === 'required') ? TRUE : FALSE),
'#disabled' => $has_data,
'#states' => array(
'invisible' => array(
'input[name="field[settings][enddate_get]"]' => array('checked' => FALSE),
),
),
);
$description = t('Select the timezone handling method for this date field.');
$form['tz_handling'] = array(
'#type' => 'select',
'#title' => t('Time zone handling'),
'#default_value' => $tz_handling,
'#options' => date_timezone_handling_options(),
'#description' => $description,
'#disabled' => $has_data,
'#attached' => array(
'js' => array(drupal_get_path('module', 'date') . '/date_admin.js'),
),
);
// Force this value to hidden because we don't want to allow it to be changed
// right now, but allow it to be a variable if needed.
$form['timezone_db'] = array(
'#type' => 'hidden',
'#value' => date_get_timezone_db($tz_handling),
);
$form['cache_enabled'] = array(
'#type' => 'checkbox',
'#title' => t('Cache dates'),
'#description' => t('Date objects can be created and cached as date fields are loaded rather than when they are displayed to improve performance.'),
'#default_value' => !empty($settings['cache_enabled']),
'#weight' => 10,
);
$form['cache_count'] = array(
'#type' => 'textfield',
'#title' => t('Maximum dates per field'),
'#default_value' => (isset($settings['cache_count'])) ? $settings['cache_count'] : 4,
'#description' => t("If set to '0', all date values on every entity will be cached. Note that caching every date on fields that may have a large number of multiple or repeating values may create a significant performance penalty when the cache is cleared. The suggested setting for multiple value and repeating fields is no more than 4 values per field."),
'#size' => 3,
'#weight' => 11,
'#states' => array(
'visible' => array(
'input[name="field[settings][cache_enabled]"]' => array('checked' => TRUE),
),
),
);
$context = array(
'field' => $field,
'instance' => $instance,
'has_data' => $has_data,
);
drupal_alter('date_field_settings_form', $form, $context);
return $form;
}
/**
* Form validation handler for _date_field_settings_form().
*/
function date_field_settings_validate(&$form, &$form_state) {
$field = &$form_state['values']['field'];
if ($field['settings']['tz_handling'] == 'none') {
form_set_value($form['timezone_db'], '', $form_state);
}
else {
form_set_value($form['timezone_db'], date_get_timezone_db($field['settings']['tz_handling']), $form_state);
}
if ($field['settings']['tz_handling'] != 'none' && !in_array('hour', array_filter($field['settings']['granularity']))) {
form_set_error('field[settings][tz_handling]', t('Dates without hours granularity must not use any timezone handling.'));
}
// Extract the correct 'todate' value out of the two end date checkboxes.
if ($field['settings']['enddate_get']) {
if ($field['settings']['enddate_required']) {
$field['settings']['todate'] = 'required';
}
else {
$field['settings']['todate'] = 'optional';
}
}
else {
$field['settings']['todate'] = '';
}
// Don't save the pseudo values created in the UI.
unset($field['settings']['enddate_get'], $field['settings']['enddate_required']);
if (!empty($field['settings']['cache_enabled'])) {
if (!is_numeric($field['settings']['cache_count'])) {
form_set_error('field[settings][cache_count]', t('The number of cache values must be a number.'));
}
elseif ($field['settings']['cache_count'] < 0) {
form_set_error('field[settings][cache_count]', t('The number of cache values must be a number 0 or greater.'));
}
}
}
/**
* Timezone handling options.
*
* The 'none' option will do no timezone conversions and will store and display
* dates exactly as entered useful in locales or situations where timezone
* conversions are not working reliably, for dates with no times, for historical
* dates where timezones are irrelevant, or anytime conversion is unnecessary or
* undesirable.
*/
function date_timezone_handling_options() {
return array(
'site' => t("Site's time zone"),
'date' => t("Date's time zone"),
'user' => t("User's time zone"),
'utc' => 'UTC',
'none' => t('No time zone conversion'),
);
}

View File

@@ -0,0 +1,81 @@
(function ($) {
Drupal.behaviors.dateAdmin = {};
Drupal.behaviors.dateAdmin.attach = function (context, settings) {
// Remove timezone handling options for fields without hours granularity.
var $hour = $('#edit-field-settings-granularity-hour').once('date-admin');
if ($hour.length) {
new Drupal.dateAdmin.TimezoneHandler($hour);
}
};
Drupal.dateAdmin = {};
/**
* Constructor for the TimezoneHandler object.
*
* This object is responsible for showing the timezone handling options dropdown
* when the user has chosen to collect hours as part of the date field, and
* hiding it otherwise.
*/
Drupal.dateAdmin.TimezoneHandler = function ($checkbox) {
this.$checkbox = $checkbox;
this.$dropdown = $('#edit-field-settings-tz-handling');
this.$timezoneDiv = $('.form-item-field-settings-tz-handling');
// Store the initial value of the timezone handling dropdown.
this.storeTimezoneHandling();
// Toggle the timezone handling section when the user clicks the "Hour"
// checkbox.
this.$checkbox.bind('click', $.proxy(this.clickHandler, this));
// Trigger the click handler so that if the checkbox is unchecked on initial
// page load, the timezone handling section will be hidden.
this.clickHandler();
};
/**
* Event handler triggered when the user clicks the "Hour" checkbox.
*/
Drupal.dateAdmin.TimezoneHandler.prototype.clickHandler = function () {
if (this.$checkbox.is(':checked')) {
this.restoreTimezoneHandlingOptions();
}
else {
this.hideTimezoneHandlingOptions();
}
};
/**
* Hide the timezone handling options section of the form.
*/
Drupal.dateAdmin.TimezoneHandler.prototype.hideTimezoneHandlingOptions = function () {
this.storeTimezoneHandling();
this.$dropdown.val('none');
this.$timezoneDiv.hide();
};
/**
* Show the timezone handling options section of the form.
*/
Drupal.dateAdmin.TimezoneHandler.prototype.restoreTimezoneHandlingOptions = function () {
var val = this.getTimezoneHandling();
this.$dropdown.val(val);
this.$timezoneDiv.show();
};
/**
* Store the current value of the timezone handling dropdown.
*/
Drupal.dateAdmin.TimezoneHandler.prototype.storeTimezoneHandling = function () {
this._timezoneHandling = this.$dropdown.val();
};
/**
* Return the stored value of the timezone handling dropdown.
*/
Drupal.dateAdmin.TimezoneHandler.prototype.getTimezoneHandling = function () {
return this._timezoneHandling;
};
})(jQuery);

View File

@@ -0,0 +1,9 @@
Date All Day
This module provides the option to add an 'All Day' checkbox to toggle time on
and off for date fields. It also contains the theme that displays the 'All Day'
text on fields that have no time.
Additionally, this module serves as an example of how other modules can inject
new functionality into date fields using various hooks provided by Date and by
the Field API.

View File

@@ -0,0 +1,13 @@
name = Date All Day
description = Adds 'All Day' functionality to date fields, including an 'All Day' theme and 'All Day' checkboxes for the Date select and Date popup widgets.
dependencies[] = date_api
dependencies[] = date
package = Date/Time
core = 7.x
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,419 @@
<?php
/**
* @file
* Adds All Day functionality to the Date field.
*
* This module implements a number of hooks in the Date field and
* Date api element processing and to add an All Day checkbox to
* date widgets, and also adds an All Day theme and
* All Day information to the formatting.
*
* Keep in mind that the process hooks are fired from the top down,
* first date_combo, then the date_select or date_popup.
*
* Validation fires from the bottom up, first date_select and
* date_popup, then date_combo.
*/
/**
* Implements hook_theme().
*/
function date_all_day_theme() {
$themes = array(
'date_all_day' => array(
'variables' => array(
'field' => NULL,
'instance' => NULL,
'which' => NULL,
'date1' => NULL,
'date2' => NULL,
'format' => NULL,
'entity_type' => NULL,
'entity' => NULL,
'view' => NULL
)
),
'date_all_day_label' => array(
'variables' => array()
),
);
return $themes;
}
/**
* Implements hook_date_formatter_dates_alter().
*
* This allows us to alter the $dates array created
* by date_formatter_process.
*/
function date_all_day_date_formatter_dates_alter(&$dates, $context) {
$field = $context['field'];
$instance = $context['instance'];
$format = $context['format'];
$entity_type = $context['entity_type'];
$entity = $context['entity'];
$date1 = $dates['value']['local']['object'];
$date2 = $dates['value2']['local']['object'];
$is_all_day = date_all_day_field($field, $instance, $date1, $date2);
$all_day1 = '';
$all_day2 = '';
if ($format != 'format_interval' && $is_all_day) {
$all_day1 = theme('date_all_day', array(
'field' => $field,
'instance' => $instance,
'which' => 'date1',
'date1' => $date1,
'date2' => $date2,
'format' => $format,
'entity_type' => $entity_type,
'entity' => $entity));
$all_day2 = theme('date_all_day', array(
'field' => $field,
'instance' => $instance,
'which' => 'date2',
'date1' => $date1,
'date2' => $date2,
'format' => $format,
'entity_type' => $entity_type,
'entity' => $entity));
$dates['value']['formatted_time'] = theme('date_all_day_label');
$dates['value2']['formatted_time'] = theme('date_all_day_label');
$dates['value']['formatted'] = $all_day1;
$dates['value2']['formatted'] = $all_day2;
}
}
/**
* Adjust start/end date format to account for 'all day' .
*
* @param array $field, the field definition for this date field.
* @param string $which, which value to return, 'date1' or 'date2' .
* @param object $date1, a date/time object for the 'start' date.
* @param object $date2, a date/time object for the 'end' date.
* @param string $format
* @param object $entity, the node this date comes from (may be incomplete, always contains nid).
* @param object $view, the view this node comes from, if applicable.
* @return formatted date.
*/
function theme_date_all_day($vars) {
$field = $vars['field'];
$instance = $vars['instance'];
$which = $vars['which'];
$date1 = $vars['date1'];
$date2 = $vars['date2'];
$format = $vars['format'];
$entity = $vars['entity'];
$view = !empty($vars['view']) ? $vars['view'] : NULL;
if (empty($date1) || !is_object($date1) || $format == 'format_interval') {
return;
}
if (empty($date2)) {
$date2 = $date1;
}
$suffix = '';
if (!date_has_time($field['settings']['granularity'])) {
$format = date_limit_format($format, array('year', 'month', 'day'));
}
else {
$format_granularity = date_format_order($format);
$format_has_time = FALSE;
if (in_array('hour', $format_granularity)) {
$format_has_time = TRUE;
}
$all_day = date_all_day_field($field, $instance, $date1, $date2);
if ($all_day && $format_has_time) {
$format = date_limit_format($format, array('year', 'month', 'day'));
$suffix = ' ' . theme('date_all_day_label');
}
}
return trim(date_format_date($$which, 'custom', $format) . $suffix);
}
/**
* Theme the way an 'all day' label will look.
*/
function theme_date_all_day_label() {
return '(' . t('All day', array(), array('context' => 'datetime')) .')';
}
/**
* Determine if a Start/End date combination qualify as 'All day'.
*
* @param array $field, the field definition for this date field.
* @param object $date1, a date/time object for the 'Start' date.
* @param object $date2, a date/time object for the 'End' date.
* @return TRUE or FALSE.
*/
function date_all_day_field($field, $instance, $date1, $date2 = NULL) {
if (empty($date1) || !is_object($date1)) {
return FALSE;
}
elseif (!date_has_time($field['settings']['granularity'])) {
return TRUE;
}
if (empty($date2)) {
$date2 = $date1;
}
$granularity = date_granularity_precision($field['settings']['granularity']);
$increment = isset($instance['widget']['settings']['increment']) ? $instance['widget']['settings']['increment'] : 1;
return date_is_all_day(date_format($date1, DATE_FORMAT_DATETIME), date_format($date2, DATE_FORMAT_DATETIME), $granularity, $increment);
}
/**
* Implements hook_date_combo_process_alter().
*
* This hook lets us make changes to the date_combo element.
*/
function date_all_day_date_combo_process_alter(&$element, &$form_state, $context) {
$field = $context['field'];
$instance = $context['instance'];
// Add the all_day checkbox to the combo element.
if (!empty($instance['widget']['settings']['display_all_day'])) {
$parents = $element['#parents'];
$first_parent = array_shift($parents);
$all_day_id = $first_parent . '[' . implode('][', $parents) . '][all_day]';;
foreach (array('value', 'value2') as $key) {
if (array_key_exists($key, $element)) {
$element[$key]['#date_all_day_id'] = $all_day_id;
}
}
$from = $element['#default_value']['value'];
$to = !empty($element['#default_value']['value2']) ? $element['#default_value']['value2'] : $element['#default_value']['value'];
$date_is_all_day = date_is_all_day($from, $to);
$all_day = !empty($form_state['values']['all_day']) || $date_is_all_day;
$element['all_day'] = array(
'#title' => t('All Day'),
'#type' => 'checkbox',
'#default_value' => $all_day,
'#weight' => -21,
'#prefix' => '<div class="date-float">',
'#suffix' => '</div>',
);
}
// Set all_day to 0 for all other date fields.
else {
$form['all_day']['#type'] = 'hidden';
$form['all_day']['#value'] = 0;
}
}
/**
* Implements hook_date_text_process_alter().
*
* This hook lets us make changes to the date_select widget.
*/
function date_all_day_date_text_process_alter(&$element, &$form_state, $context) {
$all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : '';
if ($all_day_id != '') {
// All Day handling on text dates works only if the user leaves the time out of the input value.
// There is no element to hide or show.
}
}
/**
* Implements hook_date_select_process_alter().
*
* This hook lets us make changes to the date_select widget.
*/
function date_all_day_date_select_process_alter(&$element, &$form_state, $context) {
// Hide or show this element in reaction to the all_day status for this element.
$all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : '';
if ($all_day_id != '') {
foreach(array('hour', 'minute', 'second', 'ampm') as $field) {
if (array_key_exists($field, $element)) {
$element[$field]['#states'] = array(
'visible' => array(
'input[name="' . $all_day_id . '"]' => array('checked' => FALSE),
));
}
}
}
}
/**
* Implements hook_date_popup_process_alter().
*
* This hook lets us make changes to the date_popup element.
*/
function date_all_day_date_popup_process_alter(&$element, &$form_state, $context) {
// Hide or show this element in reaction to the all_day status for this element.
$all_day_id = !empty($element['#date_all_day_id']) ? $element['#date_all_day_id'] : '';
if ($all_day_id != '' && array_key_exists('time', $element)) {
$element['time']['#states'] = array(
'visible' => array(
'input[name="' . $all_day_id . '"]' => array('checked' => FALSE),
));
}
}
/**
* Implements hook_date_select_pre_validate_alter().
*
* This hook lets us alter the element or the form_state before the rest
* of the date_select validation gets fired.
*/
function date_all_day_date_text_pre_validate_alter(&$element, &$form_state, &$input) {
// Let Date module massage the format for all day values so they will pass validation.
// The All day flag, if used, actually exists on the parent element.
date_all_day_value($element, $form_state);
}
/**
* Implements hook_date_select_pre_validate_alter().
*
* This hook lets us alter the element or the form_state before the rest
* of the date_select validation gets fired.
*/
function date_all_day_date_select_pre_validate_alter(&$element, &$form_state, &$input) {
// Let Date module massage the format for all day values so they will pass validation.
// The All day flag, if used, actually exists on the parent element.
date_all_day_value($element, $form_state);
}
/**
* Implements hook_date_select_pre_validate_alter().
*
* This hook lets us alter the element or the form_state before the rest
* of the date_popup validation gets fired.
*/
function date_all_day_date_popup_pre_validate_alter(&$element, &$form_state, &$input) {
// Let Date module massage the format for all day values so they will pass validation.
// The All day flag, if used, actually exists on the parent element.
date_all_day_value($element, $form_state);
}
/**
* A helper function to check if the all day flag is set on the parent of an
* element, and adjust the date_format accordingly so the missing time will
* not cause validation errors.
*/
function date_all_day_value(&$element, $form_state) {
if (!empty($element['#date_all_day_id'])) {
$parents = $element['#parents'];
array_pop($parents);
$parent_element = drupal_array_get_nested_value($form_state['values'], $parents);
if (!empty($parent_element) && !empty($parent_element['all_day'])) {
$element['#date_format'] = date_part_format('date', $element['#date_format']);
return $parent_element['all_day'];
}
}
return FALSE;
}
/**
* Implements hook_date_combo_pre_validate_alter().
*
* This hook lets us alter the element or the form_state before the rest
* of the date_combo validation gets fired.
*/
function date_all_day_date_combo_pre_validate_alter(&$element, &$form_state, $context) {
if (!empty($context['item']['all_day'])) {
$field = $context['field'];
// If we have an all day flag on this date and the time is empty,
// change the format to match the input value so we don't get validation errors.
$element['#date_is_all_day'] = TRUE;
$element['value']['#date_format'] = date_part_format('date', $element['value']['#date_format']);
if (!empty($field['settings']['todate'])) {
$element['value2']['#date_format'] = date_part_format('date', $element['value2']['#date_format']);
}
}
}
/**
* Implements hook_date_combo_validate_date_start_alter().
*
* This hook lets us alter the local date objects created by the date_combo validation
* before they are converted back to the database timezone and stored.
*/
function date_all_day_date_combo_validate_date_start_alter(&$date, &$form_state, $context) {
// If this is an 'All day' value, set the time to midnight.
if (!empty($context['element']['#date_is_all_day'])) {
$date->setTime(0, 0, 0);
}
}
/**
* Implements hook_date_combo_validate_date_end_alter().
*
* This hook lets us alter the local date objects created by the date_combo validation
* before they are converted back to the database timezone and stored.
*/
function date_all_day_date_combo_validate_date_end_alter(&$date, &$form_state, $context) {
// If this is an 'All day' value, set the time to midnight.
if (!empty($context['element']['#date_is_all_day'])) {
$date->setTime(0, 0, 0);
}
}
/**
* Implements hook_field_widget_info_alter().
*
* This Field API hook lets us add a new setting to the widgets.
*/
function date_all_day_field_widget_info_alter(&$info) {
// Add a setting to a widget type.
$info['date_select']['settings'] += array(
'display_all_day' => 0,
);
$info['date_text']['settings'] += array(
'display_all_day' => 0,
);
if (module_exists('date_popup')) {
$info['date_popup']['settings'] += array(
'display_all_day' => 0,
);
}
}
/**
* Implements hook_date_field_widget_settings_form_alter().
*
* This hook lets us alter the field settings form by adding a place
* to set the value added above.
*/
function date_all_day_date_field_widget_settings_form_alter(&$form, $context) {
$field = $context['field'];
$instance = $context['instance'];
$settings = $instance['widget']['settings'];
$form['display_all_day'] = array(
'#type' => 'checkbox',
'#title' => t('Display all day checkbox'),
'#default_value' => $settings['display_all_day'],
'#description' => t("Determines whether to display the 'All Day' checkbox to the user."),
'#weight' => 8,
'#fieldset' => 'date_format',
);
// Hide the option to use the all day checkbox for dates with no time.
if (!date_has_time($field['settings']['granularity'])) {
$form['display_all_day']['#type'] = 'hidden';
$form['display_all_day']['#value'] = 0;
}
}

View File

@@ -0,0 +1,27 @@
.container-inline-date > .form-item {
margin-left: 0.5em;
margin-right: 0;
}
.container-inline-date .form-item .form-item {
float: right;
}
.container-inline-date .form-item input,
.container-inline-date .form-item select,
.container-inline-date .form-item option {
margin-left: 5px;
margin-right: 0;
}
.container-inline-date .date-spacer {
margin-left: 0;
margin-right: -5px;
}
.form-type-date-select .form-type-select[class$=hour] {
margin-right: .75em;
}
#edit-field-settings-granularity .form-type-checkbox {
margin-left: .6em;
}

View File

@@ -0,0 +1,188 @@
/**
* @file
* Main stylesheet for Date module.
*/
/* Force start/end dates to float using inline-block, where it works, otherwise inline. */
.container-inline-date {
clear: both;
}
.container-inline-date .form-item {
float: none;
margin: 0;
padding: 0;
}
.container-inline-date > .form-item {
display: inline-block;
margin-right: 0.5em; /* LTR */
margin-bottom: 10px;
vertical-align: top;
}
.container-inline-date .form-item .form-item {
float: left; /* LTR */
}
.container-inline-date .form-item,
.container-inline-date .form-item input {
width: auto;
}
.container-inline-date .description {
clear: both;
}
.container-inline-date .form-item input,
.container-inline-date .form-item select,
.container-inline-date .form-item option {
margin-right: 5px; /* LTR */
}
.container-inline-date .date-spacer {
margin-left: -5px; /* LTR */
}
.views-right-60 .container-inline-date div {
margin: 0;
padding: 0;
}
.container-inline-date .date-timezone .form-item {
clear: both;
float: none;
width: auto;
}
/* The exposed Views form doesn't need some of these styles */
.container-inline-date .date-padding {
padding: 10px;
float: left;
}
.views-exposed-form .container-inline-date .date-padding {
padding: 0;
}
/* Fixes for date popup css so it will behave in Drupal */
#calendar_div,
#calendar_div td,
#calendar_div th {
margin: 0;
padding: 0;
}
#calendar_div,
.calendar_control,
.calendar_links,
.calendar_header,
.calendar {
border-collapse: separate;
margin: 0;
width: 185px;
}
.calendar td {
padding: 0;
}
/* formatting for start/end dates in nodes and views */
span.date-display-single {
}
span.date-display-start {
}
span.date-display-end {
}
.date-prefix-inline {
display: inline-block;
}
.date-clear {
clear: both;
display: block;
float: none;
}
.date-no-float {
clear: both;
float: none;
width: 98%;
}
.date-float {
clear: none;
float: left;
width: auto;
}
/* Add space between date option checkboxes ('All day' & 'Collect End Date') */
.date-float .form-type-checkbox{
padding-right: 1em;
}
/* Add space between the date and time portions of the date_select widget. */
.form-type-date-select .form-type-select[class$=hour] {
margin-left: .75em; /* LTR */
}
.date-container .date-format-delete {
float: left;
margin-top: 1.8em;
margin-left: 1.5em;
}
.date-container .date-format-name {
float: left;
}
.date-container .date-format-type {
float: left;
padding-left: 10px;
}
.date-container .select-container {
clear: left;
float: left;
}
/* Calendar day css */
div.date-calendar-day {
background: #F3F3F3;
border-top: 1px solid #EEE;
border-left: 1px solid #EEE;
border-right: 1px solid #BBB;
border-bottom: 1px solid #BBB;
color: #999;
float: left;
line-height: 1;
margin: 6px 10px 0 0;
text-align: center;
width: 40px;
}
div.date-calendar-day span {
display: block;
text-align: center;
}
div.date-calendar-day span.month {
background-color: #B5BEBE;
color: white;
font-size: .9em;
padding: 2px;
text-transform: uppercase;
}
div.date-calendar-day span.day {
font-size: 2em;
font-weight: bold;
}
div.date-calendar-day span.year {
font-size: .9em;
padding: 2px;
}
/* Admin styling */
.form-item.form-item-instance-widget-settings-input-format-custom,
.form-item.form-item-field-settings-enddate-required {
margin-left: 1.3em;
}
#edit-field-settings-granularity .form-type-checkbox {
margin-right: .6em; /* LTR */
}
.date-year-range-select {
margin-right: 1em;
}

View File

@@ -0,0 +1,126 @@
<?php
/**
* @file
* Administrative page callbacks for the date_api module.
*/
/**
* Create replacement values for deprecated timezone names.
*/
function _date_timezone_replacement($old) {
$replace = array(
'Brazil/Acre' => 'America/Rio_Branco',
'Brazil/DeNoronha' => 'America/Noronha',
'Brazil/East' => 'America/Recife',
'Brazil/West' => 'America/Manaus',
'Canada/Atlantic' => 'America/Halifax',
'Canada/Central' => 'America/Winnipeg',
'Canada/East-Saskatchewan' => 'America/Regina',
'Canada/Eastern' => 'America/Toronto',
'Canada/Mountain' => 'America/Edmonton',
'Canada/Newfoundland' => 'America/St_Johns',
'Canada/Pacific' => 'America/Vancouver',
'Canada/Saskatchewan' => 'America/Regina',
'Canada/Yukon' => 'America/Whitehorse',
'CET' => 'Europe/Berlin',
'Chile/Continental' => 'America/Santiago',
'Chile/EasterIsland' => 'Pacific/Easter',
'CST6CDT' => 'America/Chicago',
'Cuba' => 'America/Havana',
'EET' => 'Europe/Bucharest',
'Egypt' => 'Africa/Cairo',
'Eire' => 'Europe/Belfast',
'EST' => 'America/New_York',
'EST5EDT' => 'America/New_York',
'GB' => 'Europe/London',
'GB-Eire' => 'Europe/Belfast',
'Etc/GMT' => 'UTC',
'Etc/GMT+0' => 'UTC',
'Etc/GMT+1' => 'UTC',
'Etc/GMT+10' => 'UTC',
'Etc/GMT+11' => 'UTC',
'Etc/GMT+12' => 'UTC',
'Etc/GMT+2' => 'UTC',
'Etc/GMT+3' => 'UTC',
'Etc/GMT+4' => 'UTC',
'Etc/GMT+5' => 'UTC',
'Etc/GMT+6' => 'UTC',
'Etc/GMT+7' => 'UTC',
'Etc/GMT+8' => 'UTC',
'Etc/GMT+9' => 'UTC',
'Etc/GMT-0' => 'UTC',
'Etc/GMT-1' => 'UTC',
'Etc/GMT-10' => 'UTC',
'Etc/GMT-11' => 'UTC',
'Etc/GMT-12' => 'UTC',
'Etc/GMT-13' => 'UTC',
'Etc/GMT-14' => 'UTC',
'Etc/GMT-2' => 'UTC',
'Etc/GMT-3' => 'UTC',
'Etc/GMT-4' => 'UTC',
'Etc/GMT-5' => 'UTC',
'Etc/GMT-6' => 'UTC',
'Etc/GMT-7' => 'UTC',
'Etc/GMT-8' => 'UTC',
'Etc/GMT-9' => 'UTC',
'Etc/GMT0' => 'UTC',
'Etc/Greenwich' => 'UTC',
'Etc/UCT' => 'UTC',
'Etc/Universal' => 'UTC',
'Etc/UTC' => 'UTC',
'Etc/Zulu' => 'UTC',
'Factory' => 'UTC',
'GMT' => 'UTC',
'GMT+0' => 'UTC',
'GMT-0' => 'UTC',
'GMT0' => 'UTC',
'Hongkong' => 'Asia/Hong_Kong',
'HST' => 'Pacific/Honolulu',
'Iceland' => 'Atlantic/Reykjavik',
'Iran' => 'Asia/Tehran',
'Israel' => 'Asia/Tel_Aviv',
'Jamaica' => 'America/Jamaica',
'Japan' => 'Asia/Tokyo',
'Kwajalein' => 'Pacific/Kwajalein',
'Libya' => 'Africa/Tunis',
'MET' => 'Europe/Budapest',
'Mexico/BajaNorte' => 'America/Tijuana',
'Mexico/BajaSur' => 'America/Mazatlan',
'Mexico/General' => 'America/Mexico_City',
'MST' => 'America/Boise',
'MST7MDT' => 'America/Boise',
'Navajo' => 'America/Phoenix',
'NZ' => 'Pacific/Auckland',
'NZ-CHAT' => 'Pacific/Chatham',
'Poland' => 'Europe/Warsaw',
'Portugal' => 'Europe/Lisbon',
'PRC' => 'Asia/Chongqing',
'PST8PDT' => 'America/Los_Angeles',
'ROC' => 'Asia/Taipei',
'ROK' => 'Asia/Seoul',
'Singapore' => 'Asia/Singapore',
'Turkey' => 'Europe/Istanbul',
'US/Alaska' => 'America/Anchorage',
'US/Aleutian' => 'America/Adak',
'US/Arizona' => 'America/Phoenix',
'US/Central' => 'America/Chicago',
'US/East-Indiana' => 'America/Indianapolis',
'US/Eastern' => 'America/New_York',
'US/Hawaii' => 'Pacific/Honolulu',
'US/Indiana-Starke' => 'America/Indiana/Knox',
'US/Michigan' => 'America/Detroit',
'US/Mountain' => 'America/Boise',
'US/Pacific' => 'America/Los_Angeles',
'US/Pacific-New' => 'America/Los_Angeles',
'US/Samoa' => 'Pacific/Samoa',
'W-SU' => 'Europe/Moscow',
'WET' => 'Europe/Paris',
);
if (array_key_exists($old, $replace)) {
return $replace[$old];
}
else {
return $old;
}
}

View File

@@ -0,0 +1,17 @@
name = Date API
description = A Date API that can be used by other modules.
package = Date/Time
core = 7.x
php = 5.2
stylesheets[all][] = date.css
files[] = date_api.module
files[] = date_api_sql.inc
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,252 @@
<?php
/**
* @file
* Install, update and uninstall functions for the date_api module.
*/
/**
* Helper function for setting Date variables.
*/
function date_api_set_variables() {
// Set absolute minimum and maximum year for dates on this site.
// There is actually no maximum and minimum year in PHP 5, but a date with
// a year less than 0 would result in negative ISO and DATETIME dates,
// like -1250-01-01T00:00:00, which probably won't make sense or work
// correctly anywhere.
// The odd construct of using variable_get() instead of variable_set()
// is so we don't accidentally write over an existing value. If
// no value is set, variable_get() will set it.
variable_get('date_max_year', 4000);
variable_get('date_min_year', 1);
variable_get('date_php_min_year', 1901);
// Set an API version in a way that other modules can test for compatibility.
variable_set('date_api_version', '7.2');
}
/**
* Implements hook_requirements().
*/
function date_api_requirements($phase) {
$requirements = array();
if ($phase == 'runtime') {
$t = get_t();
module_load_include('module', 'date_api');
$messages = date_api_status();
$error_messages = !empty($messages['errors']) ? $messages['errors'] : array();
$success_messages = !empty($messages['success']) ? $messages['success'] : array();
if (!empty($error_messages)) {
$requirements['date'] = array(
'title' => $t('Date API'),
'value' => $t('Missing system date settings'),
'description' => implode(' ', array_merge($error_messages, $success_messages)),
'severity' => REQUIREMENT_ERROR,
);
}
else {
$requirements['date'] = array(
'title' => $t('Date API'),
'value' => $t('System date settings'),
'description' => implode(' ', $success_messages),
);
}
}
return $requirements;
}
/**
* Implements hook_install().
*/
function date_api_install() {
// Only set the message if Drupal itself is already installed.
if (variable_get('install_task') == 'done') {
// Ensure translations don't break at install time.
$t = get_t();
// date_api_set_variables can install date_timezone. The
// date_timezone_install() function does a module_enable('date_api'). This
// means that date_api_enable() can be called before date_api_install()
// finishes! So the date_api schema needs to be installed before this line!
date_api_set_variables();
$message = $t('The Date API requires that you set up the <a href="@regional_settings">site timezone and first day of week settings</a> and the <a href="@regional_date_time">date format settings</a> to function correctly.', array('@regional_settings' => url('admin/config/regional/settings'), '@regional_date_time' => url('admin/config/regional/date-time')));
drupal_set_message(filter_xss_admin($message), 'warning');
}
}
/**
* Implements hook_enable().
*/
function date_api_enable() {
date_api_set_variables();
}
/**
* Implements hook_uninstall().
*/
function date_api_uninstall() {
cache_clear_all('date_timezone_identifiers_list', 'cache');
$variables = array(
'date_api_version',
'date_min_year',
'date_max_year',
'date_php_min_year',
'date_db_tz_support',
'date_api_use_iso8601',
);
foreach ($variables as $variable) {
variable_del($variable);
}
if (db_table_exists('views_display')) {
$displays = array(
'date_nav',
);
db_query("DELETE FROM {views_display} WHERE display_plugin IN ('" . implode("','", $displays) . "')");
db_query("DELETE FROM {cache_views}");
}
}
/**
* Implements hook_update_last_removed().
*/
function date_api_update_last_removed() {
return 6005;
}
/**
* Move old date format data to new date format tables, and delete the old
* tables. Insert only values that don't already exist in the new tables, in
* case new version of those custom values have already been created.
*/
function date_api_update_7000() {
// Move format data from the old 'date_format_types' table to the new
// 'date_format_type' table.
if (db_table_exists('date_format_types')) {
// Find all the custom entries in the D6 table.
$result = db_select('date_format_types', 'old')
->fields('old', array('type', 'title', 'locked'))
->condition('locked', 0)
->execute()
->fetchAll(PDO::FETCH_ASSOC);
// Iterate over all the old values.
foreach ($result as $row) {
// See if this value already exists in the new table
// (it might have been added manually before this update got run).
$count = db_select('date_format_type', 'new')
->condition('type', $row['type'])
->countQuery()
->execute()
->fetchField();
// If the value is missing, insert it.
// Do nothing if it already exists, assume the value in the
// new table trumps the old values.
if (empty($count)) {
db_insert('date_format_type')
->fields(array(
'type' => $row['type'],
'title' => $row['title'],
'locked' => $row['locked'],
))
->execute();
}
}
// Drop the old table.
db_drop_table('date_format_types');
}
// Move format data from the old 'date_formats' table (which was renamed to
// 'd6_date_formats') to the new 'date_formats' table.
if (db_table_exists('d6_date_formats')) {
// Find all the custom entries in the D6 table.
$result = db_select('d6_date_formats', 'old')
->fields('old', array('format', 'type', 'locked'))
->condition('type', 'custom')
->execute()
->fetchAll(PDO::FETCH_ASSOC);
// Iterate over all the old values.
foreach ($result as $row) {
// See if this value already exists in the new table (it might have been
// added manually before this update got run).
$count = db_select('date_formats', 'new')
->condition('format', $row['format'])
->condition('type', $row['type'])
->countQuery()
->execute()
->fetchField();
// If the value is missing, insert it. Do nothing if it already exists,
// assume the value in the new table trumps the old values.
if (empty($count)) {
db_insert('date_formats')
->fields(array(
'format' => $row['format'],
'type' => $row['type'],
'locked' => $row['locked'],
))
->execute();
}
}
// Drop the old table.
db_drop_table('d6_date_formats');
}
// Move format data from the old 'date_format_locale' table (which was renamed
// to 'd6_date_format_locale') to the new 'date_format_locale' table.
if (db_table_exists('d6_date_format_locale')) {
// Find all the custom entries in the D6 table.
$result = db_select('d6_date_format_locale', 'old')
->fields('old', array('format', 'type', 'language'))
->condition('type', 'custom')
->execute()
->fetchAll(PDO::FETCH_ASSOC);
// Iterate over all the old values.
foreach ($result as $row) {
// See if this value already exists in the new table (it might have been
// added manually before this update got run).
$count = db_select('date_format_locale', 'new')
->condition('format', $row['format'])
->condition('type', $row['type'])
->condition('language', $row['language'])
->countQuery()
->execute()
->fetchField();
// If the value is missing, insert it.
// Do nothing if it already exists, assume the value in the
// new table trumps the old values.
if (empty($count)) {
db_insert('date_format_locale')
->fields(array(
'format' => $row['format'],
'type' => $row['type'],
'language' => $row['language'],
))
->execute();
}
}
// Drop the old table.
db_drop_table('d6_date_format_locale');
}
}
/**
* Drop D6 timezone_name field on {users} after upgrading to D7.
*/
function date_api_update_7001() {
if (db_field_exists('users', 'timezone_name')) {
db_drop_field('users', 'timezone_name');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,752 @@
<?php
/**
* @file
* Date API elements themes and validation.
* This file is only included during the edit process to reduce memory usage.
*/
/**
* Implements hook_element_info().
*
* Parameters for date form elements, designed to have sane defaults so any
* or all can be omitted.
*
* Fill the element #default_value with a date in datetime format,
* (YYYY-MM-DD HH:MM:SS), adjusted to the proper local timezone.
*
* NOTE - Converting a date stored in the database from UTC to the local zone
* and converting it back to UTC before storing it is not handled by this
* element and must be done in pre-form and post-form processing!!
*
* The date_select element will create a collection of form elements, with a
* separate select or textfield for each date part. The whole collection will
* get reformatted back to a date value of the requested type during validation.
*
* The date_text element will create a textfield that can contain a whole
* date or any part of a date as text. The user input value will be re-formatted
* back into a date value of the requested type during validation.
*
* The date_timezone element will create a drop-down selector to pick a
* timezone name.
*
* The date_year_range element will create two textfields (for users with
* JavaScript enabled they will appear as drop-down selectors with an option
* for custom text entry) to pick a range of years that will be passed to form
* submit handlers as a single string (e.g., -3:+3).
*
* #date_timezone
* The local timezone to be used to create this date.
*
* #date_format
* A format string that describes the format and order of date parts to
* display in the edit form for this element. This makes it possible
* to show date parts in a custom order, or to leave some of them out.
* Be sure to add 'A' or 'a' to get an am/pm selector. Defaults to the
* short site default format.
*
* #date_label_position
* Handling option for date part labels, like 'Year', 'Month', and 'Day',
* can be 'above' the date part, 'within' it, or 'none', default is 'above' .
* The 'within' option shows the label as the first option in a select list
* or the default value for an empty textfield, taking up less screen space.
*
* #date_increment
* Increment minutes and seconds by this amount, default is 1.
*
* #date_year_range
* The number of years to go back and forward in a year selector,
* default is -3:+3 (3 back and 3 forward).
*
* #date_text_parts
* Array of date parts that should use textfields instead of selects
* i.e. array('year') will format the year as a textfield and other
* date parts as drop-down selects.
*/
function _date_api_element_info() {
$date_base = array(
'#input' => TRUE, '#tree' => TRUE,
'#date_timezone' => date_default_timezone(),
'#date_flexible' => 0,
'#date_format' => variable_get('date_format_short', 'm/d/Y - H:i'),
'#date_text_parts' => array(),
'#date_increment' => 1,
'#date_year_range' => '-3:+3',
'#date_label_position' => 'above',
);
if (module_exists('ctools')) {
$date_base['#pre_render'] = array('ctools_dependent_pre_render');
}
$type['date_select'] = array_merge($date_base, array(
'#process' => array('date_select_element_process'),
'#theme_wrappers' => array('date_select'),
'#value_callback' => 'date_select_element_value_callback',
));
$type['date_text'] = array_merge($date_base, array(
'#process' => array('date_text_element_process'),
'#theme_wrappers' => array('date_text'),
'#value_callback' => 'date_text_element_value_callback',
));
$type['date_timezone'] = array(
'#input' => TRUE, '#tree' => TRUE,
'#process' => array('date_timezone_element_process'),
'#theme_wrappers' => array('date_text'),
'#value_callback' => 'date_timezone_element_value_callback',
);
$type['date_year_range'] = array(
'#input' => TRUE,
'#process' => array('date_year_range_element_process'),
'#value_callback' => 'date_year_range_element_value_callback',
'#element_validate' => array('date_year_range_validate'),
);
return $type;
}
/**
* Create a date object from a datetime string value.
*/
function date_default_date($element) {
$granularity = date_format_order($element['#date_format']);
$default_value = $element['#default_value'];
$format = DATE_FORMAT_DATETIME;
// The text and popup widgets might return less than a full datetime string.
if (strlen($element['#default_value']) < 19) {
switch (strlen($element['#default_value'])) {
case 16:
$format = 'Y-m-d H:i';
break;
case 13:
$format = 'Y-m-d H';
break;
case 10:
$format = 'Y-m-d';
break;
case 7:
$format = 'Y-m';
break;
case 4:
$format = 'Y';
break;
}
}
$date = new DateObject($default_value, $element['#date_timezone'], $format);
if (is_object($date)) {
$date->limitGranularity($granularity);
if ($date->validGranularity($granularity, $element['#date_flexible'])) {
date_increment_round($date, $element['#date_increment']);
}
return $date;
}
return NULL;
}
/**
* Process callback which creates a date_year_range form element.
*/
function date_year_range_element_process($element, &$form_state, $form) {
// Year range is stored in the -3:+3 format, but collected as two separate
// textfields.
$element['years_back'] = array(
'#type' => 'textfield',
'#title' => t('Starting year'),
'#default_value' => $element['#value']['years_back'],
'#size' => 10,
'#maxsize' => 10,
'#attributes' => array('class' => array('select-list-with-custom-option', 'back')),
'#description' => t('Enter a relative value (-9, +9) or an absolute year such as 2015.'),
);
$element['years_forward'] = array(
'#type' => 'textfield',
'#title' => t('Ending year'),
'#default_value' => $element['#value']['years_forward'],
'#size' => 10,
'#maxsize' => 10,
'#attributes' => array('class' => array('select-list-with-custom-option', 'forward')),
'#description' => t('Enter a relative value (-9, +9) or an absolute year such as 2015.'),
);
$element['#tree'] = TRUE;
$element['#attached']['js'][] = drupal_get_path('module', 'date_api') . '/date_year_range.js';
$context = array(
'form' => $form,
);
drupal_alter('date_year_range_process', $element, $form_state, $context);
return $element;
}
/**
* Element value callback for the date_year_range form element.
*/
function date_year_range_element_value_callback($element, $input = FALSE, &$form_state = array()) {
// Convert the element's default value from a string to an array (to match
// what we will get from the two textfields when the form is submitted).
if ($input === FALSE) {
list($years_back, $years_forward) = explode(':', $element['#default_value']);
return array(
'years_back' => $years_back,
'years_forward' => $years_forward,
);
}
}
/**
* Element validation function for the date_year_range form element.
*/
function date_year_range_validate(&$element, &$form_state) {
// Recombine the two submitted form values into the -3:+3 format we will
// validate and save.
$year_range_submitted = drupal_array_get_nested_value($form_state['values'], $element['#parents']);
$year_range = $year_range_submitted['years_back'] . ':' . $year_range_submitted['years_forward'];
drupal_array_set_nested_value($form_state['values'], $element['#parents'], $year_range);
if (!date_range_valid($year_range)) {
form_error($element['years_back'], t('Starting year must be in the format -9, or an absolute year such as 1980.'));
form_error($element['years_forward'], t('Ending year must be in the format +9, or an absolute year such as 2030.'));
}
}
/**
* Element value callback for date_timezone element.
*/
function date_timezone_element_value_callback($element, $input = FALSE, &$form_state = array()) {
$return = '';
if ($input !== FALSE) {
$return = $input;
}
elseif (!empty($element['#default_value'])) {
$return = array('timezone' => $element['#default_value']);
}
return $return;
}
/**
* Creates a timezone form element.
*
* @param array $element
* The timezone form element.
*
* @return array
* the timezone form element
*/
function date_timezone_element_process($element, &$form_state, $form) {
if (date_hidden_element($element)) {
return $element;
}
$element['#tree'] = TRUE;
$label = theme('date_part_label_timezone', array('part_type' => 'select', 'element' => $element));
$element['timezone'] = array(
'#type' => 'select',
'#title' => $element['#date_label_position'] == 'above' ? $label : '',
'#options' => date_timezone_names($element['#required']),
'#value' => $element['#value'],
'#weight' => $element['#weight'],
'#required' => $element['#required'],
'#theme' => 'date_select_element',
'#theme_wrappers' => array('form_element'),
);
if (isset($element['#element_validate'])) {
array_push($element['#element_validate'], 'date_timezone_validate');
}
else {
$element['#element_validate'] = array('date_timezone_validate');
}
$context = array(
'form' => $form,
);
drupal_alter('date_timezone_process', $element, $form_state, $context);
return $element;
}
/**
* Validation for timezone input
*
* Move the timezone value from the nested field back to the original field.
*/
function date_timezone_validate($element, &$form_state) {
if (date_hidden_element($element)) {
return;
}
form_set_value($element, $element['#value']['timezone'], $form_state);
}
/**
* Element value callback for date_text element.
*/
function date_text_element_value_callback($element, $input = FALSE, &$form_state = array()) {
$return = array('date' => '');
$date = NULL;
// Normal input from submitting the form element.
// Check is_array() to skip the string input values created by Views pagers.
// Those string values, if present, should be interpreted as empty input.
if ($input != FALSE && is_array($input)) {
$return = $input;
$date = date_text_input_date($element, $input);
}
// No input? Try the default value.
elseif (!empty($element['#default_value'])) {
$date = date_default_date($element);
}
if (date_is_date($date)) {
$return['date'] = date_format_date($date, 'custom', $element['#date_format']);
}
return $return;
}
/**
* Text date input form.
*
* Display all or part of a date in a single textfield.
*
* The exact parts displayed in the field are those in #date_granularity.
* The display of each part comes from #date_format.
*
*/
function date_text_element_process($element, &$form_state, $form) {
if (date_hidden_element($element)) {
return $element;
}
$element['#tree'] = TRUE;
$element['#theme_wrappers'] = array('date_text');
$element['date']['#value'] = $element['#value']['date'];
$element['date']['#type'] = 'textfield';
$element['date']['#weight'] = !empty($element['date']['#weight']) ? $element['date']['#weight'] : $element['#weight'];
$element['date']['#attributes'] = array('class' => isset($element['#attributes']['class']) ? $element['#attributes']['class'] += array('date-date') : array('date-date'));
$now = date_example_date();
$element['date']['#description'] = ' ' . t('Format: @date', array('@date' => date_format_date(date_example_date(), 'custom', $element['#date_format'])));
$element['date']['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE;
// Keep the system from creating an error message for the sub-element.
// We'll set our own message on the parent element.
// $element['date']['#required'] = $element['#required'];
$element['date']['#theme'] = 'date_textfield_element';
if (isset($element['#element_validate'])) {
array_push($element['#element_validate'], 'date_text_validate');
}
else {
$element['#element_validate'] = array('date_text_validate');
}
if (!empty($element['#force_value'])) {
$element['date']['#value'] = $element['date']['#default_value'];
}
$context = array(
'form' => $form,
);
drupal_alter('date_text_process', $element, $form_state, $context);
return $element;
}
/**
* Validation for text input.
*
* When used as a Views widget, the validation step always gets triggered,
* even with no form submission. Before form submission $element['#value']
* contains a string, after submission it contains an array.
*
*/
function date_text_validate($element, &$form_state) {
if (date_hidden_element($element)) {
return;
}
if (is_string($element['#value'])) {
return;
}
$input_exists = NULL;
$input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
drupal_alter('date_text_pre_validate', $element, $form_state, $input);
$label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : '');
$date = date_text_input_date($element, $input);
// If the field has errors, display them.
// If something was input but there is no date, the date is invalid.
// If the field is empty and required, set error message and return.
$error_field = implode('][', $element['#parents']);
if (empty($date) || !empty($date->errors)) {
if (is_object($date) && !empty($date->errors)) {
$message = t('The value input for field %field is invalid:', array('%field' => $label));
$message .= '<br />' . implode('<br />', $date->errors);
form_set_error($error_field, $message);
return;
}
if (!empty($element['#required'])) {
$message = t('A valid date is required for %title.', array('%title' => $label));
form_set_error($error_field, $message);
return;
}
// Fall through, some other error.
if (!empty($input['date'])) {
form_error($element, t('%title is invalid.', array('%title' => $label)));
return;
}
}
$value = !empty($date) ? $date->format(DATE_FORMAT_DATETIME) : NULL;
form_set_value($element, $value, $form_state);
}
/**
* Helper function for creating a date object out of user input.
*/
function date_text_input_date($element, $input) {
if (empty($input) || !is_array($input) || !array_key_exists('date', $input) || empty($input['date'])) {
return NULL;
}
$granularity = date_format_order($element['#date_format']);
$date = new DateObject($input['date'], $element['#date_timezone'], $element['#date_format']);
if (is_object($date)) {
$date->limitGranularity($granularity);
if ($date->validGranularity($granularity, $element['#date_flexible'])) {
date_increment_round($date, $element['#date_increment']);
}
return $date;
}
return NULL;
}
/**
* Element value callback for date_select element.
*/
function date_select_element_value_callback($element, $input = FALSE, &$form_state = array()) {
$return = array('year' => '', 'month' => '', 'day' => '', 'hour' => '', 'minute' => '', 'second' => '');
$date = NULL;
if ($input !== FALSE) {
$return = $input;
$date = date_select_input_date($element, $input);
}
elseif (!empty($element['#default_value'])) {
$date = date_default_date($element);
}
$granularity = date_format_order($element['#date_format']);
$formats = array('year' => 'Y', 'month' => 'n', 'day' => 'j', 'hour' => 'H', 'minute' => 'i', 'second' => 's');
foreach ($granularity as $field) {
if ($field != 'timezone') {
$return[$field] = date_is_date($date) ? $date->format($formats[$field]) : '';
}
}
return $return;
}
/**
* Flexible date/time drop-down selector.
*
* Splits date into a collection of date and time sub-elements, one
* for each date part. Each sub-element can be either a textfield or a
* select, based on the value of ['#date_settings']['text_fields'].
*
* The exact parts displayed in the field are those in #date_granularity.
* The display of each part comes from ['#date_settings']['format'].
*
*/
function date_select_element_process($element, &$form_state, $form) {
if (date_hidden_element($element)) {
return $element;
}
$date = NULL;
$granularity = date_format_order($element['#date_format']);
if (is_array($element['#default_value'])) {
$date = date_select_input_date($element, $element['#default_value']);
}
elseif (!empty($element['#default_value'])) {
$date = date_default_date($element);
}
$element['#tree'] = TRUE;
$element['#theme_wrappers'] = array('date_select');
$element += (array) date_parts_element($element, $date, $element['#date_format']);
// Store a hidden value for all date parts not in the current display.
$granularity = date_format_order($element['#date_format']);
$formats = array('year' => 'Y', 'month' => 'n', 'day' => 'j', 'hour' => 'H', 'minute' => 'i', 'second' => 's');
foreach (date_nongranularity($granularity) as $field) {
if ($field != 'timezone') {
$element[$field] = array(
'#type' => 'value',
'#value' => 0,
);
}
}
if (isset($element['#element_validate'])) {
array_push($element['#element_validate'], 'date_select_validate');
}
else {
$element['#element_validate'] = array('date_select_validate');
}
$context = array(
'form' => $form,
);
drupal_alter('date_select_process', $element, $form_state, $context);
return $element;
}
/**
* Creates form elements for one or more date parts.
*
* Get the order of date elements from the provided format.
* If the format order omits any date parts in the granularity, alter the
* granularity array to match the format, then flip the $order array
* to get the position for each element. Then iterate through the
* elements and create a sub-form for each part.
*
* @param array $element
* The date element.
* @param object $date
* The date object.
* @param string $format
* A date format string.
*
* @return array
* The form array for the submitted date parts.
*/
function date_parts_element($element, $date, $format) {
$granularity = date_format_order($format);
$sub_element = array('#granularity' => $granularity);
$order = array_flip($granularity);
$hours_format = strpos(strtolower($element['#date_format']), 'a') ? 'g': 'G';
$month_function = strpos($element['#date_format'], 'F') !== FALSE ? 'date_month_names' : 'date_month_names_abbr';
$count = 0;
$increment = min(intval($element['#date_increment']), 1);
// Allow empty value as option if date is not required or there is no date.
$part_required = (bool) $element['#required'] && is_object($date);
foreach ($granularity as $field) {
$part_type = in_array($field, $element['#date_text_parts']) ? 'textfield' : 'select';
$sub_element[$field] = array(
'#weight' => $order[$field],
'#required' => $part_required,
'#attributes' => array('class' => isset($element['#attributes']['class']) ? $element['#attributes']['class'] += array('date-' . $field) : array('date-' . $field)),
'#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
);
switch ($field) {
case 'year':
$range = date_range_years($element['#date_year_range'], $date);
$min_year = $range[0];
$max_year = $range[1];
$sub_element[$field]['#default_value'] = is_object($date) ? $date->format('Y') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_years($min_year, $max_year, $part_required));
}
break;
case 'month':
$sub_element[$field]['#default_value'] = is_object($date) ? $date->format('n') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = $month_function($part_required);
}
break;
case 'day':
$sub_element[$field]['#default_value'] = is_object($date) ? $date->format('j') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_days($part_required));
}
break;
case 'hour':
$sub_element[$field]['#default_value'] = is_object($date) ? $date->format($hours_format) : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_hours($hours_format, $part_required));
}
$sub_element[$field]['#prefix'] = theme('date_part_hour_prefix', $element);
break;
case 'minute':
$sub_element[$field]['#default_value'] = is_object($date) ? $date->format('i') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_minutes('i', $part_required, $element['#date_increment']));
}
$sub_element[$field]['#prefix'] = theme('date_part_minsec_prefix', $element);
break;
case 'second':
$sub_element[$field]['#default_value'] = is_object($date) ? $date->format('s') : '';
if ($part_type == 'select') {
$sub_element[$field]['#options'] = drupal_map_assoc(date_seconds('s', $part_required, $element['#date_increment']));
}
$sub_element[$field]['#prefix'] = theme('date_part_minsec_prefix', $element);
break;
}
// Add handling for the date part label.
$label = theme('date_part_label_' . $field, array('part_type' => $part_type, 'element' => $element));
if (in_array($field, $element['#date_text_parts'])) {
$sub_element[$field]['#type'] = 'textfield';
$sub_element[$field]['#theme'] = 'date_textfield_element';
$sub_element[$field]['#size'] = 7;
if ($element['#date_label_position'] == 'within') {
if (!empty($sub_element[$field]['#options']) && is_array($sub_element[$field]['#options'])) {
$sub_element[$field]['#options'] = array(
'-' . $label => '-' . $label) + $sub_element[$field]['#options'];
}
if (empty($sub_element[$field]['#default_value'])) {
$sub_element[$field]['#default_value'] = '-' . $label;
}
}
elseif ($element['#date_label_position'] != 'none') {
$sub_element[$field]['#title'] = $label;
}
}
else {
$sub_element[$field]['#type'] = 'select';
$sub_element[$field]['#theme'] = 'date_select_element';
if ($element['#date_label_position'] == 'within') {
$sub_element[$field]['#options'] = array(
'' => '-' . $label) + $sub_element[$field]['#options'];
}
elseif ($element['#date_label_position'] != 'none') {
$sub_element[$field]['#title'] = $label;
}
}
}
// Views exposed filters are treated as submitted even if not,
// so force the #default value in that case. Make sure we set
// a default that is in the option list.
if (!empty($element['#force_value'])) {
$options = $sub_element[$field]['#options'];
$default = !empty($sub_element[$field]['#default_value']) ? $sub_element[$field]['#default_value'] : array_shift($options);
$sub_element[$field]['#value'] = $default;
}
if (($hours_format == 'g' || $hours_format == 'h') && date_has_time($granularity)) {
$sub_element['ampm'] = array(
'#type' => 'select',
'#theme' => 'date_select_element',
'#default_value' => is_object($date) ? (date_format($date, 'G') >= 12 ? 'pm' : 'am') : '',
'#options' => drupal_map_assoc(date_ampm($part_required)),
'#required' => $part_required,
'#weight' => 8,
'#attributes' => array('class' => array('date-ampm')),
);
if ($element['#date_label_position'] == 'within') {
$sub_element['ampm']['#options'] = array('' => '-' . theme('date_part_label_ampm', array('part_type' => 'ampm', 'eleement' => $element))) + $sub_element['ampm']['#options'];
}
elseif ($element['#date_label_position'] != 'none') {
$sub_element['ampm']['#title'] = theme('date_part_label_ampm', array('part_type' => 'ampm', 'element' => $element));
}
}
return $sub_element;
}
/**
* Validation function for date selector.
*
* When used as a Views widget, the validation step always gets triggered,
* even with no form submission. Before form submission $element['#value']
* contains a string, after submission it contains an array.
*/
function date_select_validate($element, &$form_state) {
if (date_hidden_element($element)) {
return;
}
if (is_string($element['#value'])) {
return;
}
$input_exists = NULL;
$input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
// Strip field labels out of the results.
foreach ($element['#value'] as $field => $field_value) {
if (substr($field_value, 0, 1) == '-') {
$input[$field] = '';
}
}
drupal_alter('date_select_pre_validate', $element, $form_state, $input);
$label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : '');
if (isset($input['ampm'])) {
if ($input['ampm'] == 'pm' && $input['hour'] < 12) {
$input['hour'] += 12;
}
elseif ($input['ampm'] == 'am' && $input['hour'] == 12) {
$input['hour'] -= 12;
}
}
unset($input['ampm']);
$date = date_select_input_date($element, $input);
// If the field has errors, display them.
$error_field = implode('][', $element['#parents']);
$entered = array_values(array_filter($input));
if (empty($date) || !empty($date->errors)) {
// The input created a date but it has errors.
if (is_object($date) && !empty($date->errors)) {
$message = t('The value input for field %field is invalid:', array('%field' => $label));
$message .= '<br />' . implode('<br />', $date->errors);
form_set_error($error_field, $message);
return;
}
// Nothing was entered but the date is required.
elseif (empty($entered) && $element['#required']) {
$message = t('A valid date is required for %title.', array('%title' => $label));
form_set_error($error_field, $message);
return;
}
// Something was input but it wasn't enough to create a valid date.
elseif (!empty($entered)) {
$message = t('The value input for field %field is invalid.', array('%field' => $label));
form_set_error($error_field, $message);
return;
}
}
$value = !empty($date) ? $date->format(DATE_FORMAT_DATETIME) : NULL;
form_set_value($element, $value, $form_state);
}
/**
* Helper function for creating a date object out of user input.
*/
function date_select_input_date($element, $input) {
// Was anything entered? If not, we have no date.
if (!is_array($input)) {
return NULL;
}
else {
$entered = array_values(array_filter($input));
if (empty($entered)) {
return NULL;
}
}
$granularity = date_format_order($element['#date_format']);
if (isset($input['ampm'])) {
if ($input['ampm'] == 'pm' && $input['hour'] < 12) {
$input['hour'] += 12;
}
elseif ($input['ampm'] == 'am' && $input['hour'] == 12) {
$input['hour'] -= 12;
}
}
unset($input['ampm']);
// Make the input match the granularity.
foreach (date_nongranularity($granularity) as $part) {
unset($input[$part]);
}
$date = new DateObject($input, $element['#date_timezone']);
if (is_object($date)) {
$date->limitGranularity($granularity);
if ($date->validGranularity($granularity, $element['#date_flexible'])) {
date_increment_round($date, $element['#date_increment']);
}
return $date;
}
return NULL;
}

View File

@@ -0,0 +1,803 @@
<?php
/**
* @file
* Parse iCal data.
*
* This file must be included when these functions are needed.
*/
/**
* Return an array of iCalendar information from an iCalendar file.
*
* No timezone adjustment is performed in the import since the timezone
* conversion needed will vary depending on whether the value is being
* imported into the database (when it needs to be converted to UTC), is being
* viewed on a site that has user-configurable timezones (when it needs to be
* converted to the user's timezone), if it needs to be converted to the
* site timezone, or if it is a date without a timezone which should not have
* any timezone conversion applied.
*
* Properties that have dates and times are converted to sub-arrays like:
* 'datetime' => date in YYYY-MM-DD HH:MM format, not timezone adjusted
* 'all_day' => whether this is an all-day event
* 'tz' => the timezone of the date, could be blank for absolute
* times that should get no timezone conversion.
*
* Exception dates can have muliple values and are returned as arrays
* like the above for each exception date.
*
* Most other properties are returned as PROPERTY => VALUE.
*
* Each item in the VCALENDAR will return an array like:
* [0] => Array (
* [TYPE] => VEVENT
* [UID] => 104
* [SUMMARY] => An example event
* [URL] => http://example.com/node/1
* [DTSTART] => Array (
* [datetime] => 1997-09-07 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [DTEND] => Array (
* [datetime] => 1997-09-07 11:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [RRULE] => Array (
* [FREQ] => Array (
* [0] => MONTHLY
* )
* [BYDAY] => Array (
* [0] => 1SU
* [1] => -1SU
* )
* )
* [EXDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* [RDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* )
*
* @todo
* figure out how to handle this if subgroups are nested,
* like a VALARM nested inside a VEVENT.
*
* @param string $filename
* Location (local or remote) of a valid iCalendar file.
*
* @return array
* An array with all the elements from the ical.
*/
function date_ical_import($filename) {
// Fetch the iCal data. If file is a URL, use drupal_http_request. fopen
// isn't always configured to allow network connections.
if (substr($filename, 0, 4) == 'http') {
// Fetch the ical data from the specified network location.
$icaldatafetch = drupal_http_request($filename);
// Check the return result.
if ($icaldatafetch->error) {
watchdog('date ical', 'HTTP Request Error importing %filename: @error', array('%filename' => $filename, '@error' => $icaldatafetch->error));
return FALSE;
}
// Break the return result into one array entry per lines.
$icaldatafolded = explode("\n", $icaldatafetch->data);
}
else {
$icaldatafolded = @file($filename, FILE_IGNORE_NEW_LINES);
if ($icaldatafolded === FALSE) {
watchdog('date ical', 'Failed to open file: %filename', array('%filename' => $filename));
return FALSE;
}
}
// Verify this is iCal data.
if (trim($icaldatafolded[0]) != 'BEGIN:VCALENDAR') {
watchdog('date ical', 'Invalid calendar file: %filename', array('%filename' => $filename));
return FALSE;
}
return date_ical_parse($icaldatafolded);
}
/**
* Returns an array of iCalendar information from an iCalendar file.
*
* As date_ical_import() but different param.
*
* @param array $icaldatafolded
* An array of lines from an ical feed.
*
* @return array
* An array with all the elements from the ical.
*/
function date_ical_parse($icaldatafolded = array()) {
$items = array();
// Verify this is iCal data.
if (trim($icaldatafolded[0]) != 'BEGIN:VCALENDAR') {
watchdog('date ical', 'Invalid calendar file.');
return FALSE;
}
// "Unfold" wrapped lines.
$icaldata = array();
foreach ($icaldatafolded as $line) {
$out = array();
// See if this looks like the beginning of a new property or value. If not,
// it is a continuation of the previous line. The regex is to ensure that
// wrapped QUOTED-PRINTABLE data is kept intact.
if (!preg_match('/([A-Z]+)[:;](.*)/', $line, $out)) {
// Trim up to 1 leading space from wrapped line per iCalendar standard.
$line = array_pop($icaldata) . (ltrim(substr($line, 0, 1)) . substr($line, 1));
}
$icaldata[] = $line;
}
unset($icaldatafolded);
// Parse the iCal information.
$parents = array();
$subgroups = array();
$vcal = '';
foreach ($icaldata as $line) {
$line = trim($line);
$vcal .= $line . "\n";
// Deal with begin/end tags separately.
if (preg_match('/(BEGIN|END):V(\S+)/', $line, $matches)) {
$closure = $matches[1];
$type = 'V' . $matches[2];
if ($closure == 'BEGIN') {
array_push($parents, $type);
array_push($subgroups, array());
}
elseif ($closure == 'END') {
end($subgroups);
$subgroup = &$subgroups[key($subgroups)];
switch ($type) {
case 'VCALENDAR':
if (prev($subgroups) == FALSE) {
$items[] = array_pop($subgroups);
}
else {
$parent[array_pop($parents)][] = array_pop($subgroups);
}
break;
// Add the timezones in with their index their TZID.
case 'VTIMEZONE':
$subgroup = end($subgroups);
$id = $subgroup['TZID'];
unset($subgroup['TZID']);
// Append this subgroup onto the one above it.
prev($subgroups);
$parent = &$subgroups[key($subgroups)];
$parent[$type][$id] = $subgroup;
array_pop($subgroups);
array_pop($parents);
break;
// Do some fun stuff with durations and all_day events and then append
// to parent.
case 'VEVENT':
case 'VALARM':
case 'VTODO':
case 'VJOURNAL':
case 'VVENUE':
case 'VFREEBUSY':
default:
// Can't be sure whether DTSTART is before or after DURATION, so
// parse DURATION at the end.
if (isset($subgroup['DURATION'])) {
date_ical_parse_duration($subgroup, 'DURATION');
}
// Add a top-level indication for the 'All day' condition. Leave it
// in the individual date components, too, so it is always available
// even when you are working with only a portion of the VEVENT
// array, like in Feed API parsers.
$subgroup['all_day'] = FALSE;
// iCal spec states 'The "DTEND" property for a "VEVENT" calendar
// component specifies the non-inclusive end of the event'. Adjust
// multi-day events to remove the extra day because the Date code
// assumes the end date is inclusive.
if (!empty($subgroup['DTEND']) && (!empty($subgroup['DTEND']['all_day']))) {
// Make the end date one day earlier.
$date = new DateObject ($subgroup['DTEND']['datetime'] . ' 00:00:00', $subgroup['DTEND']['tz']);
date_modify($date, '-1 day');
$subgroup['DTEND']['datetime'] = date_format($date, 'Y-m-d');
}
// If a start datetime is defined AND there is no definition for
// the end datetime THEN make the end datetime equal the start
// datetime and if it is an all day event define the entire event
// as a single all day event.
if (!empty($subgroup['DTSTART']) &&
(empty($subgroup['DTEND']) && empty($subgroup['RRULE']) && empty($subgroup['RRULE']['COUNT']))) {
$subgroup['DTEND'] = $subgroup['DTSTART'];
}
// Add this element to the parent as an array under the component
// name.
if (!empty($subgroup['DTSTART']['all_day'])) {
$subgroup['all_day'] = TRUE;
}
// Add this element to the parent as an array under the
prev($subgroups);
$parent = &$subgroups[key($subgroups)];
$parent[$type][] = $subgroup;
array_pop($subgroups);
array_pop($parents);
break;
}
}
}
// Handle all other possibilities.
else {
// Grab current subgroup.
end($subgroups);
$subgroup = &$subgroups[key($subgroups)];
// Split up the line into nice pieces for PROPERTYNAME,
// PROPERTYATTRIBUTES, and PROPERTYVALUE.
preg_match('/([^;:]+)(?:;([^:]*))?:(.+)/', $line, $matches);
$name = !empty($matches[1]) ? strtoupper(trim($matches[1])) : '';
$field = !empty($matches[2]) ? $matches[2] : '';
$data = !empty($matches[3]) ? $matches[3] : '';
$parse_result = '';
switch ($name) {
// Keep blank lines out of the results.
case '':
break;
// Lots of properties have date values that must be parsed out.
case 'CREATED':
case 'LAST-MODIFIED':
case 'DTSTART':
case 'DTEND':
case 'DTSTAMP':
case 'FREEBUSY':
case 'DUE':
case 'COMPLETED':
$parse_result = date_ical_parse_date($field, $data);
break;
case 'EXDATE':
case 'RDATE':
$parse_result = date_ical_parse_exceptions($field, $data);
break;
case 'TRIGGER':
// A TRIGGER can either be a date or in the form -PT1H.
if (!empty($field)) {
$parse_result = date_ical_parse_date($field, $data);
}
else {
$parse_result = array('DATA' => $data);
}
break;
case 'DURATION':
// Can't be sure whether DTSTART is before or after DURATION in
// the VEVENT, so store the data and parse it at the end.
$parse_result = array('DATA' => $data);
break;
case 'RRULE':
case 'EXRULE':
$parse_result = date_ical_parse_rrule($field, $data);
break;
case 'STATUS':
case 'SUMMARY':
case 'DESCRIPTION':
$parse_result = date_ical_parse_text($field, $data);
break;
case 'LOCATION':
$parse_result = date_ical_parse_location($field, $data);
break;
// For all other properties, just store the property and the value.
// This can be expanded on in the future if other properties should
// be given special treatment.
default:
$parse_result = $data;
break;
}
// Store the result of our parsing.
$subgroup[$name] = $parse_result;
}
}
return $items;
}
/**
* Parses a ical date element.
*
* Possible formats to parse include:
* PROPERTY:YYYYMMDD[T][HH][MM][SS][Z]
* PROPERTY;VALUE=DATE:YYYYMMDD[T][HH][MM][SS][Z]
* PROPERTY;VALUE=DATE-TIME:YYYYMMDD[T][HH][MM][SS][Z]
* PROPERTY;TZID=XXXXXXXX;VALUE=DATE:YYYYMMDD[T][HH][MM][SS]
* PROPERTY;TZID=XXXXXXXX:YYYYMMDD[T][HH][MM][SS]
*
* The property and the colon before the date are removed in the import
* process above and we are left with $field and $data.
*
* @param string $field
* The text before the colon and the date, i.e.
* ';VALUE=DATE:', ';VALUE=DATE-TIME:', ';TZID='
* @param string $data
* The date itself, after the colon, in the format YYYYMMDD[T][HH][MM][SS][Z]
* 'Z', if supplied, means the date is in UTC.
*
* @return array
* $items array, consisting of:
* 'datetime' => date in YYYY-MM-DD HH:MM format, not timezone adjusted
* 'all_day' => whether this is an all-day event with no time
* 'tz' => the timezone of the date, could be blank if the ical
* has no timezone; the ical specs say no timezone
* conversion should be done if no timezone info is
* supplied
* @todo
* Another option for dates is the format PROPERTY;VALUE=PERIOD:XXXX. The
* period may include a duration, or a date and a duration, or two dates, so
* would have to be split into parts and run through date_ical_parse_date()
* and date_ical_parse_duration(). This is not commonly used, so ignored for
* now. It will take more work to figure how to support that.
*/
function date_ical_parse_date($field, $data) {
$items = array('datetime' => '', 'all_day' => '', 'tz' => '');
if (empty($data)) {
return $items;
}
// Make this a little more whitespace independent.
$data = trim($data);
// Turn the properties into a nice indexed array of
// array(PROPERTYNAME => PROPERTYVALUE);
$field_parts = preg_split('/[;:]/', $field);
$properties = array();
foreach ($field_parts as $part) {
if (strpos($part, '=') !== FALSE) {
$tmp = explode('=', $part);
$properties[$tmp[0]] = $tmp[1];
}
}
// Make this a little more whitespace independent.
$data = trim($data);
// Record if a time has been found.
$has_time = FALSE;
// If a format is specified, parse it according to that format.
if (isset($properties['VALUE'])) {
switch ($properties['VALUE']) {
case 'DATE':
preg_match(DATE_REGEX_ICAL_DATE, $data, $regs);
// Date.
$datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]);
break;
case 'DATE-TIME':
preg_match(DATE_REGEX_ICAL_DATETIME, $data, $regs);
// Date.
$datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]);
// Time.
$datetime .= ' ' . date_pad($regs[4]) . ':' . date_pad($regs[5]) . ':' . date_pad($regs[6]);
$has_time = TRUE;
break;
}
}
// If no format is specified, attempt a loose match.
else {
preg_match(DATE_REGEX_LOOSE, $data, $regs);
if (!empty($regs) && count($regs) > 2) {
// Date.
$datetime = date_pad($regs[1]) . '-' . date_pad($regs[2]) . '-' . date_pad($regs[3]);
if (isset($regs[4])) {
$has_time = TRUE;
// Time.
$datetime .= ' ' . (!empty($regs[5]) ? date_pad($regs[5]) : '00') .
':' . (!empty($regs[6]) ? date_pad($regs[6]) : '00') .
':' . (!empty($regs[7]) ? date_pad($regs[7]) : '00');
}
}
}
// Use timezone if explicitly declared.
if (isset($properties['TZID'])) {
$tz = $properties['TZID'];
// Fix alternatives like US-Eastern which should be US/Eastern.
$tz = str_replace('-', '/', $tz);
// Unset invalid timezone names.
module_load_include('inc', 'date_api', 'date_api.admin');
$tz = _date_timezone_replacement($tz);
if (!date_timezone_is_valid($tz)) {
$tz = '';
}
}
// If declared as UTC with terminating 'Z', use that timezone.
elseif (strpos($data, 'Z') !== FALSE) {
$tz = 'UTC';
}
// Otherwise this date is floating.
else {
$tz = '';
}
$items['datetime'] = $datetime;
$items['all_day'] = $has_time ? FALSE : TRUE;
$items['tz'] = $tz;
return $items;
}
/**
* Parse an ical repeat rule.
*
* @return array
* Array in the form of PROPERTY => array(VALUES)
* PROPERTIES include FREQ, INTERVAL, COUNT, BYDAY, BYMONTH, BYYEAR, UNTIL
*/
function date_ical_parse_rrule($field, $data) {
$data = preg_replace("/RRULE.*:/", '', $data);
$items = array('DATA' => $data);
$rrule = explode(';', $data);
foreach ($rrule as $key => $value) {
$param = explode('=', $value);
// Must be some kind of invalid data.
if (count($param) != 2) {
continue;
}
if ($param[0] == 'UNTIL') {
$values = date_ical_parse_date('', $param[1]);
}
else {
$values = explode(',', $param[1]);
}
// Treat items differently if they have multiple or single values.
if (in_array($param[0], array('FREQ', 'INTERVAL', 'COUNT', 'WKST'))) {
$items[$param[0]] = $param[1];
}
else {
$items[$param[0]] = $values;
}
}
return $items;
}
/**
* Parse exception dates (can be multiple values).
*
* @return array
* an array of date value arrays.
*/
function date_ical_parse_exceptions($field, $data) {
$data = str_replace($field . ':', '', $data);
$items = array('DATA' => $data);
$ex_dates = explode(',', $data);
foreach ($ex_dates as $ex_date) {
$items[] = date_ical_parse_date('', $ex_date);
}
return $items;
}
/**
* Parses the duration of the event.
*
* Example:
* DURATION:PT1H30M
* DURATION:P1Y2M
*
* @param array $subgroup
* Array of other values in the vevent so we can check for DTSTART.
*/
function date_ical_parse_duration(&$subgroup, $field = 'DURATION') {
$items = $subgroup[$field];
$data = $items['DATA'];
preg_match('/^P(\d{1,4}[Y])?(\d{1,2}[M])?(\d{1,2}[W])?(\d{1,2}[D])?([T]{0,1})?(\d{1,2}[H])?(\d{1,2}[M])?(\d{1,2}[S])?/', $data, $duration);
$items['year'] = isset($duration[1]) ? str_replace('Y', '', $duration[1]) : '';
$items['month'] = isset($duration[2]) ?str_replace('M', '', $duration[2]) : '';
$items['week'] = isset($duration[3]) ?str_replace('W', '', $duration[3]) : '';
$items['day'] = isset($duration[4]) ?str_replace('D', '', $duration[4]) : '';
$items['hour'] = isset($duration[6]) ?str_replace('H', '', $duration[6]) : '';
$items['minute'] = isset($duration[7]) ?str_replace('M', '', $duration[7]) : '';
$items['second'] = isset($duration[8]) ?str_replace('S', '', $duration[8]) : '';
$start_date = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['datetime'] : date_format(date_now(), DATE_FORMAT_ISO);
$timezone = array_key_exists('DTSTART', $subgroup) ? $subgroup['DTSTART']['tz'] : variable_get('date_default_timezone');
if (empty($timezone)) {
$timezone = 'UTC';
}
$date = new DateObject($start_date, $timezone);
$date2 = clone($date);
foreach ($items as $item => $count) {
if ($count > 0) {
date_modify($date2, '+' . $count . ' ' . $item);
}
}
$format = isset($subgroup['DTSTART']['type']) && $subgroup['DTSTART']['type'] == 'DATE' ? 'Y-m-d' : 'Y-m-d H:i:s';
$subgroup['DTEND'] = array(
'datetime' => date_format($date2, DATE_FORMAT_DATETIME),
'all_day' => isset($subgroup['DTSTART']['all_day']) ? $subgroup['DTSTART']['all_day'] : 0,
'tz' => $timezone,
);
$duration = date_format($date2, 'U') - date_format($date, 'U');
$subgroup['DURATION'] = array('DATA' => $data, 'DURATION' => $duration);
}
/**
* Parse and clean up ical text elements.
*/
function date_ical_parse_text($field, $data) {
if (strstr($field, 'QUOTED-PRINTABLE')) {
$data = quoted_printable_decode($data);
}
// Strip line breaks within element.
$data = str_replace(array("\r\n ", "\n ", "\r "), '', $data);
// Put in line breaks where encoded.
$data = str_replace(array("\\n", "\\N"), "\n", $data);
// Remove other escaping.
$data = stripslashes($data);
return $data;
}
/**
* Parse location elements.
*
* Catch situations like the upcoming.org feed that uses
* LOCATION;VENUE-UID="http://upcoming.yahoo.com/venue/104/":111 First Street...
* or more normal LOCATION;UID=123:111 First Street...
* Upcoming feed would have been improperly broken on the ':' in http://
* so we paste the $field and $data back together first.
*
* Use non-greedy check for ':' in case there are more of them in the address.
*/
function date_ical_parse_location($field, $data) {
if (preg_match('/UID=[?"](.+)[?"][*?:](.+)/', $field . ':' . $data, $matches)) {
$location = array();
$location['UID'] = $matches[1];
$location['DESCRIPTION'] = stripslashes($matches[2]);
return $location;
}
else {
// Remove other escaping.
$location = stripslashes($data);
return $location;
}
}
/**
* Return a date object for the ical date, adjusted to its local timezone.
*
* @param array $ical_date
* An array of ical date information created in the ical import.
* @param string $to_tz
* The timezone to convert the date's value to.
*
* @return object
* A timezone-adjusted date object.
*/
function date_ical_date($ical_date, $to_tz = FALSE) {
// If the ical date has no timezone, must assume it is stateless
// so treat it as a local date.
if (empty($ical_date['datetime'])) {
return NULL;
}
elseif (empty($ical_date['tz'])) {
$from_tz = date_default_timezone();
}
else {
$from_tz = $ical_date['tz'];
}
if (strlen($ical_date['datetime']) < 11) {
$ical_date['datetime'] .= ' 00:00:00';
}
$date = new DateObject($ical_date['datetime'], new DateTimeZone($from_tz));
if ($to_tz && $ical_date['tz'] != '' && $to_tz != $ical_date['tz']) {
date_timezone_set($date, timezone_open($to_tz));
}
return $date;
}
/**
* Escape #text elements for safe iCal use.
*
* @param string $text
* Text to escape
*
* @return string
* Escaped text
*
*/
function date_ical_escape_text($text) {
$text = drupal_html_to_text($text);
$text = trim($text);
// TODO Per #38130 the iCal specs don't want : and " escaped
// but there was some reason for adding this in. Need to watch
// this and see if anything breaks.
// $text = str_replace('"', '\"', $text);
// $text = str_replace(":", "\:", $text);
$text = preg_replace("/\\\b/", "\\\\", $text);
$text = str_replace(",", "\,", $text);
$text = str_replace(";", "\;", $text);
$text = str_replace("\n", "\\n ", $text);
return trim($text);
}
/**
* Build an iCal RULE from $form_values.
*
* @param array $form_values
* An array constructed like the one created by date_ical_parse_rrule().
* [RRULE] => Array (
* [FREQ] => Array (
* [0] => MONTHLY
* )
* [BYDAY] => Array (
* [0] => 1SU
* [1] => -1SU
* )
* [UNTIL] => Array (
* [datetime] => 1997-21-31 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* [EXDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
* [RDATE] => Array (
* [0] = Array (
* [datetime] => 1997-09-21 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* [1] = Array (
* [datetime] => 1997-10-05 09:00:00
* [all_day] => 0
* [tz] => US/Eastern
* )
* )
*/
function date_api_ical_build_rrule($form_values) {
$RRULE = '';
if (empty($form_values) || !is_array($form_values)) {
return $RRULE;
}
// Grab the RRULE data and put them into iCal RRULE format.
$RRULE .= 'RRULE:FREQ=' . (!array_key_exists('FREQ', $form_values) ? 'DAILY' : $form_values['FREQ']);
$RRULE .= ';INTERVAL=' . (!array_key_exists('INTERVAL', $form_values) ? 1 : $form_values['INTERVAL']);
// Unset the empty 'All' values.
if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY'])) {
unset($form_values['BYDAY']['']);
}
if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH'])) {
unset($form_values['BYMONTH']['']);
}
if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY'])) {
unset($form_values['BYMONTHDAY']['']);
}
if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY']) && $BYDAY = implode(",", $form_values['BYDAY'])) {
$RRULE .= ';BYDAY=' . $BYDAY;
}
if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH']) && $BYMONTH = implode(",", $form_values['BYMONTH'])) {
$RRULE .= ';BYMONTH=' . $BYMONTH;
}
if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY']) && $BYMONTHDAY = implode(",", $form_values['BYMONTHDAY'])) {
$RRULE .= ';BYMONTHDAY=' . $BYMONTHDAY;
}
// The UNTIL date is supposed to always be expressed in UTC.
// The input date values may already have been converted to a date object on a
// previous pass, so check for that.
if (array_key_exists('UNTIL', $form_values) && array_key_exists('datetime', $form_values['UNTIL']) && !empty($form_values['UNTIL']['datetime'])) {
// We only collect a date for UNTIL, but we need it to be inclusive, so
// force it to a full datetime element at the last second of the day.
if (!is_object($form_values['UNTIL']['datetime'])) {
// If this is a date without time, give it time.
if (strlen($form_values['UNTIL']['datetime']) < 11) {
$form_values['UNTIL']['datetime'] .= ' 23:59:59';
$form_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second')));
$form_values['UNTIL']['all_day'] = FALSE;
}
$until = date_ical_date($form_values['UNTIL'], 'UTC');
}
else {
$until = $form_values['UNTIL']['datetime'];
}
$RRULE .= ';UNTIL=' . date_format($until, DATE_FORMAT_ICAL) . 'Z';
}
// Our form doesn't allow a value for COUNT, but it may be needed by
// modules using the API, so add it to the rule.
if (array_key_exists('COUNT', $form_values)) {
$RRULE .= ';COUNT=' . $form_values['COUNT'];
}
// iCal rules presume the week starts on Monday unless otherwise specified,
// so we'll specify it.
if (array_key_exists('WKST', $form_values)) {
$RRULE .= ';WKST=' . $form_values['WKST'];
}
else {
$RRULE .= ';WKST=' . date_repeat_dow2day(variable_get('date_first_day', 0));
}
// Exceptions dates go last, on their own line.
// The input date values may already have been converted to a date
// object on a previous pass, so check for that.
if (isset($form_values['EXDATE']) && is_array($form_values['EXDATE'])) {
$ex_dates = array();
foreach ($form_values['EXDATE'] as $value) {
if (!empty($value['datetime'])) {
$date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime'];
$ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': '';
if (!empty($ex_date)) {
$ex_dates[] = $ex_date;
}
}
}
if (!empty($ex_dates)) {
sort($ex_dates);
$RRULE .= chr(13) . chr(10) . 'EXDATE:' . implode(',', $ex_dates);
}
}
elseif (!empty($form_values['EXDATE'])) {
$RRULE .= chr(13) . chr(10) . 'EXDATE:' . $form_values['EXDATE'];
}
// Exceptions dates go last, on their own line.
if (isset($form_values['RDATE']) && is_array($form_values['RDATE'])) {
$ex_dates = array();
foreach ($form_values['RDATE'] as $value) {
$date = !is_object($value['datetime']) ? date_ical_date($value, 'UTC') : $value['datetime'];
$ex_date = !empty($date) ? date_format($date, DATE_FORMAT_ICAL) . 'Z': '';
if (!empty($ex_date)) {
$ex_dates[] = $ex_date;
}
}
if (!empty($ex_dates)) {
sort($ex_dates);
$RRULE .= chr(13) . chr(10) . 'RDATE:' . implode(',', $ex_dates);
}
}
elseif (!empty($form_values['RDATE'])) {
$RRULE .= chr(13) . chr(10) . 'RDATE:' . $form_values['RDATE'];
}
return $RRULE;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
(function ($) {
Drupal.behaviors.dateYearRange = {};
Drupal.behaviors.dateYearRange.attach = function (context, settings) {
var $textfield, $textfields, i;
// Turn the years back and forward fieldsets into dropdowns.
$textfields = $('input.select-list-with-custom-option', context).once('date-year-range');
for (i = 0; i < $textfields.length; i++) {
$textfield = $($textfields[i]);
new Drupal.dateYearRange.SelectListWithCustomOption($textfield);
}
};
Drupal.dateYearRange = {};
/**
* Constructor for the SelectListWithCustomOption object.
*
* This object is responsible for turning the years back and forward textfields
* into dropdowns with an 'other' option that lets the user enter a custom
* value.
*/
Drupal.dateYearRange.SelectListWithCustomOption = function ($textfield) {
this.$textfield = $textfield;
this.$description = $textfield.next('div.description');
this.defaultValue = $textfield.val();
this.$dropdown = this.createDropdown();
this.$dropdown.insertBefore($textfield);
};
/**
* Get the value of the textfield as it existed on page load.
*
* @param {String} type
* The type of the variable to be returned. Defaults to string.
* @return
* The original value of the textfield. Returned as an integer, if the type
* parameter was 'int'.
*/
Drupal.dateYearRange.SelectListWithCustomOption.prototype.getOriginal = function (type) {
var original;
if (type === 'int') {
original = parseInt(this.defaultValue, 10);
if (window.isNaN(original)) {
original = 0;
}
}
else {
original = this.defaultValue;
}
return original;
};
/**
* Get the correct first value for the dropdown.
*/
Drupal.dateYearRange.SelectListWithCustomOption.prototype.getStartValue = function () {
var direction = this.getDirection();
var start;
switch (direction) {
case 'back':
// For the 'years back' dropdown, the first option should be -10, unless
// the default value of the textfield is even smaller than that.
start = Math.min(this.getOriginal('int'), -10);
break;
case 'forward':
start = 0;
break;
}
return start;
};
/**
* Get the correct last value for the dropdown.
*/
Drupal.dateYearRange.SelectListWithCustomOption.prototype.getEndValue = function () {
var direction = this.getDirection();
var end;
var originalString = this.getOriginal();
switch (direction) {
case 'back':
end = 0;
break;
case 'forward':
// If the original value of the textfield is an absolute year such as
// 2020, don't try to include it in the dropdown.
if (originalString.indexOf('+') === -1) {
end = 10;
}
// If the original value is a relative value (+x), we want it to be
// included in the possible dropdown values.
else {
end = Math.max(this.getOriginal('int'), 10);
}
break;
}
return end;
};
/**
* Create a dropdown select list with the correct options for this textfield.
*/
Drupal.dateYearRange.SelectListWithCustomOption.prototype.createDropdown = function () {
var $dropdown = $('<select>').addClass('form-select date-year-range-select');
var $option, i, value;
var start = this.getStartValue();
var end = this.getEndValue();
var direction = this.getDirection();
for (i = start; i <= end; i++) {
// Make sure we include the +/- sign in the option value.
value = i;
if (i > 0) {
value = '+' + i;
}
// Zero values must have a + or - in front.
if (i === 0) {
if (direction === 'back') {
value = '-' + i;
}
else {
value = '+' + i;
}
}
$option = $('<option>' + Drupal.formatPlural(value, '@count year from now', '@count years from now') + '</option>').val(value);
$dropdown.append($option);
}
// Create an 'Other' option.
$option = $('<option class="custom-option">' + Drupal.t('Other') + '</option>').val('');
$dropdown.append($option);
// When the user changes the selected option in the dropdown, perform
// appropriate actions (such as showing or hiding the textfield).
$dropdown.bind('change', $.proxy(this.handleDropdownChange, this));
// Set the initial value of the dropdown.
this._setInitialDropdownValue($dropdown);
return $dropdown;
};
Drupal.dateYearRange.SelectListWithCustomOption.prototype._setInitialDropdownValue = function ($dropdown) {
var textfieldValue = this.getOriginal();
// Determine whether the original textfield value exists in the dropdown.
var possible = $dropdown.find('option[value="' + textfieldValue + '"]');
// If the original textfield value is one of the dropdown options, preselect
// it and hide the 'other' textfield.
if (possible.length) {
$dropdown.val(textfieldValue);
this.hideTextfield();
}
// If the original textfield value isn't one of the dropdown options, choose
// the 'Other' option in the dropdown.
else {
$dropdown.val('');
}
};
/**
* Determine whether this is the "years back" or "years forward" textfield.
*/
Drupal.dateYearRange.SelectListWithCustomOption.prototype.getDirection = function () {
if (this.direction) {
return this.direction;
}
var direction;
if (this.$textfield.hasClass('back')) {
direction = 'back';
}
else if (this.$textfield.hasClass('forward')) {
direction = 'forward';
}
this.direction = direction;
return direction;
};
/**
* Change handler for the dropdown, to modify the textfield as appropriate.
*/
Drupal.dateYearRange.SelectListWithCustomOption.prototype.handleDropdownChange = function () {
// Since the dropdown changed, we need to make the content of the textfield
// match the (new) selected option.
this.syncTextfield();
// Show the textfield if the 'Other' option was selected, and hide it if one
// of the preset options was selected.
if ($(':selected', this.$dropdown).hasClass('custom-option')) {
this.revealTextfield();
}
else {
this.hideTextfield();
}
};
/**
* Display the textfield and its description.
*/
Drupal.dateYearRange.SelectListWithCustomOption.prototype.revealTextfield = function () {
this.$textfield.show();
this.$description.show();
};
/**
* Hide the textfield and its description.
*/
Drupal.dateYearRange.SelectListWithCustomOption.prototype.hideTextfield = function () {
this.$textfield.hide();
this.$description.hide();
};
/**
* Copy the selected value of the dropdown to the textfield.
*
* FAPI doesn't know about the JS-only dropdown, so the textfield needs to
* reflect the value of the dropdown.
*/
Drupal.dateYearRange.SelectListWithCustomOption.prototype.syncTextfield = function () {
var value = this.$dropdown.val();
this.$textfield.val(value);
};
})(jQuery);

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 B

View File

@@ -0,0 +1,229 @@
<?php
/**
* @file
* Theme files for Date API.
*/
/**
* Returns HTML for a date timezone element.
*/
function theme_date_timezone($variables) {
$element = $variables['element'];
$attributes = $element['#attributes'];
$wrapper_attributes = array();
// Add an wrapper to mimic the way a single value field works, for ease in
// using #states.
if (isset($element['#children'])) {
$element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) . '>' . $element['#children'] . '</div>';
}
return '<div ' . drupal_attributes($attributes) . '>' . theme('form_element', $element) . '</div>';
}
/**
* Returns HTML for a date select element.
*/
function theme_date_select($variables) {
$element = $variables['element'];
$attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
$attributes['class'][] = 'container-inline-date';
$wrapper_attributes = array('class' => array('date-padding'));
$wrapper_attributes['class'][] = 'clearfix';
// Add an wrapper to mimic the way a single value field works, for ease in
// using #states.
if (isset($element['#children'])) {
$element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) . '>' . $element['#children'] . '</div>';
}
return '<div ' . drupal_attributes($attributes) . '>' . theme('form_element', $element) . '</div>';
}
/**
* Returns HTML for a date text element.
*/
function theme_date_text($variables) {
$element = $variables['element'];
$attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
$attributes['class'][] = 'container-inline-date';
// If there is no description, the floating date elements need some extra
// padding below them.
$wrapper_attributes = array('class' => array('date-padding'));
if (empty($element['date']['#description'])) {
$wrapper_attributes['class'][] = 'clearfix';
}
// Add an wrapper to mimic the way a single value field works, for ease in
// using #states.
if (isset($element['#children'])) {
$element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) . '>' . $element['#children'] . '</div>';
}
return '<div ' . drupal_attributes($attributes) . '>' . theme('form_element', $element) . '</div>';
}
/**
* Returns HTML for a date select input form element.
*/
function theme_date_select_element($variables) {
$element = $variables['element'];
$parents = $element['#parents'];
$part = array_pop($parents);
return '<div class="date-' . $part . '">' . theme('select', $element) . '</div>';
}
/**
* Returns HTML for a date textfield input form element.
*/
function theme_date_textfield_element($variables) {
$element = $variables['element'];
$parents = $element['#parents'];
$part = array_pop($parents);
return '<div class="date-' . $part . '">' . theme('textfield', $element) . '</div>';
}
/**
* Returns HTML for a 'hour' date part prefix.
*/
function theme_date_part_hour_prefix($variables) {
$element = $variables['element'];
if ($element['#date_label_position'] != 'above') {
return '<span class="form-item date-spacer">&nbsp;-&nbsp;</span>';
}
}
/**
* Returns HTML for a 'minutes and seconds' date part prefix.
*/
function theme_date_part_minsec_prefix($variables) {
$element = $variables['element'];
if ($element['#date_label_position'] != 'above') {
return '<span class="form-item date-spacer">:</span>';
}
}
/**
* Returns HTML for a date_select 'year' label.
*/
function theme_date_part_label_year($variables) {
return t('Year', array(), array('context' => 'datetime'));
}
/**
* Returns HTML for a date_select 'month' label.
*/
function theme_date_part_label_month($variables) {
return t('Month', array(), array('context' => 'datetime'));
}
/**
* Returns HTML for a date_select 'day' label.
*/
function theme_date_part_label_day($variables) {
return t('Day', array(), array('context' => 'datetime'));
}
/**
* Returns HTML for a date_select 'hour' label.
*/
function theme_date_part_label_hour($variables) {
return t('Hour', array(), array('context' => 'datetime'));
}
/**
* Returns HTML for a date_select 'minute' label.
*/
function theme_date_part_label_minute($variables) {
return t('Minute', array(), array('context' => 'datetime'));
}
/**
* Returns HTML for a date_select 'second' label.
*/
function theme_date_part_label_second($variables) {
return t('Second', array(), array('context' => 'datetime'));
}
/**
* Returns HTML for a date_select 'ampm' label.
*/
function theme_date_part_label_ampm($variables) {
return '&nbsp;';
}
/**
* Returns HTML for a date_select 'timezone' label.
*/
function theme_date_part_label_timezone($variables) {
return t('Timezone');
}
/**
* Returns HTML for a date_select 'date' label.
*/
function theme_date_part_label_date($variables) {
return t('Date', array(), array('context' => 'datetime'));
}
/**
* Returns HTML for a date_select 'time' label.
*/
function theme_date_part_label_time($variables) {
return t('Time', array(), array('context' => 'datetime'));
}
/**
* Returns HTML for a date block that looks like a mini calendar day.
*
* Pass in a date object already set to the right timezone, format as a calendar
* page date. The calendar styling is created in CSS.
*/
function theme_date_calendar_day($variables) {
$output = '';
$date = $variables['date'];
if (!empty($date)) {
$output .= '<div class="date-calendar-day">';
$output .= '<span class="month">' . date_format_date($date, 'custom', 'M') . '</span>';
$output .= '<span class="day">' . date_format_date($date, 'custom', 'j') . '</span>';
$output .= '<span class="year">' . date_format_date($date, 'custom', 'Y') . '</span>';
$output .= '</div>';
}
return $output;
}
/**
* Returns HTML for a date in the format 'time ago'.
*/
function theme_date_time_ago($variables) {
$start_date = $variables['start_date'];
$end_date = $variables['end_date'];
$interval = !empty($variables['interval']) ? $variables['interval'] : 2;
$display = isset($variables['interval_display']) ? $variables['interval_display'] : 'time ago';
// If no date is sent, then return nothing.
if (empty($start_date) || empty($end_date)) {
return;
}
// Time to compare dates to.
$now = date_format(date_now(), DATE_FORMAT_UNIX);
$start = date_format($start_date, DATE_FORMAT_UNIX);
// will be positive for a datetime in the past (ago), and negative for a datetime in the future (hence)
$time_diff = $now - $start;
// Uses the same options used by Views format_interval.
switch ($display) {
case 'raw time ago':
return format_interval($time_diff, $interval);
case 'time ago':
return t('%time ago', array('%time' => format_interval($time_diff, $interval)));
case 'raw time hence':
return format_interval(-$time_diff, $interval);
case 'time hence':
return t('%time hence', array('%time' => format_interval(-$time_diff, $interval)));
case 'raw time span':
return ($time_diff < 0 ? '-' : '') . format_interval(abs($time_diff), $interval);
case 'inverse time span':
return ($time_diff > 0 ? '-' : '') . format_interval(abs($time_diff), $interval);
case 'time span':
return t(($time_diff < 0 ? '%time hence' : '%time ago'), array('%time' => format_interval(abs($time_diff), $interval)));
}
}

View File

@@ -0,0 +1,16 @@
name = Date Context
description = Adds an option to the Context module to set a context condition based on the value of a date field.
package = Date/Time
core = 7.x
dependencies[] = date
dependencies[] = context
files[] = date_context.module
files[] = plugins/date_context_date_condition.inc
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,54 @@
<?php
/**
* @TODO
*
* Currently only implemented for nodes. Need to add $plugin->execute()
* for any other entities that might be supported.
*
* Cache the date processing, perhaps cache the formatted, timezone-adjusted
* date strings for each entity (would have to be cached differently for each
* timezone, based on the tz_handling method for the date).
*
* Add an option to set/not set the context on forms vs views.
*/
/**
* Implements hook_context_node_condition_alter().
*/
function date_context_context_node_condition_alter($node, $op) {
if ($plugin = context_get_plugin('condition', 'date_context_date_condition')) {
$plugin->execute($node, $op);
}
}
/**
* Implements hook_context_plugins()
*/
function date_context_context_plugins() {
$plugins = array();
$plugins['date_context_date_condition'] = array(
'handler' => array(
'class' => 'date_context_date_condition',
'parent' => 'context_condition_node',
'path' => drupal_get_path('module', 'date_context') . '/plugins',
'file' => 'date_context_date_condition.inc',
),
);
return $plugins;
}
/**
* Implements hook_context_registry()
*/
function date_context_context_registry() {
return array(
'conditions' => array(
'date_context_date_condition' => array(
'title' => t('Date'),
'description' => t('Set a condition based on the value of a date field'),
'plugin' => 'date_context_date_condition',
),
),
);
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* Expose term views/term forms by vocabulary as a context condition.
*/
class date_context_date_condition extends context_condition_node {
function condition_values() {
$values = array();
$fields = field_info_fields();
foreach ($fields as $field_name => $field) {
if (in_array($field['type'], array('date', 'datetime', 'datestamp'))) {
$values[$field_name] = $field_name;
}
}
return $values;
}
function options_form($context) {
$defaults = $this->fetch_from_context($context, 'options');
$options = array(
'<' => t('Is less than'),
'<=' => t('Is less than or equal to'),
'>=' => t('Is greater than or equal to'),
'>' => t('Is greater than'),
'=' => t('Is equal to'),
'!=' => t('Is not equal to'),
'empty' => t('Is empty'),
'not empty' => t('Is not Empty'),
);
$form['operation'] = array(
'#title' => t('Operation'),
'#type' => 'select',
'#options' => $options,
'#description' => t('The comparison to perform to determine if the date field meets the condition. For multiple value date fields, all values will be checked to see if any meet the condition.'),
'#default_value' => isset($defaults['operation']) ? $defaults['operation'] : '',
'#required' => TRUE,
);
$form['value'] = array(
'#title' => t('Value'),
'#type' => 'textfield',
'#description' => t("The value the field should contain to meet the condition. This can either be an absolute date in ISO format (YYYY-MM-DDTHH:MM:SS) or a relative string like '12AM today'. Examples: 2011-12-31T00:00:00, now, now +1 day, 12AM today, Monday next week. <a href=\"@relative_format\">More examples of relative date formats in the PHP documentation</a>.", array('@relative_format' => 'http://www.php.net/manual/en/datetime.formats.relative.php')),
'#default_value' => isset($defaults['value']) ? $defaults['value'] : '',
'#process' => array('ctools_dependent_process'),
'#dependency' => array(':input[name="conditions[plugins][date_context_date_condition][options][operation]"]' => array('<', '<=', '>', '>=', '=', '!=')),
);
return $form;
}
function execute($entity, $op) {
if (in_array($op, array('view', 'form'))) {
foreach ($this->get_contexts() as $context) {
$options = $this->fetch_from_context($context, 'options');
$fields = $this->fetch_from_context($context, 'values');
foreach ($fields as $field_name => $label) {
// If this field does not exist on this entity, just move along.
if (empty($entity->{$field_name})) {
continue;
}
$items = field_get_items('node', $entity, $field_name);
// If there are no values, nothing to do unless we were looking for 'empty' or '!='.
if (empty($items)) {
if ($options['operation'] == '!=' || $options['operation'] == 'empty') {
$this->condition_met($context, $field_name);
}
}
// We don't have to construct the date values if we're just testing for 'not empty'.
elseif ($options['operation'] == 'not empty') {
$this->condition_met($context, $field_name);
}
// All other operations need to retrieve the date values for comparision.
else {
$field = field_info_field($field_name);
$timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
foreach ($items as $delta => $item) {
$timezone = date_get_timezone($field['settings']['tz_handling'], $item['timezone']);
$date = new DateObject($item['value'], $timezone_db);
date_timezone_set($date, timezone_open($timezone));
$date1 = $date->format(DATE_FORMAT_DATETIME);
if (empty($item['value2'])) {
$item['value2'] = $item['value'];
}
$date = new DateObject($item['value2'], $timezone_db);
date_timezone_set($date, timezone_open($timezone));
$date2 = $date->format(DATE_FORMAT_DATETIME);
str_replace('now', 'today', $options['value']);
$date = date_create($options['value'], date_default_timezone_object());
$compdate = $date->format(DATE_FORMAT_DATETIME);
switch($options['operation']) {
case '=':
if ($date2 >= $compdate && $date1 <= $compdate) {
$this->condition_met($context, $field_name);
}
break;
case '>':
if ($date1 > $compdate) {
$this->condition_met($context, $field_name);
}
break;
case '>=':
if ($date1 >= $compdate) {
$this->condition_met($context, $field_name);
}
break;
case '<':
if ($date2 < $compdate) {
$this->condition_met($context, $field_name);
}
break;
case '<=':
if ($date2 <= $compdate) {
$this->condition_met($context, $field_name);
}
break;
case '!=':
if ($date1 < $compdate || $date2 > $compdate) {
$this->condition_met($context, $field_name);
}
break;
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,660 @@
<?php
/**
* @file
* Date forms and form themes and validation.
*
* All code used in form editing and processing is in this file,
* included only during form editing.
*/
/**
* Private implementation of hook_widget().
*
* The widget builds out a complex date element in the following way:
*
* - A field is pulled out of the database which is comprised of one or
* more collections of start/end dates.
*
* - The dates in this field are all converted from the UTC values stored
* in the database back to the local time. This is done in #process
* to avoid making this change to dates that are not being processed,
* like those hidden with #access.
*
* - If values are empty, the field settings rules are used to determine
* if the default_values should be empty, now, the same, or use strtotime.
*
* - Each start/end combination is created using the date_combo element type
* defined by the date module. If the timezone is date-specific, a
* timezone selector is added to the first combo element.
*
* - The date combo element creates two individual date elements, one each
* for the start and end field, using the appropriate individual Date API
* date elements, like selects, textfields, or popups.
*
* - In the individual element validation, the data supplied by the user is
* used to update the individual date values.
*
* - In the combo date validation, the timezone is updated, if necessary,
* then the user input date values are used with that timezone to create
* date objects, which are used update combo date timezone and offset values.
*
* - In the field's submission processing, the new date values, which are in
* the local timezone, are converted back to their UTC values and stored.
*
*/
function date_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $base) {
$element = $base;
$field_name = $field['field_name'];
$entity_type = $instance['entity_type'];
// If this is a new entity, populate the field with the right default values.
// This happens early so even fields later hidden with #access get those values.
// We should only add default values to new entities, to avoid over-writing
// a value that has already been set. This means we can't just check to see
// if $items is empty, because it might have been set that way on purpose.
// @see date_field_widget_properties_alter() where we flagged if this is a new entity.
// We check !isset($items[$delta]['value']) because entity translation may create
// a new translation entity for an existing entity and we don't want to clobber
// values that were already set in that case.
// @see http://drupal.org/node/1478848.
$is_default = FALSE;
if (!empty($instance['widget']['is_new']) && !isset($items[$delta]['value'])) {
$items = date_default_value($field, $instance, $langcode);
$is_default = TRUE;
}
// @TODO Repeating dates should probably be made into their own field type and completely separated out.
// That will have to wait for a new branch since it may break other things, including other modules
// that have an expectation of what the date field types are.
// Since repeating dates cannot use the default Add more button, we have to handle our own behaviors here.
// Return only the first multiple value for repeating dates, then clean up the 'Add more' bits in #after_build.
// The repeating values will be re-generated when the repeat widget form is validated.
// At this point we can't tell if this form element is going to be hidden by #access, and we're going to
// lose all but the first value by doing this, so store the original values in case we need to replace them later.
if (!empty($field['settings']['repeat'])) {
if ($delta == 0) {
$form['#after_build'] = array('date_repeat_after_build');
$form_state['storage']['repeat_fields'][$field_name] = array_merge($form['#parents'], array($field_name));
$form_state['storage']['date_items'][$field_name][$langcode] = $items;
}
else {
return;
}
}
module_load_include('inc', 'date_api', 'date_api_elements');
$timezone = date_get_timezone($field['settings']['tz_handling'], isset($items[0]['timezone']) ? $items[0]['timezone'] : date_default_timezone());
// TODO see if there's a way to keep the timezone element from ever being
// nested as array('timezone' => 'timezone' => value)). After struggling
// with this a while, I can find no way to get it displayed in the form
// correctly and get it to use the timezone element without ending up
// with nesting.
if (is_array($timezone)) {
$timezone = $timezone['timezone'];
}
$element += array(
'#type' => 'date_combo',
'#theme_wrappers' => array('date_combo'),
'#weight' => $delta,
'#default_value' => isset($items[$delta]) ? $items[$delta] : '',
'#date_timezone' => $timezone,
'#element_validate' => array('date_combo_validate'),
'#date_is_default' => $is_default,
// Store the original values, for use with disabled and hidden fields.
'#date_items' => isset($items[$delta]) ? $items[$delta] : '',
);
$element['#title'] = $instance['label'];
if ($field['settings']['tz_handling'] == 'date') {
$element['timezone'] = array(
'#type' => 'date_timezone',
'#theme_wrappers' => array('date_timezone'),
'#delta' => $delta,
'#default_value' => $timezone,
'#weight' => $instance['widget']['weight'] + 1,
'#attributes' => array('class' => array('date-no-float')),
'#date_label_position' => $instance['widget']['settings']['label_position'],
);
}
return $element;
}
/**
* Create local date object.
*
* Create a date object set to local time from the field and
* widget settings and item values. Default values for new entities
* are set by the default value callback, so don't need to be accounted for here.
*/
function date_local_date($item, $timezone, $field, $instance, $part = 'value') {
$value = $item[$part];
// If the value is empty, don't try to create a date object because it will
// end up being the current day.
if (empty($value)) {
return NULL;
}
// @TODO Figure out how to replace date_fuzzy_datetime() function.
// Special case for ISO dates to create a valid date object for formatting.
// Is this still needed?
/*
if ($field['type'] == DATE_ISO) {
$value = date_fuzzy_datetime($value);
}
else {
$db_timezone = date_get_timezone_db($field['settings']['tz_handling']);
$value = date_convert($value, $field['type'], DATE_DATETIME, $db_timezone);
}
*/
$date = new DateObject($value, date_get_timezone_db($field['settings']['tz_handling']));
$date->limitGranularity($field['settings']['granularity']);
if (empty($date)) {
return NULL;
}
date_timezone_set($date, timezone_open($timezone));
return $date;
}
/**
* The callback for setting a default value for an empty date field.
*/
function date_default_value($field, $instance, $langcode) {
$item = array();
$db_format = date_type_format($field['type']);
$date = date_default_value_part($item, $field, $instance, $langcode, 'value');
$item[0]['value'] = is_object($date) ? date_format($date, $db_format) : '';
if (!empty($field['settings']['todate'])) {
$date = date_default_value_part($item, $field, $instance, $langcode, 'value2');
$item[0]['value2'] = is_object($date) ? date_format($date, $db_format) : '';
}
// Make sure the default value has the same construct as a loaded field value
// to avoid errors if the default value is used on a hidden element.
$item[0]['timezone'] = date_get_timezone($field['settings']['tz_handling']);
$item[0]['timezone_db'] = date_get_timezone_db($field['settings']['tz_handling']);
$item[0]['date_type'] = $field['type'];
if (!isset($item[0]['value2'])) {
$item[0]['value2'] = $item[0]['value'];
}
return $item;
}
/**
* Helper function for the date default value callback to set
* either 'value' or 'value2' to its default value.
*/
function date_default_value_part($item, $field, $instance, $langcode, $part = 'value') {
$timezone = date_get_timezone($field['settings']['tz_handling']);
$timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
$date = NULL;
if ($part == 'value') {
$default_value = $instance['settings']['default_value'];
$default_value_code = $instance['settings']['default_value_code'];
}
else {
$default_value = $instance['settings']['default_value2'];
$default_value_code = $instance['settings']['default_value_code2'];
}
if (empty($default_value) || $default_value == 'blank') {
return NULL;
}
elseif ($default_value == 'strtotime' && !empty($default_value_code)) {
$date = new DateObject($default_value_code, date_default_timezone());
}
elseif ($part == 'value2' && $default_value == 'same') {
if ($instance['settings']['default_value'] == 'blank' || empty($item[0]['value'])) {
return NULL;
}
else {
// The date stored in 'value' has already been switched to the db timezone.
$date = new DateObject($item[0]['value'], $timezone_db, DATE_FORMAT_DATETIME);
}
}
// Special case for 'now' when using dates with no timezone,
// make sure 'now' isn't adjusted to UTC value of 'now' .
elseif ($field['settings']['tz_handling'] == 'none') {
$date = date_now();
}
else {
$date = date_now($timezone);
}
// The default value needs to be in the database timezone.
date_timezone_set($date, timezone_open($timezone_db));
$date->limitGranularity($field['settings']['granularity']);
return $date;
}
/**
* Process an individual date element.
*/
function date_combo_element_process($element, &$form_state, $form) {
if (date_hidden_element($element)) {
// A hidden value for a new entity that had its end date set to blank
// will not get processed later to populate the end date, so set it here.
if (isset($element['#value']['value2']) && empty($element['#value']['value2'])) {
$element['#value']['value2'] = $element['#value']['value'];
}
return $element;
}
$field_name = $element['#field_name'];
$delta = $element['#delta'];
$bundle = $element['#bundle'];
$entity_type = $element['#entity_type'];
$langcode = $element['#language'];
$date_is_default = $element['#date_is_default'];
$field = field_widget_field($element, $form_state);
$instance = field_widget_instance($element, $form_state);
// Figure out how many items are in the form, including new ones added by ajax.
$field_state = field_form_get_state($element['#field_parents'], $field_name, $element['#language'], $form_state);
$items_count = $field_state['items_count'];
$columns = $element['#columns'];
if (isset($columns['rrule'])) {
unset($columns['rrule']);
}
$from_field = 'value';
$to_field = 'value2';
$tz_field = 'timezone';
$offset_field = 'offset';
$offset_field2 = 'offset2';
// Convert UTC dates to their local values in DATETIME format,
// and adjust the default values as specified in the field settings.
// It would seem to make sense to do this conversion when the data
// is loaded instead of when the form is created, but the loaded
// field data is cached and we can't cache dates that have been converted
// to the timezone of an individual user, so we cache the UTC values
// instead and do our conversion to local dates in the form and
// in the formatters.
$process = date_process_values($field, $instance);
foreach ($process as $processed) {
if (!isset($element['#default_value'][$processed])) {
$element['#default_value'][$processed] = '';
}
$date = date_local_date($element['#default_value'], $element['#date_timezone'], $field, $instance, $processed);
$element['#default_value'][$processed] = is_object($date) ? date_format($date, DATE_FORMAT_DATETIME) : '';
}
// Blank out the end date for optional end dates that match the start date,
// except when this is a new node that has default values that should be honored.
if (!$date_is_default && $field['settings']['todate'] != 'required'
&& !empty($element['#default_value'][$to_field])
&& $element['#default_value'][$to_field] == $element['#default_value'][$from_field]) {
unset($element['#default_value'][$to_field]);
}
$show_todate = !empty($form_state['values']['show_todate']) || !empty($element['#default_value'][$to_field]) || $field['settings']['todate'] == 'required';
$element['show_todate'] = array(
'#title' => t('Show End Date'),
'#type' => 'checkbox',
'#default_value' => $show_todate,
'#weight' => -20,
'#access' => $field['settings']['todate'] == 'optional',
'#prefix' => '<div class="date-float">',
'#suffix' => '</div>',
);
$parents = $element['#parents'];
$first_parent = array_shift($parents);
$show_id = $first_parent . '[' . implode('][', $parents) . '][show_todate]';
$element[$from_field] = array(
'#field' => $field,
'#instance' => $instance,
'#weight' => $instance['widget']['weight'],
'#required' => ($instance['required'] && $delta == 0) ? 1 : 0,
'#default_value' => isset($element['#default_value'][$from_field]) ? $element['#default_value'][$from_field] : '',
'#delta' => $delta,
'#date_timezone' => $element['#date_timezone'],
'#date_format' => date_limit_format(date_input_format($element, $field, $instance), $field['settings']['granularity']),
'#date_text_parts' => (array) $instance['widget']['settings']['text_parts'],
'#date_increment' => $instance['widget']['settings']['increment'],
'#date_year_range' => $instance['widget']['settings']['year_range'],
'#date_label_position' => $instance['widget']['settings']['label_position'],
);
$description = !empty($instance['description']) ? t($instance['description']) : '';
// Give this element the right type, using a Date API
// or a Date Popup element type.
$element[$from_field]['#attributes'] = array('class' => array('date-clear'));
$element[$from_field]['#wrapper_attributes'] = array('class' => array());
$element[$from_field]['#wrapper_attributes']['class'][] = 'date-no-float';
switch ($instance['widget']['type']) {
case 'date_select':
$element[$from_field]['#type'] = 'date_select';
$element[$from_field]['#theme_wrappers'] = array('date_select');
$element['#attached']['js'][] = drupal_get_path('module', 'date') . '/date.js';
$element[$from_field]['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE;
break;
case 'date_popup':
$element[$from_field]['#type'] = 'date_popup';
$element[$from_field]['#theme_wrappers'] = array('date_popup');
$element[$from_field]['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE;
break;
default:
$element[$from_field]['#type'] = 'date_text';
$element[$from_field]['#theme_wrappers'] = array('date_text');
$element[$from_field]['#ajax'] = !empty($element['#ajax']) ? $element['#ajax'] : FALSE;
break;
}
// If this field uses the 'End', add matching element
// for the 'End' date, and adapt titles to make it clear which
// is the 'Start' and which is the 'End' .
if (!empty($field['settings']['todate'])) {
$element[$from_field]['#title'] = '';
$element[$to_field] = $element[$from_field];
$element[$to_field]['#title'] = t('to:');
$element[$from_field]['#wrapper_attributes']['class'][] = 'start-date-wrapper';
$element[$to_field]['#wrapper_attributes']['class'][] = 'end-date-wrapper';
$element[$to_field]['#default_value'] = isset($element['#default_value'][$to_field]) ? $element['#default_value'][$to_field] : '';
$element[$to_field]['#required'] = ($element[$from_field]['#required'] && $field['settings']['todate'] == 'required');
$element[$to_field]['#weight'] += .2;
$element[$to_field]['#prefix'] = '';
// Users with JS enabled will never see initially blank values for the end
// date (see Drupal.date.EndDateHandler()), so hide the message for them.
$description .= '<span class="js-hide"> ' . t("Empty 'End date' values will use the 'Start date' values.") . '</span>';
$element['#fieldset_description'] = $description;
if ($field['settings']['todate'] == 'optional') {
$element[$to_field]['#states'] = array(
'visible' => array(
'input[name="' . $show_id . '"]' => array('checked' => TRUE),
));
}
}
else {
$element[$from_field]['#description'] = $description;
}
// Create label for error messages that make sense in multiple values
// and when the title field is left blank.
if ($field['cardinality'] <> 1 && empty($field['settings']['repeat'])) {
$element[$from_field]['#date_title'] = t('@field_name Start date value #@delta', array('@field_name' => $instance['label'], '@delta' => $delta + 1));
if (!empty($field['settings']['todate'])) {
$element[$to_field]['#date_title'] = t('@field_name End date value #@delta', array('@field_name' => $instance['label'], '@delta' => $delta + 1));
}
}
elseif (!empty($field['settings']['todate'])) {
$element[$from_field]['#date_title'] = t('@field_name Start date', array('@field_name' => $instance['label']));
$element[$to_field]['#date_title'] = t('@field_name End date', array('@field_name' => $instance['label']));
}
else {
$element[$from_field]['#date_title'] = $instance['label'];
}
$context = array(
'field' => $field,
'instance' => $instance,
'form' => $form,
);
drupal_alter('date_combo_process', $element, $form_state, $context);
return $element;
}
function date_element_empty($element, &$form_state) {
$item = array();
$item['value'] = NULL;
$item['value2'] = NULL;
$item['timezone'] = NULL;
$item['offset'] = NULL;
$item['offset2'] = NULL;
$item['rrule'] = NULL;
form_set_value($element, $item, $form_state);
return $item;
}
/**
* Validate and update a combo element.
* Don't try this if there were errors before reaching this point.
*/
function date_combo_validate($element, &$form_state) {
// Disabled and hidden elements won't have any input and don't need validation,
// we just need to re-save the original values, from before they were processed into
// widget arrays and timezone-adjusted.
if (date_hidden_element($element) || !empty($element['#disabled'])) {
form_set_value($element, $element['#date_items'], $form_state);
return;
}
$field_name = $element['#field_name'];
$delta = $element['#delta'];
$langcode = $element['#language'];
$form_values = drupal_array_get_nested_value($form_state['values'], $element['#field_parents']);
$form_input = drupal_array_get_nested_value($form_state['input'], $element['#field_parents']);
// If the whole field is empty and that's OK, stop now.
if (empty($form_input[$field_name]) && !$element['#required']) {
return;
}
$item = $form_values[$field_name][$langcode][$delta];
$posted = $form_input[$field_name][$langcode][$delta];
$field = field_widget_field($element, $form_state);
$instance = field_widget_instance($element, $form_state);
$context = array(
'field' => $field,
'instance' => $instance,
'item' => $item,
);
drupal_alter('date_combo_pre_validate', $element, $form_state, $context);
$from_field = 'value';
$to_field = 'value2';
$tz_field = 'timezone';
$offset_field = 'offset';
$offset_field2 = 'offset2';
// Check for empty 'Start date', which could either be an empty
// value or an array of empty values, depending on the widget.
$empty = TRUE;
if (!empty($item[$from_field])) {
if (!is_array($item[$from_field])) {
$empty = FALSE;
}
else {
foreach ($item[$from_field] as $key => $value) {
if (!empty($value)) {
$empty = FALSE;
break;
}
}
}
}
// An 'End' date without a 'Start' date is a validation error.
if ($empty && !empty($item[$to_field])) {
if (!is_array($item[$to_field])) {
form_error($element, t("A 'Start date' date is required if an 'end date' is supplied for field %field #%delta.", array('%delta' => $field['cardinality'] ? intval($delta + 1) : '', '%field' => $instance['label'])));
$empty = FALSE;
}
else {
foreach ($item[$to_field] as $key => $value) {
if (!empty($value)) {
form_error($element, t("A 'Start date' date is required if an 'End date' is supplied for field %field #%delta.", array('%delta' => $field['cardinality'] ? intval($delta + 1) : '', '%field' => $instance['label'])));
$empty = FALSE;
break;
}
}
}
}
// If the user chose the option to not show the end date, just swap in the
// start date as that value so the start and end dates are the same.
if ($field['settings']['todate'] == 'optional' && empty($item['show_todate'])) {
$item[$to_field] = $item[$from_field];
$posted[$to_field] = $posted[$from_field];
}
if ($empty) {
$item = date_element_empty($element, $form_state);
if (!$element['#required']) {
return;
}
}
// Don't look for further errors if errors are already flagged
// because otherwise we'll show errors on the nested elements
// more than once.
elseif (!form_get_errors()) {
$timezone = !empty($item[$tz_field]) ? $item[$tz_field] : $element['#date_timezone'];
$timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
$element[$from_field]['#date_timezone'] = $timezone;
$from_date = date_input_date($field, $instance, $element[$from_field], $posted[$from_field]);
if (!empty($field['settings']['todate'])) {
$element[$to_field]['#date_timezone'] = $timezone;
$to_date = date_input_date($field, $instance, $element[$to_field], $posted[$to_field]);
}
else {
$to_date = $from_date;
}
// Neither the start date nor the end date should be empty at this point
// unless they held values that couldn't be evaluated.
if (!$instance['required'] && (!date_is_date($from_date) || !date_is_date($to_date))) {
$item = date_element_empty($element, $form_state);
$errors[] = t('The dates are invalid.');
}
elseif (!empty($field['settings']['todate']) && $from_date > $to_date) {
form_set_value($element[$to_field], $to_date, $form_state);
$errors[] = t('The End date must be greater than the Start date.');
}
else {
// Convert input dates back to their UTC values and re-format to ISO
// or UNIX instead of the DATETIME format used in element processing.
$item[$tz_field] = $timezone;
// Update the context for changes in the $item, and allow other modules to
// alter the computed local dates.
$context['item'] = $item;
// We can only pass two additional values to drupal_alter, so $element
// needs to be included in $context.
$context['element'] = $element;
drupal_alter('date_combo_validate_date_start', $from_date, $form_state, $context);
drupal_alter('date_combo_validate_date_end', $to_date, $form_state, $context);
$item[$offset_field] = date_offset_get($from_date);
$test_from = date_format($from_date, 'r');
$test_to = date_format($to_date, 'r');
$item[$offset_field2] = date_offset_get($to_date);
date_timezone_set($from_date, timezone_open($timezone_db));
date_timezone_set($to_date, timezone_open($timezone_db));
$item[$from_field] = date_format($from_date, date_type_format($field['type']));
$item[$to_field] = date_format($to_date, date_type_format($field['type']));
if (isset($form_values[$field_name]['rrule'])) {
$item['rrule'] = $form_values[$field['field_name']]['rrule'];
}
// If the db timezone is not the same as the display timezone
// and we are using a date with time granularity,
// test a roundtrip back to the original timezone to catch
// invalid dates, like 2AM on the day that spring daylight savings
// time begins in the US.
$granularity = date_format_order($element[$from_field]['#date_format']);
if ($timezone != $timezone_db && date_has_time($granularity)) {
date_timezone_set($from_date, timezone_open($timezone));
date_timezone_set($to_date, timezone_open($timezone));
if ($test_from != date_format($from_date, 'r')) {
$errors[] = t('The Start date is invalid.');
}
if ($test_to != date_format($to_date, 'r')) {
$errors[] = t('The End date is invalid.');
}
}
if (empty($errors)) {
form_set_value($element, $item, $form_state);
}
}
}
if (!empty($errors)) {
if ($field['cardinality']) {
form_error($element, t('There are errors in @field_name value #@delta:', array('@field_name' => $instance['label'], '@delta' => $delta + 1)) . theme('item_list', array('items' => $errors)));
}
else {
form_error($element, t('There are errors in @field_name:', array('@field_name' => $instance['label'])) . theme('item_list', array('items' => $errors)));
}
}
}
/**
* Determine the input format for this element.
*/
function date_input_format($element, $field, $instance) {
if (!empty($instance['widget']['settings']['input_format_custom'])) {
return $instance['widget']['settings']['input_format_custom'];
}
elseif (!empty($instance['widget']['settings']['input_format']) && $instance['widget']['settings']['input_format'] != 'site-wide') {
return $instance['widget']['settings']['input_format'];
}
return variable_get('date_format_short', 'm/d/Y - H:i');
}
/**
* Implements hook_date_select_pre_validate_alter().
*/
function date_date_select_pre_validate_alter(&$element, &$form_state, &$input) {
date_empty_end_date($element, $form_state, $input);
}
/**
* Implements hook_date_text_pre_validate_alter().
*/
function date_date_text_pre_validate_alter(&$element, &$form_state, &$input) {
date_empty_end_date($element, $form_state, $input);
}
/**
* Implements hook_date_popup_pre_validate_alter().
*/
function date_date_popup_pre_validate_alter(&$element, &$form_state, &$input) {
date_empty_end_date($element, $form_state, $input);
}
/**
* Helper function to clear out end date when not being used.
*/
function date_empty_end_date(&$element, &$form_state, &$input) {
// If this is the end date and the option to show an end date has not been selected,
// empty the end date to surpress validation errors and stop further processing.
$parents = $element['#parents'];
$parent = array_pop($parents);
if ($parent == 'value2') {
$parent_values = drupal_array_get_nested_value($form_state['values'], $parents);
if (isset($parent_values['show_todate']) && $parent_values['show_todate'] != 1) {
$input = array();
form_set_value($element, NULL, $form_state);
}
}
}

View File

@@ -0,0 +1,175 @@
<?php
/**
* @file
* Support for migration into Date fields.
*/
class DateMigrateFieldHandler extends MigrateFieldHandler {
/**
* Declares the types of fields used.
*/
public function __construct() {
$this->registerTypes(array('date', 'datestamp', 'datetime'));
}
/**
* Arguments for a date field migration.
*
* @param string $timezone
* Timezone (such as UTC, America/New_York, etc.) to apply.
* @param string $timezone_db
* Timezone_db value for the field.
* @param string $rrule
* Rule string for a repeating date field.
* @param string $language
* Language of the text (defaults to destination language)
*
* @return array
* An array of the defined variables in this scope.
*/
static function arguments($timezone = 'UTC', $timezone_db = 'UTC', $rrule = NULL, $language = NULL) {
return get_defined_vars();
}
/**
* Converts incoming data into the proper field arrays for Date fields.
*
* @param object $entity
* The destination entity which will hold the field arrays.
* @param array $field_info
* Metadata for the date field being populated.
* @param array $instance
* Metadata for this instance of the date field being populated.
* @param array $values
* Array of date values to be fielded.
*
* @return array|null
* An array of date fields.
*/
public function prepare($entity, array $field_info, array $instance, array $values) {
if (isset($values['arguments'])) {
$arguments = $values['arguments'];
unset($values['arguments']);
}
else {
$arguments = array();
}
if (isset($arguments['timezone'])) {
$default_timezone = $arguments['timezone'];
}
else {
$default_timezone = 'UTC';
}
if (isset($arguments['timezone_db'])) {
$default_timezone_db = $arguments['timezone_db'];
}
else {
$default_timezone_db = NULL;
}
if (isset($arguments['rrule'])) {
$default_rrule = $arguments['rrule'];
}
else {
$default_rrule = NULL;
}
$language = $this->getFieldLanguage($entity, $field_info, $arguments);
// Setup the standard Field API array for saving.
$delta = 0;
foreach ($values as $from) {
// Set defaults.
$to = NULL;
$timezone = $default_timezone;
$timezone_db = $default_timezone_db;
$rrule = $default_rrule;
// Is the value a straight datetime value, or JSON containing a set of
// properties?
if (!empty($from) && $from{0} == '{') {
$properties = drupal_json_decode($from);
$from = $properties['from'];
// Properties passed in with the date override any set via arguments.
if (!empty($properties['to'])) {
$to = $properties['to'];
}
if (!empty($properties['timezone'])) {
$timezone = $properties['timezone'];
}
if (!empty($properties['timezone_db'])) {
$timezone_db = $properties['timezone_db'];
}
if (!empty($properties['rrule'])) {
$rrule = $properties['rrule'];
}
}
// Missing data? Create an empty value and return;
// Don't try to turn the empty value into a bogus
// timestamp for 'now'.
if (empty($from)) {
$return[$language][$delta]['value'] = NULL;
if (!empty($field_info['settings']['todate'])) {
$return[$language][$delta]['value2'] = NULL;
}
return $return;
}
// If there is no 'to' date, just use the 'from' date.
if (!empty($field_info['settings']['todate']) && empty($to)) {
$to = $from;
}
// If we have a value, work from a timestamp.
$from = MigrationBase::timestamp($from);
if ($to) {
$to = MigrationBase::timestamp($to);
}
// What does the destination field expect?
switch ($field_info['type']) {
case 'datestamp':
// Already done.
break;
case 'datetime':
// YYYY-MM-DD HH:MM:SS.
$from = format_date($from, 'custom', 'Y-m-d H:i:s', $timezone);
if ($to) {
$to = format_date($to, 'custom', 'Y-m-d H:i:s', $timezone);
}
break;
case 'date':
// ISO date: YYYY-MM-DDTHH:MM:SS.
$from = format_date($from, 'custom', 'Y-m-d\TH:i:s', $timezone);
if ($to) {
$to = format_date($to, 'custom', 'Y-m-d\TH:i:s', $timezone);
}
break;
default:
break;
}
// Handle repeats, coming in as RRULEs. Many field instances may be
// created.
if (function_exists('date_repeat_build_dates') && !empty($field_info['settings']['repeat']) && $rrule) {
include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc';
$item = array('value' => $from, 'value2' => $to, 'timezone' => $timezone);
// Can be de-uglified when http://drupal.org/node/1159404 is committed.
$return[$language] = date_repeat_build_dates(NULL, date_ical_parse_rrule($field_info, $rrule), $field_info, $item);
}
else {
$return[$language][$delta]['value'] = $from;
if (!empty($to)) {
$return[$language][$delta]['value2'] = $to;
}
}
$delta++;
}
if (!isset($return)) {
$return = NULL;
}
return $return;
}
}

View File

@@ -0,0 +1,16 @@
name = Date Migration
description = Provides support for importing into date fields with the Migrate module.
core = 7.x
package = Date/Time
dependencies[] = migrate
dependencies[] = date
files[] = date.migrate.inc
files[] = date_migrate.test
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,16 @@
<?php
/**
* @file
* Migration integration for Date Migrate.
*/
/**
* Implements hook_migrate_api().
*/
function date_migrate_migrate_api() {
$api = array(
'api' => 2,
);
return $api;
}

View File

@@ -0,0 +1,70 @@
<?php
/**
* @file
* Test for using date fields with Migrate module.
*/
/**
* Test date migration.
*/
class DateMigrateExampleUnitTest extends DrupalWebTestCase {
/**
* Provides information about this test.
*/
public static function getInfo() {
return array(
'name' => 'Date2 migration',
'description' => 'Testing migration of date fields',
'group' => 'Migrate',
);
}
/**
* Declars the module dependencies for the test.
*/
function setUp() {
parent::setUp('migrate', 'features', 'date', 'date_repeat', 'date_repeat_field', 'date_migrate_example');
}
/**
* Verify that date fields are imported correctly. When no timezone is
* explicitly provided with the source data, we want the displayed time on the
* Drupal site to match that in the source data. To validate that, we make
* sure we have set a consistent timezone at the PHP and Drupal levels, and
* that the format used on the page is not locale-dependent (no day or month
* names). Then, we can just look for the desired date/time strings in the
* node page.
*/
function testDateImport() {
date_default_timezone_set('America/Los_Angeles');
variable_set('date_default_timezone', 'America/Los_Angeles');
variable_set('date_format_medium', 'Y-m-d H:i');
$migration = Migration::getInstance('DateExample');
$result = $migration->processImport();
$this->assertEqual($result, Migration::RESULT_COMPLETED, t('Variety term import returned RESULT_COMPLETED'));
$rawnodes = node_load_multiple(FALSE, array('type' => 'date_migrate_example'), TRUE);
$this->assertEqual(count($rawnodes), 2, t('Two sample nodes created'));
$node = reset($rawnodes);
$this->drupalGet('/node/' . $node->nid);
$this->assertText('2011-05-12 19:43', t('Simple date field found'));
$this->assertText('2011-06-13 18:32 to 2011-07-23 10:32', t('Date range field found'));
$this->assertText('2011-07-22 12:13', t('Datestamp field found'));
$this->assertText('2011-08-01 00:00 to 2011-09-01 00:00', t('Datestamp range field found'));
$this->assertText('2011-11-18 15:00', t('Datetime field with +9 timezone found'));
$this->assertText('2011-10-30 14:43 to 2011-12-31 17:59', t('Datetime range field with -5 timezone found'));
$this->assertText('2011-11-25 09:01', t('First date repeat instance found'));
$this->assertText('2011-12-09 09:01', t('Second date repeat instance found'));
$this->assertNoText('2011-12-23 09:01', t('Skipped date repeat instance not found'));
$this->assertText('2012-05-11 09:01', t('Last date repeat instance found'));
$node = next($rawnodes);
$this->drupalGet('/node/' . $node->nid);
$this->assertText('2012-06-21 15:32', t('First date value found'));
$this->assertText('2012-12-02 11:08', t('Second date value found'));
$this->assertText('2004-02-03 01:15', t('Start for first date range found'));
$this->assertText('2005-03-04 22:11', t('End for first date range found'));
$this->assertText('2014-09-01 17:21', t('Start for second date range found'));
$this->assertText('2015-12-23 00:01', t('End for first second range found'));
}
}

View File

@@ -0,0 +1,680 @@
<?php
/**
* @file
* Examples and test folder for migration into date fields.
*/
/**
* Implements hook_field_default_fields().
*/
function date_migrate_example_field_default_fields() {
$fields = array();
// Exported field: 'node-date_migrate_example-body'
$fields['node-date_migrate_example-body'] = array(
'field_config' => array(
'active' => '1',
'cardinality' => '1',
'deleted' => '0',
'entity_types' => array(
'0' => 'node',
),
'field_name' => 'body',
'foreign keys' => array(
'format' => array(
'columns' => array(
'format' => 'format',
),
'table' => 'filter_format',
),
),
'indexes' => array(
'format' => array(
'0' => 'format',
),
),
'module' => 'text',
'settings' => array(),
'translatable' => '1',
'type' => 'text_with_summary',
),
'field_instance' => array(
'bundle' => 'date_migrate_example',
'default_value' => NULL,
'deleted' => '0',
'description' => '',
'display' => array(
'default' => array(
'label' => 'hidden',
'module' => 'text',
'settings' => array(),
'type' => 'text_default',
'weight' => '0',
),
'teaser' => array(
'label' => 'hidden',
'module' => 'text',
'settings' => array(
'trim_length' => 600,
),
'type' => 'text_summary_or_trimmed',
'weight' => 0,
),
),
'entity_type' => 'node',
'field_name' => 'body',
'label' => 'Body',
'required' => FALSE,
'settings' => array(
'display_summary' => TRUE,
'text_processing' => 1,
'user_register_form' => FALSE,
),
'widget' => array(
'module' => 'text',
'settings' => array(
'rows' => 20,
'summary_rows' => 5,
),
'type' => 'text_textarea_with_summary',
'weight' => '1',
),
),
);
// Exported field: 'node-date_migrate_example-field_date'
$fields['node-date_migrate_example-field_date'] = array(
'field_config' => array(
'active' => '1',
'cardinality' => '-1',
'deleted' => '0',
'entity_types' => array(),
'field_name' => 'field_date',
'foreign keys' => array(),
'indexes' => array(),
'module' => 'date',
'settings' => array(
'granularity' => array(
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'month' => 'month',
'year' => 'year',
),
'repeat' => 0,
'timezone_db' => 'UTC',
'todate' => '',
'tz_handling' => 'site',
),
'translatable' => '1',
'type' => 'date',
),
'field_instance' => array(
'bundle' => 'date_migrate_example',
'deleted' => '0',
'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
'module' => 'date',
'settings' => array(
'format_type' => 'medium',
'fromto' => 'both',
'multiple_from' => '',
'multiple_number' => '',
'multiple_to' => '',
'show_repeat_rule' => 'show',
),
'type' => 'date_default',
'weight' => '1',
),
'teaser' => array(
'label' => 'above',
'settings' => array(),
'type' => 'hidden',
'weight' => 0,
),
),
'entity_type' => 'node',
'field_name' => 'field_date',
'label' => 'Date',
'required' => 0,
'settings' => array(
'default_format' => 'medium',
'default_value' => 'now',
'default_value2' => 'blank',
'default_value_code' => '',
'default_value_code2' => '',
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
'module' => 'date',
'settings' => array(
'increment' => 1,
'input_format' => 'm/d/Y - H:i:s',
'input_format_custom' => '',
'label_position' => 'above',
'repeat_collapsed' => 0,
'text_parts' => array(),
'year_range' => '-3:+3',
),
'type' => 'date_text',
'weight' => '2',
),
),
);
// Exported field: 'node-date_migrate_example-field_date_range'
$fields['node-date_migrate_example-field_date_range'] = array(
'field_config' => array(
'active' => '1',
'cardinality' => '-1',
'deleted' => '0',
'entity_types' => array(),
'field_name' => 'field_date_range',
'foreign keys' => array(),
'indexes' => array(),
'module' => 'date',
'settings' => array(
'granularity' => array(
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'month' => 'month',
'year' => 'year',
),
'repeat' => 0,
'timezone_db' => 'UTC',
'todate' => 'required',
'tz_handling' => 'site',
),
'translatable' => '1',
'type' => 'date',
),
'field_instance' => array(
'bundle' => 'date_migrate_example',
'deleted' => '0',
'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
'module' => 'date',
'settings' => array(
'format_type' => 'medium',
'fromto' => 'both',
'multiple_from' => '',
'multiple_number' => '',
'multiple_to' => '',
'show_repeat_rule' => 'show',
),
'type' => 'date_default',
'weight' => '2',
),
'teaser' => array(
'label' => 'above',
'settings' => array(),
'type' => 'hidden',
'weight' => 0,
),
),
'entity_type' => 'node',
'field_name' => 'field_date_range',
'label' => 'Date range',
'required' => 0,
'settings' => array(
'default_format' => 'medium',
'default_value' => 'now',
'default_value2' => 'strtotime',
'default_value_code' => '',
'default_value_code2' => '+7 days',
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
'module' => 'date',
'settings' => array(
'increment' => 1,
'input_format' => 'm/d/Y - H:i:s',
'input_format_custom' => '',
'label_position' => 'above',
'repeat_collapsed' => 0,
'text_parts' => array(),
'year_range' => '-3:+3',
),
'type' => 'date_text',
'weight' => '3',
),
),
);
// Exported field: 'node-date_migrate_example-field_date_repeat'
$fields['node-date_migrate_example-field_date_repeat'] = array(
'field_config' => array(
'active' => '1',
'cardinality' => '-1',
'deleted' => '0',
'entity_types' => array(),
'field_name' => 'field_date_repeat',
'foreign keys' => array(),
'indexes' => array(),
'module' => 'date',
'settings' => array(
'granularity' => array(
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'month' => 'month',
'year' => 'year',
),
'repeat' => 1,
'timezone_db' => 'UTC',
'todate' => '',
'tz_handling' => 'site',
),
'translatable' => '1',
'type' => 'date',
),
'field_instance' => array(
'bundle' => 'date_migrate_example',
'deleted' => '0',
'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
'module' => 'date',
'settings' => array(
'format_type' => 'medium',
'fromto' => 'both',
'multiple_from' => '',
'multiple_number' => '',
'multiple_to' => '',
'show_repeat_rule' => 'show',
),
'type' => 'date_default',
'weight' => '7',
),
'teaser' => array(
'label' => 'above',
'settings' => array(),
'type' => 'hidden',
'weight' => 0,
),
),
'entity_type' => 'node',
'field_name' => 'field_date_repeat',
'label' => 'Date with repeat',
'required' => 0,
'settings' => array(
'default_format' => 'medium',
'default_value' => 'now',
'default_value2' => 'blank',
'default_value_code' => '',
'default_value_code2' => '',
'repeat_collapsed' => '0',
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
'module' => 'date',
'settings' => array(
'increment' => 1,
'input_format' => 'm/d/Y - H:i:s',
'input_format_custom' => '',
'label_position' => 'above',
'repeat_collapsed' => 0,
'text_parts' => array(),
'year_range' => '-3:+3',
),
'type' => 'date_text_repeat',
'weight' => '8',
),
),
);
// Exported field: 'node-date_migrate_example-field_datestamp'
$fields['node-date_migrate_example-field_datestamp'] = array(
'field_config' => array(
'active' => '1',
'cardinality' => '1',
'deleted' => '0',
'entity_types' => array(),
'field_name' => 'field_datestamp',
'foreign keys' => array(),
'indexes' => array(),
'module' => 'date',
'settings' => array(
'granularity' => array(
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'month' => 'month',
'year' => 'year',
),
'repeat' => 0,
'timezone_db' => 'UTC',
'todate' => '',
'tz_handling' => 'site',
),
'translatable' => '1',
'type' => 'datestamp',
),
'field_instance' => array(
'bundle' => 'date_migrate_example',
'deleted' => '0',
'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
'module' => 'date',
'settings' => array(
'format_type' => 'medium',
'fromto' => 'both',
'multiple_from' => '',
'multiple_number' => '',
'multiple_to' => '',
'show_repeat_rule' => 'show',
),
'type' => 'date_default',
'weight' => '3',
),
'teaser' => array(
'label' => 'above',
'settings' => array(),
'type' => 'hidden',
'weight' => 0,
),
),
'entity_type' => 'node',
'field_name' => 'field_datestamp',
'label' => 'Datestamp',
'required' => 0,
'settings' => array(
'default_format' => 'medium',
'default_value' => 'now',
'default_value2' => 'blank',
'default_value_code' => '',
'default_value_code2' => '',
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
'module' => 'date',
'settings' => array(
'increment' => 1,
'input_format' => 'm/d/Y - H:i:s',
'input_format_custom' => '',
'label_position' => 'above',
'repeat_collapsed' => 0,
'text_parts' => array(),
'year_range' => '-3:+3',
),
'type' => 'date_text',
'weight' => '4',
),
),
);
// Exported field: 'node-migrate_example_date-field_datestamp_range'
$fields['node-date_migrate_example-field_datestamp_range'] = array(
'field_config' => array(
'active' => '1',
'cardinality' => '1',
'deleted' => '0',
'entity_types' => array(),
'field_name' => 'field_datestamp_range',
'foreign keys' => array(),
'indexes' => array(),
'module' => 'date',
'settings' => array(
'granularity' => array(
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'month' => 'month',
'year' => 'year',
),
'repeat' => 0,
'timezone_db' => 'UTC',
'todate' => 'optional',
'tz_handling' => 'site',
),
'translatable' => '1',
'type' => 'datestamp',
),
'field_instance' => array(
'bundle' => 'date_migrate_example',
'deleted' => '0',
'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
'module' => 'date',
'settings' => array(
'format_type' => 'medium',
'fromto' => 'both',
'multiple_from' => '',
'multiple_number' => '',
'multiple_to' => '',
'show_repeat_rule' => 'show',
),
'type' => 'date_default',
'weight' => '4',
),
'teaser' => array(
'label' => 'above',
'settings' => array(),
'type' => 'hidden',
'weight' => 0,
),
),
'entity_type' => 'node',
'field_name' => 'field_datestamp_range',
'label' => 'Datestamp range',
'required' => 0,
'settings' => array(
'default_format' => 'medium',
'default_value' => 'now',
'default_value2' => 'strtotime',
'default_value_code' => '',
'default_value_code2' => '+3 months',
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
'module' => 'date',
'settings' => array(
'increment' => '1',
'input_format' => 'm/d/Y - H:i:s',
'input_format_custom' => '',
'label_position' => 'above',
'repeat_collapsed' => 0,
'text_parts' => array(),
'year_range' => '-3:+3',
),
'type' => 'date_select',
'weight' => '5',
),
),
);
// Exported field: 'node-date_migrate_example-field_datetime'
$fields['node-date_migrate_example-field_datetime'] = array(
'field_config' => array(
'active' => '1',
'cardinality' => '1',
'deleted' => '0',
'entity_types' => array(),
'field_name' => 'field_datetime',
'foreign keys' => array(),
'indexes' => array(),
'module' => 'date',
'settings' => array(
'granularity' => array(
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'month' => 'month',
'year' => 'year',
),
'repeat' => 0,
'timezone_db' => 'UTC',
'todate' => '',
'tz_handling' => 'site',
),
'translatable' => '1',
'type' => 'datetime',
),
'field_instance' => array(
'bundle' => 'date_migrate_example',
'deleted' => '0',
'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
'module' => 'date',
'settings' => array(
'format_type' => 'medium',
'fromto' => 'both',
'multiple_from' => '',
'multiple_number' => '',
'multiple_to' => '',
'show_repeat_rule' => 'show',
),
'type' => 'date_default',
'weight' => '5',
),
'teaser' => array(
'label' => 'above',
'settings' => array(),
'type' => 'hidden',
'weight' => 0,
),
),
'entity_type' => 'node',
'field_name' => 'field_datetime',
'label' => 'Datetime',
'required' => 0,
'settings' => array(
'default_format' => 'medium',
'default_value' => 'now',
'default_value2' => 'blank',
'default_value_code' => '',
'default_value_code2' => '',
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
'module' => 'date',
'settings' => array(
'increment' => 1,
'input_format' => 'm/d/Y - H:i:s',
'input_format_custom' => '',
'label_position' => 'above',
'repeat_collapsed' => 0,
'text_parts' => array(),
'year_range' => '-3:+3',
),
'type' => 'date_text',
'weight' => '6',
),
),
);
// Exported field: 'node-date_migrate_example-field_datetime_range'
$fields['node-date_migrate_example-field_datetime_range'] = array(
'field_config' => array(
'active' => '1',
'cardinality' => '1',
'deleted' => '0',
'entity_types' => array(),
'field_name' => 'field_datetime_range',
'foreign keys' => array(),
'indexes' => array(),
'module' => 'date',
'settings' => array(
'granularity' => array(
'day' => 'day',
'hour' => 'hour',
'minute' => 'minute',
'month' => 'month',
'year' => 'year',
),
'repeat' => 0,
'timezone_db' => 'UTC',
'todate' => 'required',
'tz_handling' => 'site',
),
'translatable' => '1',
'type' => 'datetime',
),
'field_instance' => array(
'bundle' => 'date_migrate_example',
'deleted' => '0',
'description' => '',
'display' => array(
'default' => array(
'label' => 'above',
'module' => 'date',
'settings' => array(
'format_type' => 'medium',
'fromto' => 'both',
'multiple_from' => '',
'multiple_number' => '',
'multiple_to' => '',
'show_repeat_rule' => 'show',
),
'type' => 'date_default',
'weight' => '6',
),
'teaser' => array(
'label' => 'above',
'settings' => array(),
'type' => 'hidden',
'weight' => 0,
),
),
'entity_type' => 'node',
'field_name' => 'field_datetime_range',
'label' => 'Datetime range',
'required' => 0,
'settings' => array(
'default_format' => 'medium',
'default_value' => 'now',
'default_value2' => 'blank',
'default_value_code' => '',
'default_value_code2' => '+1 year',
'user_register_form' => FALSE,
),
'widget' => array(
'active' => 1,
'module' => 'date',
'settings' => array(
'increment' => 1,
'input_format' => 'm/d/Y - H:i:s',
'input_format_custom' => '',
'label_position' => 'above',
'repeat_collapsed' => 0,
'text_parts' => array(),
'year_range' => '-3:+3',
),
'type' => 'date_text',
'weight' => '7',
),
),
);
// Translatables
// Included for use with string extractors like potx.
t('Body');
t('Date');
t('Date range');
t('Date with repeat');
t('Datestamp');
t('Datestamp range');
t('Datetime');
t('Datetime range');
return $fields;
}

View File

@@ -0,0 +1,23 @@
<?php
/**
* @file
* Examples and test folder for migration into date fields.
*/
/**
* Implements hook_node_info().
*/
function date_migrate_example_node_info() {
$items = array(
'date_migrate_example' => array(
'name' => t('Migrate example - dates'),
'base' => 'node_content',
'description' => t('This content type is used for demonstrating and testing migration into Date fields.'),
'has_title' => '1',
'title_label' => t('Title'),
'help' => '',
),
);
return $items;
}

View File

@@ -0,0 +1,29 @@
core = "7.x"
dependencies[] = "date"
dependencies[] = "date_repeat"
dependencies[] = "date_repeat_field"
dependencies[] = "date_migrate"
dependencies[] = "features"
dependencies[] = "migrate"
description = "Examples of migrating with the Date module"
features[field][] = "node-date_migrate_example-body"
features[field][] = "node-date_migrate_example-field_date"
features[field][] = "node-date_migrate_example-field_date_range"
features[field][] = "node-date_migrate_example-field_date_repeat"
features[field][] = "node-date_migrate_example-field_datestamp"
features[field][] = "node-date_migrate_example-field_datestamp_range"
features[field][] = "node-date_migrate_example-field_datetime"
features[field][] = "node-date_migrate_example-field_datetime_range"
features[node][] = "date_migrate_example"
files[] = date_migrate_example.migrate.inc
name = "Date Migration Example"
package = "Features"
project = "date_migrate_example"
version = "7.x-2.0"
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,14 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Date Migrate Example module.
*/
/**
* Implements hook_disable().
*/
function date_migrate_example_disable() {
Migration::deregisterMigration('DateExample');
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* @file
* Examples and test folder for migration into date fields.
*/
/**
* Migration class to test import of various date fields.
*/
class DateExampleMigration extends XMLMigration {
/**
* Sets up the migration.
*/
public function __construct() {
parent::__construct();
$this->description = t('Example migration into date fields');
$this->map = new MigrateSQLMap($this->machineName,
array(
'id' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => 'Date ID',
),
),
MigrateDestinationNode::getKeySchema()
);
// Source fields available in the XML file.
$fields = array(
'id' => t('Source id'),
'title' => t('Title'),
'body' => t('Description'),
'date' => t('A simple date'),
'date_range_from' => t('Start value for a date range'),
'datestamp' => t('Simple datestamp'),
'datestamp_range_from' => t('Start value for a datestamp range'),
'datetime' => t('Simple datetime'),
'datetime_range_from' => t('Start value for a datetime range'),
'date_repeat' => t('Sample of a repeating date field'),
);
// Our test data is in an XML file.
$xml_folder = drupal_get_path('module', 'date_migrate_example');
$items_url = $xml_folder . '/date_migrate_example.xml';
$item_xpath = '/source_data/item';
$item_ID_xpath = 'id';
$items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath);
$this->source = new MigrateSourceMultiItems($items_class, $fields);
$this->destination = new MigrateDestinationNode('date_migrate_example');
// Basic fields.
$this->addFieldMapping('title', 'title')
->xpath('title');
$this->addFieldMapping('uid')
->defaultValue(1);
$this->addFieldMapping('body', 'body')
->xpath('body');
// For simple date fields, we just need the xpath.
$this->addFieldMapping('field_date', 'date')
->xpath('date');
// For date ranges, we add the "end" value in prepareRow() below.
$this->addFieldMapping('field_date_range', 'date_range_from');
// RRULEs on repeat fields are also done in prepareRow().
$this->addFieldMapping('field_date_repeat', 'date_repeat');
$this->addFieldMapping('field_datestamp', 'datestamp')
->xpath('datestamp');
$this->addFieldMapping('field_datestamp_range', 'datestamp_range_from');
// You can specify a timezone to be applied to all values going into the
// field (Tokyo is UTC+9, no DST)
$arguments = DateMigrateFieldHandler::arguments('Asia/Tokyo');
$this->addFieldMapping('field_datetime', 'datetime')
->xpath('datetime')
->arguments($arguments);
// You can also get the timezone from the source data - it can be different
// for each instance of the field. Like To and RRULE values, it is added
// in prepareRow().
$this->addFieldMapping('field_datetime_range', 'datetime_range_from');
// Unmapped destination fields.
$this->addUnmigratedDestinations(array('is_new', 'status', 'promote', 'revision', 'language', 'sticky', 'created', 'changed', 'revision_uid'));
}
/**
* Transforms the raw migration data into the expected date formats.
*
* An advanced feature of the date field handler is that in addition to the
* basic (Start) date itself, we can add additional properties like timezone,
* encapsulating them as JSON.
*/
public function prepareRow($current_row) {
// The date range field can have multiple values.
$current_row->date_range_from = array();
foreach ($current_row->xml->date_range as $range) {
$date_data = array(
'from' => (string) $range->from[0],
'to' => (string) $range->to[0],
);
$current_row->date_range_from[] = drupal_json_encode($date_data);
}
$date_data = array(
'from' => (string) $current_row->xml->datestamp_range->from[0],
'to' => (string) $current_row->xml->datestamp_range->to[0],
);
$current_row->datestamp_range_from = drupal_json_encode($date_data);
$date_data = array(
'from' => (string) $current_row->xml->datetime_range->from[0],
'to' => (string) $current_row->xml->datetime_range->to[0],
'timezone' => (string) $current_row->xml->datetime_range->timezone[0],
);
$current_row->datetime_range_from = drupal_json_encode($date_data);
$date_data = array(
'from' => (string) $current_row->xml->date_repeat->date[0],
'rrule' => (string) $current_row->xml->date_repeat->rule[0],
);
$current_row->date_repeat = drupal_json_encode($date_data);
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* @file
* Migration integration for Date Migrate Example.
*/
include_once 'date_migrate_example.features.inc';
/**
* Implements hook_migrate_api().
*/
function date_migrate_example_migrate_api() {
$api = array(
'api' => 2,
);
return $api;
}

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<source_data>
<item>
<id>3</id>
<title>Simple example</title>
<body>This is pretty straight-forward.</body>
<date>05/12/2011 19:43</date>
<date_range>
<from>06/13/2011 6:32pm</from>
<to>07/23/2011 10:32am</to>
</date_range>
<datestamp>07/22/2011 12:13</datestamp>
<datestamp_range>
<from>8/1/2011 00:00</from>
<to>9/1/2011 00:00</to>
</datestamp_range>
<datetime>11/18/2011 06:00</datetime>
<datetime_range>
<from>10/30/2011 19:43</from>
<to>12/31/2011 23:59</to>
<timezone>America/Chicago</timezone>
</datetime_range>
<date_repeat>
<date>11/25/2011 9:01</date>
<rule>RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20120512T040000Z;WKST=MO
EXDATE:20111223</rule>
</date_repeat>
</item>
<item>
<id>8</id>
<title>Example with multi-value fields</title>
<body>This is not as straight-forward.</body>
<date>06/21/2012 15:32</date>
<date>12/02/2012 11:08</date>
<date_range>
<from>02/03/2004 1:15am</from>
<to>03/04/2005 10:11pm</to>
</date_range>
<date_range>
<from>09/01/2014 5:21pm</from>
<to>12/23/2015 00:01</to>
</date_range>
<datestamp>07/22/2011 12:13</datestamp>
<datestamp_range>
<from>8/1/2011 00:00</from>
<to>9/1/2011 00:00</to>
</datestamp_range>
<datetime>11/18/2011 06:00</datetime>
<datetime_range>
<from>10/30/2011 19:43</from>
<to>12/31/2011 23:59</to>
<timezone>America/Chicago</timezone>
</datetime_range>
<date_repeat>
<date>11/25/2011 9:01</date>
<rule>RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20120512T040000Z;WKST=MO
EXDATE:20111223</rule>
</date_repeat>
</item>
</source_data>

View File

@@ -0,0 +1,108 @@
Drupal date_popup.module README.txt
==============================================================================
Javascript popup calendar and timeentry using the
jquery UI calendar and a choice of jquery-timeentry libraries.
================================================================================
Datepicker
================================================================================
This code uses the jQuery UI datepicker that is included in core. Localization
of the interface is handled by core.
The popup will use the site default for the first day of the week.
================================================================================
Timepicker
================================================================================
There are three ways to let users select time in the Date Popup widgets.
You can choose between them by going to admin/config/content/date_popup.
The options are:
1) Manual time entry - a plain textfield where users can type in the time.
2) A 'default' jQuery timepicker, included in the code
(http://keith-wood.name/timeEntry.html).
3) The wvega timepicker (https://github.com/wvega/timepicker).
To install the alternate dropdown (wvega) timepicker:
Create a 'sites/all/libraries/wvega-timepicker' directory in your site
installation. Then visit https://github.com/wvega/timepicker/archives/master,
download the latest copy and unzip it. You will see files with names like
jquery.timepicker-1.1.2.js and jquery.timepicker-1.1.2.css. Rename them to
jquery.timepicker.js and jquery.timepicker.css and copy them into
'sites/all/libraries/wvega-timepicker'.
================================================================================
Usage
================================================================================
To include a popup calendar in a form, use the type 'date_popup':
$form['date'] = array(
'#type' => 'date_popup':
'#title => t('My Date'),
....
);
Set the #type to date_popup and fill the element #default_value with
a date adjusted to the proper local timezone, or leave it blank.
The element will create two textfields, one for the date and one for the
time. The date textfield will include a jQuery popup calendar date picker,
and the time textfield uses a jQuery timepicker.
NOTE - Converting a date stored in the database from UTC to the local zone
and converting it back to UTC before storing it is not handled by this
element and must be done in pre-form and post-form processing!!
================================================================================
Customization
================================================================================
To change the default display and functionality of the calendar, set startup
parameters by adding selectors to your element. The configurable options
are:
#date_type
The type of date to convert the input value to, DATE_DATETIME, DATE_ISO, or
DATE_UNIX
#date_format
a standard PHP date format string that represents the way the month, day,
and year will be displayed in the textfield, like m/d/Y. Months and days
must be in the 'm' and 'd' formats that include the zero prefix, the year
must be in the 'Y' (four digit) format.
Any standard separator can be used, '/', '-', '.', or a space.
The m, d, and Y elements can be in any order and the order will be preserved.
The time selector will add AM/PM if 'a' is in the format string.
The default format uses the short site default format.
#date_year_range
the number of years to go backwards and forwards from current year
in year selector, in the format -{years back}:+{years forward},
like -3:+3
#date_increment
increment minutes and seconds by this amount, default is 1
================================================================================
Example:
================================================================================
$form['date'] = array(
'#type' => 'date_popup',
'#default_value' => '2007-01-01 10:30:00,
'#date_type' => DATE_DATETIME,
'#date_timezone' => date_default_timezone(),
'#date_format' => 'm/d/Y - H:i',
'#date_increment' => 1,
'#date_year_range' => '-3:+3',
);

View File

@@ -0,0 +1,15 @@
name = Date Popup
description = Enables jquery popup calendars and time entry widgets for selecting dates and times.
dependencies[] = date_api
package = Date/Time
core = 7.x
configure = admin/config/date/date_popup
stylesheets[all][] = themes/datepicker.1.7.css
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,33 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Date Popup module.
*/
/**
* Implements hook_install().
*/
function date_popup_install() {
}
/**
* Implements hook_uninstall().
*/
function date_popup_uninstall() {
}
/**
* Implements hook_enable().
*/
function date_popup_enable() {
}
/**
* Implements hook_disable().
*/
function date_popup_disable() {
}

View File

@@ -0,0 +1,62 @@
/**
* Attaches the calendar behavior to all required fields
*/
(function ($) {
Drupal.behaviors.date_popup = {
attach: function (context) {
for (var id in Drupal.settings.datePopup) {
$('#'+ id).bind('focus', Drupal.settings.datePopup[id], function(e) {
if (!$(this).hasClass('date-popup-init')) {
var datePopup = e.data;
// Explicitely filter the methods we accept.
switch (datePopup.func) {
case 'datepicker':
$(this)
.datepicker(datePopup.settings)
.addClass('date-popup-init')
$(this).click(function(){
$(this).focus();
});
break;
case 'timeEntry':
$(this)
.timeEntry(datePopup.settings)
.addClass('date-popup-init')
$(this).click(function(){
$(this).focus();
});
break;
case 'timepicker':
// Translate the PHP date format into the style the timepicker uses.
datePopup.settings.timeFormat = datePopup.settings.timeFormat
// 12-hour, leading zero,
.replace('h', 'hh')
// 12-hour, no leading zero.
.replace('g', 'h')
// 24-hour, leading zero.
.replace('H', 'HH')
// 24-hour, no leading zero.
.replace('G', 'H')
// AM/PM.
.replace('A', 'p')
// Minutes with leading zero.
.replace('i', 'mm')
// Seconds with leading zero.
.replace('s', 'ss');
datePopup.settings.startTime = new Date(datePopup.settings.startTime);
$(this)
.timepicker(datePopup.settings)
.addClass('date-popup-init');
$(this).click(function(){
$(this).focus();
});
break;
}
}
});
}
}
};
})(jQuery);

View File

@@ -0,0 +1,738 @@
<?php
/**
* @file
* A module to enable jquery calendar and time entry popups.
* Requires the Date API.
*
* Add a type of #date_popup to any date, time, or datetime field that will
* use this popup. Set #date_format to the way the date should be presented
* to the user in the form. Set #default_value to be a date in the local
* timezone, and note the timezone name in #date_timezone.
*
* The element will create two textfields, one for the date and one for the
* time. The date textfield will include a jQuery popup calendar date picker,
* and the time textfield uses a jQuery timepicker.
*
* If no time elements are included in the format string, only the date
* textfield will be created. If no date elements are included in the format
* string, only the time textfield, will be created.
*
*/
/**
* Load needed files.
*
* Play nice with jQuery UI.
*/
function date_popup_add() {
static $loaded = FALSE;
if ($loaded) {
return;
}
drupal_add_library('system', 'ui.datepicker');
drupal_add_library('date_popup', 'timeentry');
// Add the wvega-timepicker library if it's available.
$wvega_path = date_popup_get_wvega_path();
if ($wvega_path) {
drupal_add_js($wvega_path . '/jquery.timepicker.js');
drupal_add_css($wvega_path . '/jquery.timepicker.css');
}
$loaded = TRUE;
}
/**
* Get the location of the Willington Vega timepicker library.
*
* @return
* The location of the library, or FALSE if the library isn't installed.
*/
function date_popup_get_wvega_path() {
$path = FALSE;
if (function_exists('libraries_get_path')) {
$path = libraries_get_path('wvega-timepicker');
if (!file_exists($path)) {
$path = FALSE;
}
}
elseif (file_exists('sites/all/libraries/wvega-timepicker/jquery.timepicker.js')) {
$path = 'sites/all/libraries/wvega-timepicker';
}
return $path;
}
/**
* Get the name of the preferred default timepicker.
*
* If the wvega timepicker is available on the system, default to using that,
* unless the administrator has specifically chosen otherwise.
*/
function date_popup_get_preferred_timepicker() {
$wvega_available = date_popup_get_wvega_path();
return $wvega_available ? 'wvega' : 'default';
}
/**
* Implements hook_library().
*/
function date_popup_library() {
$libraries = array();
$path = drupal_get_path('module', 'date_popup');
$libraries['timeentry'] = array(
'title' => 'Time Entry',
'website' => 'http://plugins.jquery.com/project/timeEntry',
'version' => '1.4.7',
'js' => array(
$path . '/jquery.timeentry.pack.js' => array(),
),
'css' => array(
$path . '/themes/jquery.timeentry.css' => array('preprocess' => FALSE),
),
);
return $libraries;
}
/**
* Create a unique CSS id name and output a single inline JS block for
* each startup function to call and settings array to pass it. This
* used to create a unique CSS class for each unique combination of
* function and settings, but using classes requires a DOM traversal
* and is much slower than an id lookup. The new approach returns to
* requiring a duplicate copy of the settings/code for every element
* that uses them, but is much faster. We could combine the logic by
* putting the ids for each unique function/settings combo into
* Drupal.settings and searching for each listed id.
*
* @param $pfx
* The CSS class prefix to search the DOM for.
* TODO : unused ?
* @param $func
* The jQuery function to invoke on each DOM element containing the
* returned CSS class.
* @param $settings
* The settings array to pass to the jQuery function.
* @returns
* The CSS id to assign to the element that should have
* $func($settings) invoked on it.
*/
function date_popup_js_settings_id($id, $func, $settings) {
static $js_added = FALSE;
static $id_count = array();
// Make sure popup date selector grid is in correct year.
if (!empty($settings['yearRange'])) {
$parts = explode(':', $settings['yearRange']);
// Set the default date to 0 or the lowest bound if the date ranges do not include the current year
// Necessary for the datepicker to render and select dates correctly
$defaultDate = ($parts[0] > 0 || 0 > $parts[1]) ? $parts[0] : 0;
$settings += array('defaultDate' => (string) $defaultDate . 'y');
}
if (!$js_added) {
drupal_add_js(drupal_get_path('module', 'date_popup') .'/date_popup.js');
$js_added = TRUE;
}
// We use a static array to account for possible multiple form_builder()
// calls in the same request (form instance on 'Preview').
if (!isset($id_count[$id])) {
$id_count[$id] = 0;
}
// It looks like we need the additional id_count for this to
// work correctly when there are multiple values.
// $return_id = "$id-$func-popup";
$return_id = "$id-$func-popup-". $id_count[$id]++;
$js_settings['datePopup'][$return_id] = array(
'func' => $func,
'settings' => $settings
);
drupal_add_js($js_settings, 'setting');
return $return_id;
}
function date_popup_theme() {
return array(
'date_popup' => array('render element' => 'element'),
);
}
/**
* Implements hook_element_info().
*
* Set the #type to date_popup and fill the element #default_value with
* a date adjusted to the proper local timezone in datetime format (YYYY-MM-DD HH:MM:SS).
*
* The element will create two textfields, one for the date and one for the
* time. The date textfield will include a jQuery popup calendar date picker,
* and the time textfield uses a jQuery timepicker.
*
* NOTE - Converting a date stored in the database from UTC to the local zone
* and converting it back to UTC before storing it is not handled by this
* element and must be done in pre-form and post-form processing!!
*
* #date_timezone
* The local timezone to be used to create this date.
*
* #date_format
* Unlike earlier versions of this popup, most formats will work.
*
* #date_increment
* Increment minutes and seconds by this amount, default is 1.
*
* #date_year_range
* The number of years to go back and forward in a year selector,
* default is -3:+3 (3 back and 3 forward).
*
* #datepicker_options
* An associative array representing the jQuery datepicker options you want
* to set for this element. Use the jQuery datepicker option names as keys.
* Hard coded defaults are:
* - changeMonth => TRUE
* - changeYear => TRUE
* - autoPopUp => 'focus'
* - closeAtTop => FALSE
* - speed => 'immediate'
*/
function date_popup_element_info() {
$timepicker = date_popup_get_preferred_timepicker();
$type['date_popup'] = array(
'#input' => TRUE,
'#tree' => TRUE,
'#date_timezone' => date_default_timezone(),
'#date_flexible' => 0,
'#date_format' => variable_get('date_format_short', 'm/d/Y - H:i'),
'#datepicker_options' => array(),
'#timepicker' => variable_get('date_popup_timepicker', $timepicker),
'#date_increment' => 1,
'#date_year_range' => '-3:+3',
'#date_label_position' => 'above',
'#process' => array('date_popup_element_process'),
'#value_callback' => 'date_popup_element_value_callback',
'#theme_wrappers' => array('date_popup'),
);
if (module_exists('ctools')) {
$type['date_popup']['#pre_render'] = array('ctools_dependent_pre_render');
}
return $type;
}
function date_popup_date_granularity($element) {
$granularity = date_format_order($element['#date_format']);
return array_intersect($granularity, array('month', 'day', 'year'));
}
function date_popup_time_granularity($element) {
$granularity = date_format_order($element['#date_format']);
return array_intersect($granularity, array('hour', 'minute', 'second'));
}
function date_popup_date_format($element) {
return (date_limit_format($element['#date_format'], date_popup_date_granularity($element)));
}
function date_popup_time_format($element) {
return date_popup_format_to_popup_time(date_limit_format($element['#date_format'], date_popup_time_granularity($element)), $element['#timepicker']);
}
/**
* Element value callback for date_popup element.
*/
function date_popup_element_value_callback($element, $input = FALSE, &$form_state) {
$granularity = date_format_order($element['#date_format']);
$has_time = date_has_time($granularity);
$date = NULL;
$return = $has_time ? array('date' => '', 'time' => '') : array('date' => '');
// Normal input from submitting the form element.
// Check is_array() to skip the string input values created by Views pagers.
// Those string values, if present, should be interpreted as empty input.
if ($input !== FALSE && is_array($input)) {
$return = $input;
$date = date_popup_input_date($element, $input);
}
// No input? Try the default value.
elseif (!empty($element['#default_value'])) {
$date = date_default_date($element);
}
// Date with errors won't re-display.
if (date_is_date($date)) {
$return['date'] = !$date->timeOnly ? date_format_date($date, 'custom', date_popup_date_format($element)) : '';
$return['time'] = $has_time ? date_format_date($date, 'custom', date_popup_time_format($element)) : '';
}
elseif (!empty($input)) {
$return = $input;
}
return $return;
}
/**
* Javascript popup element processing.
* Add popup attributes to $element.
*/
function date_popup_element_process($element, &$form_state, $form) {
if (date_hidden_element($element)) {
return $element;
}
date_popup_add();
module_load_include('inc', 'date_api', 'date_api_elements');
$element['#tree'] = TRUE;
$element['#theme_wrappers'] = array('date_popup');
if (!empty($element['#ajax'])) {
$element['#ajax'] += array(
'trigger_as' => array('name' =>$element['#name']),
'event' => 'change',
);
}
$element['date'] = date_popup_process_date_part($element);
$element['time'] = date_popup_process_time_part($element);
if (isset($element['#element_validate'])) {
array_push($element['#element_validate'], 'date_popup_validate');
}
else {
$element['#element_validate'] = array('date_popup_validate');
}
$context = array(
'form' => $form,
);
drupal_alter('date_popup_process', $element, $form_state, $context);
return $element;
}
/**
* Process the date portion of the element.
*/
function date_popup_process_date_part(&$element) {
$granularity = date_format_order($element['#date_format']);
$date_granularity = date_popup_date_granularity($element);
if (empty($date_granularity)) return array();
// The datepicker can't handle zero or negative values like 0:+1
// even though the Date API can handle them, so rework the value
// we pass to the datepicker to use defaults it can accept (such as +0:+1)
// date_range_string() adds the necessary +/- signs to the range string.
$this_year = date_format(date_now(), 'Y');
$date = '';
if (!empty($element['#value']['date'])) {
$date = new DateObject($element['#value']['date'], $element['#date_timezone'], date_popup_date_format($element));
}
$range = date_range_years($element['#date_year_range'], $date);
$year_range = date_range_string($range);
// Add the dynamic datepicker options. Allow element-specific datepicker
// preferences to override these options for whatever reason they see fit.
$settings = $element['#datepicker_options'] + array(
'changeMonth' => TRUE,
'changeYear' => TRUE,
'autoPopUp' => 'focus',
'closeAtTop' => FALSE,
'speed' => 'immediate',
'firstDay' => intval(variable_get('date_first_day', 0)),
//'buttonImage' => base_path() . drupal_get_path('module', 'date_api') ."/images/calendar.png",
//'buttonImageOnly' => TRUE,
'dateFormat' => date_popup_format_to_popup(date_popup_date_format($element), 'datepicker'),
'yearRange' => $year_range,
// Custom setting, will be expanded in Drupal.behaviors.date_popup()
'fromTo' => isset($fromto),
);
// Create a unique id for each set of custom settings.
$id = date_popup_js_settings_id($element['#id'], 'datepicker', $settings);
// Manually build this element and set the value - this will prevent corrupting
// the parent value
$parents = array_merge($element['#parents'], array('date'));
$sub_element = array(
'#type' => 'textfield',
'#title' => $element['#date_label_position'] == 'above' ? theme('date_part_label_date', array('part_type' => 'date', 'element' => $element)) : '',
'#default_value' => $element['#value']['date'],
'#id' => $id,
'#input' => FALSE,
'#size' => !empty($element['#size']) ? $element['#size'] : 20,
'#maxlength' => !empty($element['#maxlength']) ? $element['#maxlength'] : 30,
'#attributes' => $element['#attributes'],
'#parents' => $parents,
'#name' => array_shift($parents) . '['. implode('][', $parents) .']',
'#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
);
$sub_element['#value'] = $sub_element['#default_value'];
// TODO, figure out exactly when we want this description. In many places it is not desired.
$sub_element['#description'] = ' '. t('E.g., @date', array('@date' => date_format_date(date_example_date(), 'custom', date_popup_date_format($element))));
return $sub_element;
}
/**
* Process the time portion of the element.
*/
function date_popup_process_time_part(&$element) {
$granularity = date_format_order($element['#date_format']);
$has_time = date_has_time($granularity);
if (empty($has_time)) return array();
switch ($element['#timepicker']) {
case 'default':
$func = 'timeEntry';
$settings = array(
'show24Hours' => strpos($element['#date_format'], 'H') !== FALSE ? TRUE : FALSE,
'showSeconds' => (in_array('second', $granularity) ? TRUE : FALSE),
'timeSteps' => array(1, intval($element['#date_increment']), (in_array('second', $granularity) ? $element['#date_increment'] : 0)),
'spinnerImage' => '',
'fromTo' => isset($fromto),
);
if (strpos($element['#date_format'], 'a') !== FALSE) {
// Then we are using lowercase am/pm.
$settings['ampmNames'] = array('am', 'pm');
}
if (strpos($element['#date_format'], ' A') !== FALSE || strpos($element['#date_format'], ' a') !== FALSE) {
$settings['ampmPrefix'] = ' ';
}
break;
case 'wvega':
$func = 'timepicker';
$time_granularity = array_intersect($granularity, array('hour', 'minute', 'second'));
$format = date_popup_format_to_popup_time(date_limit_format($element['#date_format'], $time_granularity), 'wvega');
// The first value in the dropdown list should be the same as the element
// default_value, but it needs to be in JS format (i.e. milliseconds since
// the epoch).
$start_time = new DateObject($element['#default_value'], $element['#date_timezone'], DATE_FORMAT_DATETIME);
date_increment_round($start_time, $element['#date_increment']);
$start_time = $start_time->format(DATE_FORMAT_UNIX) * 1000;
$settings = array(
'timeFormat' => $format,
'interval' => $element['#date_increment'],
'startTime' => $start_time,
'scrollbar' => TRUE,
);
break;
default:
$func = '';
$settings = array();
break;
}
// Create a unique id for each set of custom settings.
$id = date_popup_js_settings_id($element['#id'], $func, $settings);
// Manually build this element and set the value - this will prevent corrupting
// the parent value
$parents = array_merge($element['#parents'], array('time'));
$sub_element = array(
'#type' => 'textfield',
'#title' => $element['#date_label_position'] == 'above' ? theme('date_part_label_time', array('part_type' => 'time', 'element' => $element)) : '',
'#default_value' => $element['#value']['time'],
'#id' => $id,
'#size' => 15,
'#maxlength' => 10,
'#attributes' => $element['#attributes'],
'#parents' => $parents,
'#name' => array_shift($parents) . '['. implode('][', $parents) .']',
'#ajax' => !empty($element['#ajax']) ? $element['#ajax'] : FALSE,
);
$sub_element['#value'] = $sub_element['#default_value'];
// TODO, figure out exactly when we want this description. In many places it is not desired.
$example_date = date_now();
date_increment_round($example_date, $element['#date_increment']);
$sub_element['#description'] = t('E.g., @date', array('@date' => date_format_date($example_date, 'custom', date_popup_time_format($element))));
return ($sub_element);
}
/**
* Massage the input values back into a single date.
*
* When used as a Views widget, the validation step always gets triggered,
* even with no form submission. Before form submission $element['#value']
* contains a string, after submission it contains an array.
*
*/
function date_popup_validate($element, &$form_state) {
if (date_hidden_element($element)) {
return;
}
if (is_string($element['#value'])) {
return;
}
module_load_include('inc', 'date_api', 'date_api_elements');
date_popup_add();
$input_exists = NULL;
$input = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
drupal_alter('date_popup_pre_validate', $element, $form_state, $input);
$granularity = date_format_order($element['#date_format']);
$date_granularity = date_popup_date_granularity($element);
$time_granularity = date_popup_time_granularity($element);
$has_time = date_has_time($granularity);
$label = !empty($element['#date_title']) ? $element['#date_title'] : (!empty($element['#title']) ? $element['#title'] : '');
$label = t($label);
$date = date_popup_input_date($element, $input);
// If the date has errors, display them.
// If something was input but there is no date, the date is invalid.
// If the field is empty and required, set error message and return.
$error_field = implode('][', $element['#parents']);
if (empty($date) || !empty($date->errors)) {
if (is_object($date) && !empty($date->errors)) {
$message = t('The value input for field %field is invalid:', array('%field' => $label));
$message .= '<br />' . implode('<br />', $date->errors);
form_set_error($error_field, $message);
return;
}
if (!empty($input['date'])) {
$message = t('The value input for field %field is invalid.', array('%field' => $label));
form_set_error($error_field, $message);
return;
}
if ($element['#required']) {
$message = t('A valid date is required for %title.', array('%title' => $label));
form_set_error($error_field, $message);
return;
}
}
// If the created date is valid, set it.
$value = !empty($date) ? $date->format(DATE_FORMAT_DATETIME) : NULL;
form_set_value($element, $value, $form_state);
}
/**
* Helper function for extracting a date value out of user input.
*
* @param autocomplete
* Should we add a time value to complete the date if there is no time?
* Useful anytime the time value is optional.
*/
function date_popup_input_date($element, $input, $auto_complete = FALSE) {
if (empty($input) || !is_array($input) || !array_key_exists('date', $input) || empty($input['date'])) {
return NULL;
}
date_popup_add();
$granularity = date_format_order($element['#date_format']);
$has_time = date_has_time($granularity);
$flexible = !empty($element['#date_flexible']) ? $element['#date_flexible'] : 0;
$format = date_popup_date_format($element);
$format .= $has_time ? ' ' . date_popup_time_format($element) : '';
$datetime = $input['date'];
$datetime .= $has_time ? ' ' . $input['time'] : '';
$date = new DateObject($datetime, $element['#date_timezone'], $format);
if (is_object($date)) {
$date->limitGranularity($granularity);
if ($date->validGranularity($granularity, $flexible)) {
date_increment_round($date, $element['#date_increment']);
}
return $date;
}
return NULL;
}
/**
* Allowable time formats.
*/
function date_popup_time_formats($with_seconds = FALSE) {
return array(
'H:i:s',
'h:i:sA',
);
}
/**
* Format options array.
*
* TODO Remove any formats not supported by the widget, if any.
*/
function date_popup_formats() {
$formats = str_replace('i', 'i:s', array_keys(system_get_date_formats('short')));
$formats = drupal_map_assoc($formats);
return $formats;
}
/**
* Recreate a date format string so it has the values popup expects.
*
* @param string $format
* a normal date format string, like Y-m-d
* @return string
* A format string in popup format, like YMD-, for the
* earlier 'calendar' version, or m/d/Y for the later 'datepicker'
* version.
*/
function date_popup_format_to_popup($format) {
if (empty($format)) {
$format = 'Y-m-d';
}
$replace = date_popup_datepicker_format_replacements();
return strtr($format, $replace);
}
/**
* Recreate a time format string so it has the values popup expects.
*
* @param string $format
* a normal time format string, like h:i (a)
* @return string
* a format string that the popup can accept like h:i a
*/
function date_popup_format_to_popup_time($format, $timepicker = NULL) {
if (empty($format)) {
$format = 'H:i';
}
$format = str_replace(array('/', '-', ' .', ',', 'F', 'M', 'l', 'z', 'w', 'W', 'd', 'j', 'm', 'n', 'y', 'Y'), '', $format);
$format = strtr($format, date_popup_timepicker_format_replacements($timepicker));
return $format;
}
/**
* Reconstruct popup format string into normal format string.
*
* @param string $format
* a string in popup format, like YMD-
* @return string
* a normal date format string, like Y-m-d
*/
function date_popup_popup_to_format($format) {
$replace = array_flip(date_popup_datepicker_format_replacements());
return strtr($format, $replace);
}
/**
* Return a map of format replacements required for a given timepicker.
*
* Client-side time entry plugins don't support all possible date formats.
* This function returns a map of format replacements required to change any
* input format into one that the given timepicker can support.
*
* @param $timepicker
* The time entry plugin being used: either 'wvega' or 'default'.
* @return
* A map of replacements.
*/
function date_popup_timepicker_format_replacements($timepicker = 'default') {
switch ($timepicker) {
case 'wvega':
return array(
'a' => 'A', // The wvega timepicker only supports uppercase AM/PM.
);
default:
return array(
'G' => 'H', // The default timeEntry plugin requires leading zeros.
'g' => 'h',
);
}
}
/**
* The format replacement patterns for the new datepicker.
*/
function date_popup_datepicker_format_replacements() {
return array(
'd' => 'dd',
'j' => 'd',
'l' => 'DD',
'D' => 'D',
'm' => 'mm',
'n' => 'm',
'F' => 'MM',
'M' => 'M',
'Y' => 'yy',
'y' => 'y',
);
}
/**
* Format a date popup element.
*
* Use a class that will float date and time next to each other.
*/
function theme_date_popup($vars) {
$element = $vars['element'];
$attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
$attributes['class'][] = 'container-inline-date';
// If there is no description, the floating date elements need some extra padding below them.
$wrapper_attributes = array('class' => array('date-padding'));
if (empty($element['date']['#description'])) {
$wrapper_attributes['class'][] = 'clearfix';
}
// Add an wrapper to mimic the way a single value field works, for ease in using #states.
if (isset($element['#children'])) {
$element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) .'>' . $element['#children'] . '</div>';
}
return '<div ' . drupal_attributes($attributes) .'>' . theme('form_element', $element) . '</div>';
}
/**
* Implements hook_menu().
*/
function date_popup_menu() {
$items = array();
// TODO Fix this later.
$items['admin/config/date/date_popup'] = array(
'title' => 'Date Popup',
'description' => 'Configure the Date Popup settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array('date_popup_settings'),
'access callback' => 'user_access',
'access arguments' => array('administer site configuration'),
);
return $items;
}
/**
* General configuration form for controlling the Date Popup behaviour.
*/
function date_popup_settings() {
$wvega_available = date_popup_get_wvega_path();
$preferred_timepicker = date_popup_get_preferred_timepicker();
$form['#prefix'] = t('<p>The Date Popup module allows for manual time entry or use of a jQuery timepicker plugin. The Date module comes with a default jQuery timepicker which is already installed. The module also supports a dropdown timepicker that must be downloaded separately. The dropdown timepicker will not appear as an option until the code is available in the libraries folder. If you do not want to use a jQuery timepicker, you can choose the "Manual time entry" option below and users will get a regular textfield instead.</p>');
$form['date_popup_timepicker'] = array(
'#type' => 'select',
'#options' => array(
'default' => t('Use default jQuery timepicker'),
'wvega' => t('Use dropdown timepicker'),
'none' => t('Manual time entry, no jQuery timepicker')
),
'#title' => t('Timepicker'),
'#default_value' => variable_get('date_popup_timepicker', $preferred_timepicker),
);
if (!$wvega_available) {
$form['#prefix'] .= t('<p>To install the dropdown timepicker, create a <code>!directory</code> directory in your site installation. Then visit <a href="@download">@download</a>, download the latest copy and unzip it. You will see files with names like jquery.timepicker-1.1.2.js and jquery.timepicker-1.1.2.css. Rename them to jquery.timepicker.js and jquery.timepicker.css and copy them into <code>!directory</code>.</p>', array('!directory' => 'sites/all/libraries/wvega-timepicker', '@download' => 'https://github.com/wvega/timepicker/archives/master'));
unset($form['date_popup_timepicker']['#options']['wvega']);
}
$css = <<<EOM
/* ___________ IE6 IFRAME FIX ________ */
.ui-datepicker-cover {
display: none; /*sorry for IE5*/
display/**/: block; /*sorry for IE5*/
position: absolute; /*must have*/
z-index: -1; /*must have*/
filter: mask(); /*must have*/
top: -4px; /*must have*/
left: -4px; /*must have*/ /* LTR */
width: 200px; /*must have*/
height: 200px; /*must have*/
}
EOM;
$form['#suffix'] = t('<p>The Date Popup calendar includes some css for IE6 that breaks css validation. Since IE 6 is now superceded by IE 7, 8, and 9, the special css for IE 6 has been removed from the regular css used by the Date Popup. If you find you need that css after all, you can add it back in your theme. Look at the way the Garland theme adds special IE-only css in in its page.tpl.php file. The css you need is:</p>') .'<blockquote><PRE>' . $css .'</PRE></blockquote>';
return system_settings_form($form);
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
#ui-datepicker-div {
font-size: 100%;
font-family: Verdana, sans-serif;
background: #eee;
border-right:2px #666 solid;
border-bottom:2px #666 solid;
z-index: 9999;
}
/* Datepicker
----------------------------------*/
.ui-datepicker { width: 17em; padding: .2em .2em 0; }
.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
.ui-datepicker .ui-datepicker-prev { left:2px; }
.ui-datepicker .ui-datepicker-next { right:2px; }
.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
.ui-datepicker .ui-datepicker-next-hover { right:1px; }
.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
.ui-datepicker .ui-datepicker-title select { float:left; font-size:1em; margin:1px 0; }
.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year { width: 49%;}
.ui-datepicker .ui-datepicker-title select.ui-datepicker-year { float: right; }
.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
.ui-datepicker td { border: 0; padding: 1px; }
.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
/* with multiple calendars */
.ui-datepicker.ui-datepicker-multi { width:auto; }
.ui-datepicker-multi .ui-datepicker-group { float:left; }
.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
.ui-datepicker-row-break { clear:both; width:100%; }
/* RTL support */
.ui-datepicker-rtl { direction: rtl; }
.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
.ui-datepicker-rtl .ui-datepicker-group { float:right; }
.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }

View File

@@ -0,0 +1,8 @@
/* TimeEntry styles */
.timeEntry_control {
vertical-align: middle;
margin-left: 2px;
}
* html .timeEntry_control { /* IE only */
margin-top: -4px;
}

View File

@@ -0,0 +1,6 @@
<?php
/**
* @file
* Empty file to avoid fatal error if it doesn't exist.
* Formerly the Date Repeat field code.
*/

View File

@@ -0,0 +1,15 @@
name = Date Repeat API
description = A Date Repeat API to calculate repeating dates and times from iCal rules.
dependencies[] = date_api
package = Date/Time
core = 7.x
php = 5.2
files[] = tests/date_repeat.test
files[] = tests/date_repeat_form.test
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,32 @@
<?php
/**
* @file
* Install, update and uninstall functions for the Date Repeat module.
*/
/**
* Implements hook_install().
*/
function date_repeat_install() {
// Make sure this module loads after date_api.
db_query("UPDATE {system} SET weight = 1 WHERE name = 'date_repeat'");
}
/**
* Implements hook_uninstall().
*/
function date_repeat_uninstall() {
}
/**
* Implements hook_enable().
*/
function date_repeat_enable() {
}
/**
* Implements hook_disable().
*/
function date_repeat_disable() {
}

View File

@@ -0,0 +1,419 @@
<?php
/**
* @file
*
* This module creates a form element that allows users to select
* repeat rules for a date, and reworks the result into an iCal
* RRULE string that can be stored in the database.
*
* The module also parses iCal RRULEs to create an array of dates
* that meet their criteria.
*
* Other modules can use this API to add self-validating form elements
* to their dates, and identify dates that meet the RRULE criteria.
*
*/
/**
* Implements hook_element_info().
*/
function date_repeat_element_info() {
$type['date_repeat_rrule'] = array(
'#input' => TRUE,
'#process' => array('date_repeat_rrule_process'),
'#element_validate' => array('date_repeat_rrule_validate'),
'#theme_wrappers' => array('date_repeat_rrule'),
);
$type['date_repeat_form_element_radios'] = array(
'#input' => TRUE,
'#process' => array('date_repeat_form_element_radios_process'),
'#theme_wrappers' => array('radios'),
'#pre_render' => array('form_pre_render_conditional_form_element'),
);
if (module_exists('ctools')) {
$type['date_repeat_rrule']['#pre_render'] = array('ctools_dependent_pre_render');
}
return $type;
}
function date_repeat_theme() {
return array(
'date_repeat_current_exceptions' => array('render element' => 'element'),
'date_repeat_current_additions' => array('render element' => 'element'),
'date_repeat_rrule' => array('render element' => 'element'),
);
}
/**
* Helper function for FREQ options.
*/
function date_repeat_freq_options() {
return array(
'DAILY' => t('Daily', array(), array('context' => 'datetime_singular')),
'WEEKLY' => t('Weekly', array(), array('context' => 'datetime_singular')),
'MONTHLY' => t('Monthly', array(), array('context' => 'datetime_singular')),
'YEARLY' => t('Yearly', array(), array('context' => 'datetime_singular')),
);
}
function date_repeat_interval_options() {
$options = range(0, 366);
unset($options[0]);
return $options;
}
/**
* Helper function for FREQ options.
*
* Translated and untranslated arrays of the iCal day of week names.
* We need the untranslated values for date_modify(), translated
* values when displayed to user.
*/
function date_repeat_dow_day_options($translated = TRUE) {
return array(
'SU' => $translated ? t('Sunday', array(), array('context' => 'day_name')) : 'Sunday',
'MO' => $translated ? t('Monday', array(), array('context' => 'day_name')) : 'Monday',
'TU' => $translated ? t('Tuesday', array(), array('context' => 'day_name')) : 'Tuesday',
'WE' => $translated ? t('Wednesday', array(), array('context' => 'day_name')) : 'Wednesday',
'TH' => $translated ? t('Thursday', array(), array('context' => 'day_name')) : 'Thursday',
'FR' => $translated ? t('Friday', array(), array('context' => 'day_name')) : 'Friday',
'SA' => $translated ? t('Saturday', array(), array('context' => 'day_name')) : 'Saturday',
);
}
/**
* Helper function for FREQ options.
*
* Translated and untranslated arrays of the iCal abbreviated day of week names.
*/
function date_repeat_dow_day_options_abbr($translated = TRUE, $length = 3) {
$return = array();
switch ($length) {
case 1:
$context = 'day_abbr1';
break;
case 2:
$context = 'day_abbr2';
break;
default:
$context = '';
break;
}
foreach (date_repeat_dow_day_untranslated() as $key => $day) {
$return[$key] = $translated ? t(substr($day, 0, $length), array(), array('context' => $context)) : substr($day, 0, $length);
}
return $return;
}
function date_repeat_dow_day_untranslated() {
static $date_repeat_weekdays;
if (empty($date_repeat_weekdays)) {
$date_repeat_weekdays = array('SU' => 'Sunday', 'MO' => 'Monday', 'TU' => 'Tuesday',
'WE' => 'Wednesday', 'TH' => 'Thursday', 'FR' => 'Friday',
'SA' => 'Saturday');
}
return $date_repeat_weekdays;
}
function date_repeat_dow_day_options_ordered($weekdays) {
$day_keys = array_keys($weekdays);
$day_values = array_values($weekdays);
for ($i = 1; $i <= variable_get('date_first_day', 0); $i++) {
$last_key = array_shift($day_keys);
array_push($day_keys, $last_key);
$last_value = array_shift($day_values);
array_push($day_values, $last_value);
}
$weekdays = array_combine($day_keys, $day_values);
return $weekdays;
}
/**
* Helper function for BYDAY options.
*/
function date_repeat_dow_count_options() {
return array('' => t('Every', array(), array('context' => 'date_order'))) + date_order_translated();
}
/**
* Helper function for BYDAY options.
*
* Creates options like -1SU and 2TU
*/
function date_repeat_dow_options() {
$options = array();
foreach (date_repeat_dow_count_options() as $count_key => $count_value) {
foreach (date_repeat_dow_day_options() as $dow_key => $dow_value) {
$options[$count_key . $dow_key] = $count_value . ' ' . $dow_value;
}
}
return $options;
}
/**
* Translate a day of week position to the iCal day name.
*
* Used with date_format($date, 'w') or get_variable('date_first_day'),
* which return 0 for Sunday, 1 for Monday, etc.
*
* dow 2 becomes 'TU', dow 3 becomes 'WE', and so on.
*/
function date_repeat_dow2day($dow) {
$days_of_week = array_keys(date_repeat_dow_day_options(FALSE));
return $days_of_week[$dow];
}
/**
* Shift the array of iCal day names into the right order
* for a specific week start day.
*/
function date_repeat_days_ordered($week_start_day) {
$days = array_flip(array_keys(date_repeat_dow_day_options(FALSE)));
$start_position = $days[$week_start_day];
$keys = array_flip($days);
if ($start_position > 0) {
for ($i = 1; $i <= $start_position; $i++) {
$last = array_shift($keys);
array_push($keys, $last);
}
}
return $keys;
}
/**
* Build a description of an iCal rule.
*
* Constructs a human-readable description of the rule.
*/
function date_repeat_rrule_description($rrule, $format = 'D M d Y') {
// Empty or invalid value.
if (empty($rrule) || !strstr($rrule, 'RRULE')) {
return;
}
module_load_include('inc', 'date_api', 'date_api_ical');
module_load_include('inc', 'date_repeat', 'date_repeat_calc');
$parts = date_repeat_split_rrule($rrule);
$additions = $parts[2];
$exceptions = $parts[1];
$rrule = $parts[0];
if ($rrule['FREQ'] == 'NONE') {
return;
}
// Make sure there will be an empty description for any unused parts.
$description = array(
'!interval' => '',
'!byday' => '',
'!bymonth' => '',
'!count' => '',
'!until' => '',
'!except' => '',
'!additional' => '',
'!week_starts_on' => '',
);
$interval = date_repeat_interval_options();
switch ($rrule['FREQ']) {
case 'WEEKLY':
$description['!interval'] = format_plural($rrule['INTERVAL'], 'every week', 'every @count weeks') . ' ';
break;
case 'MONTHLY':
$description['!interval'] = format_plural($rrule['INTERVAL'], 'every month', 'every @count months') . ' ';
break;
case 'YEARLY':
$description['!interval'] = format_plural($rrule['INTERVAL'], 'every year', 'every @count years') . ' ';
break;
default:
$description['!interval'] = format_plural($rrule['INTERVAL'], 'every day', 'every @count days') . ' ';
break;
}
if (!empty($rrule['BYDAY'])) {
$days = date_repeat_dow_day_options();
$counts = date_repeat_dow_count_options();
$results = array();
foreach ($rrule['BYDAY'] as $byday) {
// Get the numeric part of the BYDAY option, i.e. +3 from +3MO.
$day = substr($byday, -2);
$count = str_replace($day, '', $byday);
if (!empty($count)) {
// See if there is a 'pretty' option for this count, i.e. +1 => First.
$order = array_key_exists($count, $counts) ? strtolower($counts[$count]) : $count;
$results[] = trim(t('!repeats_every_interval on the !date_order !day_of_week', array('!repeats_every_interval ' => '', '!date_order' => $order, '!day_of_week' => $days[$day])));
}
else {
$results[] = trim(t('!repeats_every_interval every !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => $days[$day])));
}
}
$description['!byday'] = implode(' ' . t('and') . ' ', $results);
}
if (!empty($rrule['BYMONTH'])) {
if (sizeof($rrule['BYMONTH']) < 12) {
$results = array();
$months = date_month_names();
foreach ($rrule['BYMONTH'] as $month) {
$results[] = $months[$month];
}
if (!empty($rrule['BYMONTHDAY'])) {
$description['!bymonth'] = trim(t('!repeats_every_interval on the !month_days of !month_names', array('!repeats_every_interval ' => '', '!month_days' => implode(', ', $rrule['BYMONTHDAY']), '!month_names' => implode(', ', $results))));
}
else {
$description['!bymonth'] = trim(t('!repeats_every_interval on !month_names', array('!repeats_every_interval ' => '', '!month_names' => implode(', ', $results))));
}
}
}
if ($rrule['INTERVAL'] < 1) {
$rrule['INTERVAL'] = 1;
}
if (!empty($rrule['COUNT'])) {
$description['!count'] = trim(t('!repeats_every_interval !count times', array('!repeats_every_interval ' => '', '!count' => $rrule['COUNT'])));
}
if (!empty($rrule['UNTIL'])) {
$until = date_ical_date($rrule['UNTIL'], 'UTC');
date_timezone_set($until, date_default_timezone_object());
$description['!until'] = trim(t('!repeats_every_interval until !until_date', array('!repeats_every_interval ' => '', '!until_date' => date_format_date($until, 'custom', $format))));
}
if ($exceptions) {
$values = array();
foreach ($exceptions as $exception) {
$except = date_ical_date($exception, 'UTC');
date_timezone_set($except, date_default_timezone_object());
$values[] = date_format_date($except, 'custom', $format);
}
$description['!except'] = trim(t('!repeats_every_interval except !except_dates', array('!repeats_every_interval ' => '', '!except_dates' => implode(', ', $values))));
}
if (!empty($rrule['WKST'])) {
$day_names = date_repeat_dow_day_options();
$description['!week_starts_on'] = trim(t('!repeats_every_interval where the week start on !day_of_week', array('!repeats_every_interval ' => '', '!day_of_week' => $day_names[trim($rrule['WKST'])])));
}
if ($additions) {
$values = array();
foreach ($additions as $addition) {
$add = date_ical_date($addition, 'UTC');
date_timezone_set($add, date_default_timezone_object());
$values[] = date_format_date($add, 'custom', $format);
}
$description['!additional'] = trim(t('Also includes !additional_dates.', array('!additional_dates' => implode(', ', $values))));
}
return t('Repeats !interval !bymonth !byday !count !until !except. !additional', $description);
}
/**
* Parse an iCal rule into a parsed RRULE array and an EXDATE array.
*/
function date_repeat_split_rrule($rrule) {
$parts = explode("\n", str_replace("\r\n", "\n", $rrule));
$rrule = array();
$exceptions = array();
$additions = array();
$additions = array();
foreach ($parts as $part) {
if (strstr($part, 'RRULE')) {
$RRULE = str_replace('RRULE:', '', $part);
$rrule = (array) date_ical_parse_rrule('RRULE:', $RRULE);
}
elseif (strstr($part, 'EXDATE')) {
$EXDATE = str_replace('EXDATE:', '', $part);
$exceptions = (array) date_ical_parse_exceptions('EXDATE:', $EXDATE);
unset($exceptions['DATA']);
}
elseif (strstr($part, 'RDATE')) {
$RDATE = str_replace('RDATE:', '', $part);
$additions = (array) date_ical_parse_exceptions('RDATE:', $RDATE);
unset($additions['DATA']);
}
}
return array($rrule, $exceptions, $additions);
}
/**
* Analyze a RRULE and return dates that match it.
*/
function date_repeat_calc($rrule, $start, $end, $exceptions = array(), $timezone = NULL, $additions = array()) {
module_load_include('inc', 'date_repeat', 'date_repeat_calc');
return _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additions);
}
/**
* Generate the repeat rule setting form.
*/
function date_repeat_rrule_process($element, &$form_state, $form) {
module_load_include('inc', 'date_repeat', 'date_repeat_form');
return _date_repeat_rrule_process($element, $form_state, $form);
}
/**
* Process function for 'date_repeat_form_element_radios'.
*/
function date_repeat_form_element_radios_process($element) {
$childrenkeys = element_children($element);
if (count($element['#options']) &&
count($element['#options']) == count($childrenkeys)) {
$weight = 0;
$children = array();
$classes = isset($element['#div_classes']) ?
$element['#div_classes'] : array();
foreach ($childrenkeys as $childkey) {
$children[$childkey] = $element[$childkey];
unset($element[$childkey]);
}
foreach ($element['#options'] as $key => $choice) {
$currentchildkey = array_shift($childrenkeys);
$weight += 0.001;
$class = array_shift($classes);
$element += array($key => array());
$parents_for_id = array_merge($element['#parents'], array($key));
$element[$key] += array(
'#prefix' => '<div' . ($class ? " class=\"{$class}\"" : '') . '>',
'#type' => 'radio',
'#title' => $choice,
'#title_display' => 'invisible',
'#return_value' => $key,
'#default_value' => isset($element['#default_value']) ?
$element['#default_value'] : NULL,
'#attributes' => $element['#attributes'],
'#parents' => $element['#parents'],
'#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
'#ajax' => isset($element['#ajax']) ? $element['ajax'] : NULL,
'#weight' => $weight,
'#theme_wrappers' => array(),
'#suffix' => ' ',
);
$child = $children[$currentchildkey];
$weight += 0.001;
$child['#weight'] = $weight;
$child['#title_display'] = 'invisible';
$child['#suffix'] = (!empty($child['#suffix']) ? $child['#suffix'] : '') .
'</div>';
$child['#parents'] = $element['#parents'];
array_pop($child['#parents']);
array_push($child['#parents'], $currentchildkey);
$element_prototype = element_info($child['#type']);
$old_wrappers = array();
if (isset($child['#theme_wrappers'])) {
$old_wrappers += $child['#theme_wrappers'];
}
if (isset($element_prototype['#theme_wrappers'])) {
$old_wrappers += $element_prototype['#theme_wrappers'];
}
$child['#theme_wrappers'] = array();
foreach ($old_wrappers as $wrapper) {
if ($wrapper != 'form_element') {
$child['#theme_wrappers'][] = $wrapper;
}
}
$element[$currentchildkey] = $child;
}
}
return $element;
}

View File

@@ -0,0 +1,581 @@
<?php
/**
* @file
* Code to compute the dates that match an iCal RRULE.
*
* Moved to a separate file since it is not used on most pages
* so the code is not parsed unless needed.
*
* Extensive simpletests have been created to test the RRULE calculation
* results against official examples from RFC 2445.
*
* These calculations are expensive and results should be stored or cached
* so the calculation code is not called more often than necessary.
*
* Currently implemented:
* INTERVAL, UNTIL, COUNT, EXDATE, RDATE, BYDAY, BYMONTHDAY, BYMONTH,
* YEARLY, MONTHLY, WEEKLY, DAILY
*
* Currently not implemented:
*
* BYYEARDAY, MINUTELY, HOURLY, SECONDLY, BYMINUTE, BYHOUR, BYSECOND
* These could be implemented in the future.
*
* BYSETPOS
* Seldom used anywhere, so no reason to complicated the code.
*/
/**
* Private implementation of date_repeat_calc().
*
* Compute dates that match the requested rule, within a specified date range.
*/
function _date_repeat_calc($rrule, $start, $end, $exceptions, $timezone, $additions) {
module_load_include('inc', 'date_api', 'date_api_ical');
if (empty($timezone)) {
$timezone = date_default_timezone();
}
// Make sure the 'EXCEPTIONS' string isn't appended to the rule.
$parts = explode("\n", $rrule);
if (count($parts)) {
$rrule = $parts[0];
}
// Get the parsed array of rule values.
$rrule = date_ical_parse_rrule('RRULE:', $rrule);
// These default values indicate there is no RRULE here.
if ($rrule['FREQ'] == 'NONE' || (isset($rrule['INTERVAL']) && $rrule['INTERVAL'] == 0)) {
return array();
}
// Create a date object for the start and end dates.
$start_date = new DateObject($start, $timezone);
// Versions of PHP greater than PHP 5.3.5 require that we set an explicit time when
// using date_modify() or the time may not match the original value. Adding this
// modifier gives us the same results in both older and newer versions of PHP.
$modify_time = ' ' . $start_date->format('g:ia');
// If the rule has an UNTIL, see if that is earlier than the end date.
if (!empty($rrule['UNTIL'])) {
$end_date = new DateObject($end, $timezone);
$until_date = date_ical_date($rrule['UNTIL'], $timezone);
if (date_format($until_date, 'U') < date_format($end_date, 'U')) {
$end_date = $until_date;
}
}
// The only valid option for an empty end date is when we have a count.
elseif (empty($end)) {
if (!empty($rrule['COUNT'])) {
$end_date = NULL;
}
else {
return array();
}
}
else {
$end_date = new DateObject($end, $timezone);
}
// Get an integer value for the interval, if none given, '1' is implied.
if (empty($rrule['INTERVAL'])) {
$rrule['INTERVAL'] = 1;
}
$interval = max(1, $rrule['INTERVAL']);
$count = isset($rrule['COUNT']) ? $rrule['COUNT'] : NULL;
if (empty($rrule['FREQ'])) {
$rrule['FREQ'] = 'DAILY';
}
// Make sure DAILY frequency isn't used in places it won't work;
if (!empty($rrule['BYMONTHDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) {
$rrule['FREQ'] = 'MONTHLY';
}
elseif (!empty($rrule['BYDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) {
$rrule['FREQ'] = 'WEEKLY';
}
// Find the time period to jump forward between dates.
switch ($rrule['FREQ']) {
case 'DAILY':
$jump = $interval . ' days';
break;
case 'WEEKLY':
$jump = $interval . ' weeks';
break;
case 'MONTHLY':
$jump = $interval . ' months';
break;
case 'YEARLY':
$jump = $interval . ' years';
break;
}
$rrule = date_repeat_adjust_rrule($rrule, $start_date);
// The start date always goes into the results, whether or not it meets
// the rules. RFC 2445 includes examples where the start date DOES NOT
// meet the rules, but the expected results always include the start date.
$days = array(date_format($start_date, DATE_FORMAT_DATETIME));
// BYMONTHDAY will look for specific days of the month in one or more months.
// This process is only valid when frequency is monthly or yearly.
if (!empty($rrule['BYMONTHDAY'])) {
$finished = FALSE;
$current_day = clone($start_date);
$direction_days = array();
// Deconstruct the day in case it has a negative modifier.
foreach ($rrule['BYMONTHDAY'] as $day) {
preg_match("@(-)?([0-9]{1,2})@", $day, $regs);
if (!empty($regs[2])) {
// Convert parameters into full day name, count, and direction.
$direction_days[$day] = array(
'direction' => !empty($regs[1]) ? $regs[1] : '+',
'direction_count' => $regs[2],
);
}
}
while (!$finished) {
$period_finished = FALSE;
while (!$period_finished) {
foreach ($rrule['BYMONTHDAY'] as $monthday) {
$day = $direction_days[$monthday];
$current_day = date_repeat_set_month_day($current_day, NULL, $day['direction_count'], $day['direction'], $timezone, $modify_time);
date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule);
if ($finished = date_repeat_is_finished($current_day, $days, $count, $end_date)) {
$period_finished = TRUE;
}
}
// If it's monthly, keep looping through months, one INTERVAL at a time.
if ($rrule['FREQ'] == 'MONTHLY') {
if ($finished = date_repeat_is_finished($current_day, $days, $count, $end_date)) {
$period_finished = TRUE;
}
// Back up to first of month and jump.
$current_day = date_repeat_set_month_day($current_day, NULL, 1, '+', $timezone, $modify_time);
date_modify($current_day, '+' . $jump . $modify_time);
}
// If it's yearly, break out of the loop at the
// end of every year, and jump one INTERVAL in years.
else {
if (date_format($current_day, 'n') == 12) {
$period_finished = TRUE;
}
else {
// Back up to first of month and jump.
$current_day = date_repeat_set_month_day($current_day, NULL, 1, '+', $timezone, $modify_time);
date_modify($current_day, '+1 month' . $modify_time);
}
}
}
if ($rrule['FREQ'] == 'YEARLY') {
// Back up to first of year and jump.
$current_day = date_repeat_set_year_day($current_day, NULL, 1, '+', $timezone, $modify_time);
date_modify($current_day, '+' . $jump . $modify_time);
}
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
}
}
// This is the simple fallback case, not looking for any BYDAY,
// just repeating the start date. Because of imputed BYDAY above, this
// will only test TRUE for a DAILY or less frequency (like HOURLY).
elseif (empty($rrule['BYDAY'])) {
// $current_day will keep track of where we are in the calculation.
$current_day = clone($start_date);
$finished = FALSE;
$months = !empty($rrule['BYMONTH']) ? $rrule['BYMONTH'] : array();
while (!$finished) {
date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule);
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
date_modify($current_day, '+' . $jump . $modify_time);
}
}
else {
// More complex searches for day names and criteria like '-1SU' or '2TU,2TH',
// require that we interate through the whole time period checking each BYDAY.
// Create helper array to pull day names out of iCal day strings.
$day_names = date_repeat_dow_day_options(FALSE);
$days_of_week = array_keys($day_names);
// Parse out information about the BYDAYs and separate them
// depending on whether they have directional parameters like -1SU or 2TH.
$month_days = array();
$week_days = array();
// Find the right first day of the week to use, iCal rules say Monday
// should be used if none is specified.
$week_start_rule = !empty($rrule['WKST']) ? trim($rrule['WKST']) : 'MO';
$week_start_day = $day_names[$week_start_rule];
// Make sure the week days array is sorted into week order,
// we use the $ordered_keys to get the right values into the key
// and force the array to that order. Needed later when we
// iterate through each week looking for days so we don't
// jump to the next week when we hit a day out of order.
$ordered = date_repeat_days_ordered($week_start_rule);
$ordered_keys = array_flip($ordered);
foreach ($rrule['BYDAY'] as $day) {
preg_match("@(-)?([0-9]+)?([SU|MO|TU|WE|TH|FR|SA]{2})@", trim($day), $regs);
if (!empty($regs[2])) {
// Convert parameters into full day name, count, and direction.
$direction_days[] = array(
'day' => $day_names[$regs[3]],
'direction' => !empty($regs[1]) ? $regs[1] : '+',
'direction_count' => $regs[2],
);
}
else {
$week_days[$ordered_keys[$regs[3]]] = $day_names[$regs[3]];
}
}
ksort($week_days);
// BYDAYs with parameters like -1SU (last Sun) or 2TH (second Thur)
// need to be processed one month or year at a time.
if (!empty($direction_days) && in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) {
$finished = FALSE;
$current_day = clone($start_date);
while (!$finished) {
foreach ($direction_days as $day) {
// Find the BYDAY date in the current month.
if ($rrule['FREQ'] == 'MONTHLY') {
$current_day = date_repeat_set_month_day($current_day, $day['day'], $day['direction_count'], $day['direction'], $timezone, $modify_time);
}
else {
$current_day = date_repeat_set_year_day($current_day, $day['day'], $day['direction_count'], $day['direction'], $timezone, $modify_time);
}
date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule);
}
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
// Reset to beginning of period before jumping to next period.
// Needed especially when working with values like 'last Saturday'
// to be sure we don't skip months like February.
$year = date_format($current_day, 'Y');
$month = date_format($current_day, 'n');
if ($rrule['FREQ'] == 'MONTHLY') {
date_date_set($current_day, $year, $month, 1);
}
else {
date_date_set($current_day, $year, 1, 1);
}
// Jump to the next period.
date_modify($current_day, '+' . $jump . $modify_time);
}
}
// For BYDAYs without parameters,like TU,TH (every Tues and Thur),
// we look for every one of those days during the frequency period.
// Iterate through periods of a WEEK, MONTH, or YEAR, checking for
// the days of the week that match our criteria for each week in the
// period, then jumping ahead to the next week, month, or year,
// an INTERVAL at a time.
if (!empty($week_days) && in_array($rrule['FREQ'], array('MONTHLY', 'WEEKLY', 'YEARLY'))) {
$finished = FALSE;
$current_day = clone($start_date);
$format = $rrule['FREQ'] == 'YEARLY' ? 'Y' : 'n';
$current_period = date_format($current_day, $format);
// Back up to the beginning of the week in case we are somewhere in the
// middle of the possible week days, needed so we don't prematurely
// jump to the next week. The date_repeat_add_dates() function will
// keep dates outside the range from getting added.
if (date_format($current_day, 'l') != $day_names[$day]) {
date_modify($current_day, '-1 ' . $week_start_day . $modify_time);
}
while (!$finished) {
$period_finished = FALSE;
while (!$period_finished) {
$moved = FALSE;
foreach ($week_days as $delta => $day) {
// Find the next occurence of each day in this week, only add it
// if we are still in the current month or year. The date_repeat_add_dates
// function is insufficient to test whether to include this date
// if we are using a rule like 'every other month', so we must
// explicitly test it here.
// If we're already on the right day, don't jump or we
// will prematurely move into the next week.
if (date_format($current_day, 'l') != $day) {
date_modify($current_day, '+1 ' . $day . $modify_time);
$moved = TRUE;
}
if ($rrule['FREQ'] == 'WEEKLY' || date_format($current_day, $format) == $current_period) {
date_repeat_add_dates($days, $current_day, $start_date, $end_date, $exceptions, $rrule);
}
}
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
// Make sure we don't get stuck in endless loop if the current
// day never got changed above.
if (!$moved) {
date_modify($current_day, '+1 day' . $modify_time);
}
// If this is a WEEKLY frequency, stop after each week,
// otherwise, stop when we've moved outside the current period.
// Jump to the end of the week, then test the period.
if ($finished || $rrule['FREQ'] == 'WEEKLY') {
$period_finished = TRUE;
}
elseif ($rrule['FREQ'] != 'WEEKLY' && date_format($current_day, $format) != $current_period) {
$period_finished = TRUE;
}
}
if ($finished) {
continue;
}
// We'll be at the end of a week, month, or year when
// we get to this point in the code.
// Go back to the beginning of this period before we jump, to
// ensure we jump to the first day of the next period.
switch ($rrule['FREQ']) {
case 'WEEKLY':
date_modify($current_day, '+1 ' . $week_start_day . $modify_time);
date_modify($current_day, '-1 week' . $modify_time);
break;
case 'MONTHLY':
date_modify($current_day, '-' . (date_format($current_day, 'j') - 1) . ' days' . $modify_time);
date_modify($current_day, '-1 month' . $modify_time);
break;
case 'YEARLY':
date_modify($current_day, '-' . date_format($current_day, 'z') . ' days' . $modify_time);
date_modify($current_day, '-1 year' . $modify_time);
break;
}
// Jump ahead to the next period to be evaluated.
date_modify($current_day, '+' . $jump . $modify_time);
$current_period = date_format($current_day, $format);
$finished = date_repeat_is_finished($current_day, $days, $count, $end_date);
}
}
}
// add additional dates
foreach ($additions as $addition) {
$date = new dateObject($addition . ' ' . $start_date->format('H:i:s'), $timezone);
$days[] = date_format($date, DATE_FORMAT_DATETIME);
}
sort($days);
return $days;
}
/**
* See if the RRULE needs some imputed values added to it.
*/
function date_repeat_adjust_rrule($rrule, $start_date) {
// If this is not a valid value, do nothing;
if (empty($rrule) || empty($rrule['FREQ'])) {
return array();
}
// RFC 2445 says if no day or monthday is specified when creating repeats for
// weeks, months, or years, impute the value from the start date.
if (empty($rrule['BYDAY']) && $rrule['FREQ'] == 'WEEKLY') {
$rrule['BYDAY'] = array(date_repeat_dow2day(date_format($start_date, 'w')));
}
elseif (empty($rrule['BYDAY']) && empty($rrule['BYMONTHDAY']) && $rrule['FREQ'] == 'MONTHLY') {
$rrule['BYMONTHDAY'] = array(date_format($start_date, 'j'));
}
elseif (empty($rrule['BYDAY']) && empty($rrule['BYMONTHDAY']) && empty($rrule['BYYEARDAY']) && $rrule['FREQ'] == 'YEARLY') {
$rrule['BYMONTHDAY'] = array(date_format($start_date, 'j'));
if (empty($rrule['BYMONTH'])) {
$rrule['BYMONTH'] = array(date_format($start_date, 'n'));
}
}
// If we are processing rules for period other than YEARLY or MONTHLY
// and have BYDAYS like 2SU or -1SA, simplify them to SU or SA since the
// position rules make no sense in other periods and just add complexity.
elseif (!empty($rrule['BYDAY']) && !in_array($rrule['FREQ'], array('MONTHLY', 'YEARLY'))) {
foreach ($rrule['BYDAY'] as $delta => $BYDAY) {
$rrule['BYDAY'][$delta] = substr($BYDAY, -2);
}
}
return $rrule;
}
/**
* Helper function to add found date to the $dates array.
*
* Check that the date to be added is between the start and end date
* and that it is not in the $exceptions, nor already in the $days array,
* and that it meets other criteria in the RRULE.
*/
function date_repeat_add_dates(&$days, $current_day, $start_date, $end_date, $exceptions, $rrule) {
if (isset($rrule['COUNT']) && sizeof($days) >= $rrule['COUNT']) {
return FALSE;
}
$formatted = date_format($current_day, DATE_FORMAT_DATETIME);
if (!empty($end_date) && $formatted > date_format($end_date, DATE_FORMAT_DATETIME)) {
return FALSE;
}
if ($formatted < date_format($start_date, DATE_FORMAT_DATETIME)) {
return FALSE;
}
if (in_array(date_format($current_day, 'Y-m-d'), $exceptions)) {
return FALSE;
}
if (!empty($rrule['BYDAY'])) {
$BYDAYS = $rrule['BYDAY'];
foreach ($BYDAYS as $delta => $BYDAY) {
$BYDAYS[$delta] = substr($BYDAY, -2);
}
if (!in_array(date_repeat_dow2day(date_format($current_day, 'w')), $BYDAYS)) {
return FALSE;
}}
if (!empty($rrule['BYYEAR']) && !in_array(date_format($current_day, 'Y'), $rrule['BYYEAR'])) {
return FALSE;
}
if (!empty($rrule['BYMONTH']) && !in_array(date_format($current_day, 'n'), $rrule['BYMONTH'])) {
return FALSE;
}
if (!empty($rrule['BYMONTHDAY'])) {
// Test month days, but only if there are no negative numbers.
$test = TRUE;
$BYMONTHDAYS = array();
foreach ($rrule['BYMONTHDAY'] as $day) {
if ($day > 0) {
$BYMONTHDAYS[] = $day;
}
else {
$test = FALSE;
break;
}
}
if ($test && !empty($BYMONTHDAYS) && !in_array(date_format($current_day, 'j'), $BYMONTHDAYS)) {
return FALSE;
}
}
// Don't add a day if it is already saved so we don't throw the count off.
if (in_array($formatted, $days)) {
return TRUE;
}
else {
$days[] = $formatted;
}
}
/**
* Stop when $current_day is greater than $end_date or $count is reached.
*/
function date_repeat_is_finished($current_day, $days, $count, $end_date) {
if (($count && sizeof($days) >= $count)
|| (!empty($end_date) && date_format($current_day, 'U') > date_format($end_date, 'U'))) {
return TRUE;
}
else {
return FALSE;
}
}
/**
* Set a date object to a specific day of the month.
*
* Example,
* date_set_month_day($date, 'Sunday', 2, '-')
* will reset $date to the second to last Sunday in the month.
* If $day is empty, will set to the number of days from the
* beginning or end of the month.
*/
function date_repeat_set_month_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time) {
if (is_object($date_in)) {
$current_month = date_format($date_in, 'n');
// Reset to the start of the month.
// We should be able to do this with date_date_set(), but
// for some reason the date occasionally gets confused if run
// through this function multiple times. It seems to work
// reliably if we create a new object each time.
$datetime = date_format($date_in, DATE_FORMAT_DATETIME);
$datetime = substr_replace($datetime, '01', 8, 2);
$date = new DateObject($datetime, $timezone);
if ($direction == '-') {
// For negative search, start from the end of the month.
date_modify($date, '+1 month' . $modify_time);
}
else {
// For positive search, back up one day to get outside the
// current month, so we can catch the first of the month.
date_modify($date, '-1 day' . $modify_time);
}
if (empty($day)) {
date_modify($date, $direction . $count . ' days' . $modify_time);
}
else {
// Use the English text for order, like First Sunday
// instead of +1 Sunday to overcome PHP5 bug, (see #369020).
$order = date_order();
$step = $count <= 5 ? $order[$direction . $count] : $count;
date_modify($date, $step . ' ' . $day . $modify_time);
}
// If that takes us outside the current month, don't go there.
if (date_format($date, 'n') == $current_month) {
return $date;
}
}
return $date_in;
}
/**
* Set a date object to a specific day of the year.
*
* Example,
* date_set_year_day($date, 'Sunday', 2, '-')
* will reset $date to the second to last Sunday in the year.
* If $day is empty, will set to the number of days from the
* beginning or end of the year.
*/
function date_repeat_set_year_day($date_in, $day, $count = 1, $direction = '+', $timezone = 'UTC', $modify_time) {
if (is_object($date_in)) {
$current_year = date_format($date_in, 'Y');
// Reset to the start of the month.
// See note above.
$datetime = date_format($date_in, DATE_FORMAT_DATETIME);
$datetime = substr_replace($datetime, '01-01', 5, 5);
$date = new DateObject($datetime, $timezone);
if ($direction == '-') {
// For negative search, start from the end of the year.
date_modify($date, '+1 year' . $modify_time);
}
else {
// For positive search, back up one day to get outside the
// current year, so we can catch the first of the year.
date_modify($date, '-1 day' . $modify_time);
}
if (empty($day)) {
date_modify($date, $direction . $count . ' days' . $modify_time);
}
else {
// Use the English text for order, like First Sunday
// instead of +1 Sunday to overcome PHP5 bug, (see #369020).
$order = date_order();
$step = $count <= 5 ? $order[$direction . $count] : $count;
date_modify($date, $step . ' ' . $day . $modify_time);
}
// If that takes us outside the current year, don't go there.
if (date_format($date, 'Y') == $current_year) {
return $date;
}
}
return $date_in;
}

View File

@@ -0,0 +1,977 @@
<?php
/**
* @file
* Code to add a date repeat selection form to a date field and create
* an iCal RRULE from the chosen selections.
*
* Moved to a separate file since it is not used on most pages
* so the code is not parsed unless needed.
*
* Currently implemented:
* INTERVAL, UNTIL, EXDATE, RDATE, BYDAY, BYMONTHDAY, BYMONTH,
* YEARLY, MONTHLY, WEEKLY, DAILY
*
* Currently not implemented:
*
* BYYEARDAY, MINUTELY, HOURLY, SECONDLY, BYMINUTE, BYHOUR, BYSECOND
* These could be implemented in the future.
*
* COUNT
* The goal of this module is to create a way we can parse an iCal
* RRULE and pull out just dates for a specified date range, for
* instance with a date that repeats daily for several years, we might
* want to only be able to pull out the dates for the current year.
*
* Adding COUNT to the rules we create makes it impossible to do that
* without parsing and computing the whole range of dates that the rule
* will create. COUNT is left off of the user form completely for this
* reason.
*
* BYSETPOS
* Seldom used anywhere, so no reason to complicated the code.
*/
/**
* Generate the repeat setting form.
*/
function _date_repeat_rrule_process($element, &$form_state, $form) {
// If the RRULE field is not visible to the user, needs no processing or validation.
// The Date field module is not adding this element to forms if the field is hidden,
// this test is just in case some other module attempts to do so.
if (date_hidden_element($element)) {
return $element;
}
module_load_include('inc', 'date_api', 'date_api_ical');
if (empty($element['#date_repeat_widget'])) {
$element['#date_repeat_widget'] = module_exists('date_popup') ? 'date_popup' : 'date_select';
}
if (is_array($element['#default_value'])) {
$element['#value'] = date_repeat_merge($element['#value'], $element);
$rrule = date_api_ical_build_rrule($element['#value']);
}
else {
$rrule = $element['#default_value'];
}
// Empty the original string value of the RRULE so we can create
// an array of values for the form from the RRULE's contents.
$element['#value'] = '';
$parts = date_repeat_split_rrule($rrule);
$rrule = $parts[0];
$exceptions = $parts[1];
$additions = $parts[2];
$timezone = !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone();
$merged_values = date_repeat_merge($rrule, $element);
$UNTIL = '';
if (!empty($merged_values['UNTIL']['datetime'])) {
$until_date = new DateObject($merged_values['UNTIL']['datetime'], $merged_values['UNTIL']['tz']);
date_timezone_set($until_date, timezone_open($timezone));
$UNTIL = date_format($until_date, DATE_FORMAT_DATETIME);
}
$COUNT = '';
if (!empty($merged_values['COUNT'])) {
$COUNT = $merged_values['COUNT'];
}
$element['FREQ'] = array(
'#type' => 'select',
'#title' => t('Repeats', array(), array('context' => 'Date repeat')),
'#default_value' => !empty($rrule['FREQ']) ? $rrule['FREQ'] : 'WEEKLY',
'#options' => date_repeat_freq_options(),
'#prefix' => '<div class="date-repeat-input">',
'#suffix' => '</div>',
);
$element['daily'] = array(
'#type' => 'container',
'#tree' => TRUE,
'#prefix' => '<div class="date-clear daily">',
'#suffix' => '</div>',
'#states' => array(
'visible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'DAILY'),
),
),
);
$element['weekly'] = array(
'#type' => 'container',
'#tree' => TRUE,
'#prefix' => '<div class="date-clear weekly">',
'#suffix' => '</div>',
'#states' => array(
'visible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'WEEKLY'),
),
),
);
$element['monthly'] = array(
'#type' => 'container',
'#tree' => TRUE,
'#prefix' => '<div class="date-clear monthly">',
'#suffix' => '</div>',
'#states' => array(
'visible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'MONTHLY'),
),
),
);
$element['yearly'] = array(
'#type' => 'container',
'#tree' => TRUE,
'#prefix' => '<div class="date-clear yearly">',
'#suffix' => '</div>',
'#states' => array(
'visible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'YEARLY'),
),
),
);
list($prefix, $suffix) = explode('@interval', t('Every @interval days', array(), array('context' => 'Date repeat')));
$DAILY_INTERVAL = array(
'#type' => 'textfield',
'#title' => t('Repeats', array(), array('context' => 'Date repeat')),
'#title_display' => 'invisible',
'#default_value' => (!empty($rrule['INTERVAL']) ? $rrule['INTERVAL'] : 1),
'#element_validate' => array('element_validate_integer_positive'),
'#attributes' => array('placeholder' => array('#')),
'#size' => 3,
'#maxlength' => 3,
'#prefix' => '<div class="date-clear">',
'#suffix' => t('days') . '</div>',
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
);
list($prefix, $suffix) = explode('@interval', t('Every @interval weeks', array(), array('context' => 'Date repeat')));
$element['weekly']['INTERVAL'] = array(
'#type' => 'textfield',
'#title' => t('Repeats', array(), array('context' => 'Date repeat')),
'#default_value' => (!empty($rrule['INTERVAL']) ? $rrule['INTERVAL'] : 1),
'#element_validate' => array('element_validate_integer_positive'),
'#attributes' => array('placeholder' => array('#')),
'#size' => 3,
'#maxlength' => 3,
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
);
list($prefix, $suffix) = explode('@interval', t('Every @interval months', array(), array('context' => 'Date repeat')));
$element['monthly']['INTERVAL'] = array(
'#access' => FALSE,
'#type' => 'textfield',
'#title' => t('Repeats', array(), array('context' => 'Date repeat')),
'#default_value' => (!empty($rrule['INTERVAL']) ? $rrule['INTERVAL'] : 1),
'#element_validate' => array('element_validate_integer_positive'),
'#attributes' => array('placeholder' => array('#')),
'#size' => 3,
'#maxlength' => 3,
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
);
list($prefix, $suffix) = explode('@interval', t('Every @interval years', array(), array('context' => 'Date repeat')));
$element['yearly']['INTERVAL'] = array(
'#type' => 'textfield',
'#title' => t('Repeats', array(), array('context' => 'Date repeat')),
'#default_value' => (!empty($rrule['INTERVAL']) ? $rrule['INTERVAL'] : 1),
'#element_validate' => array('element_validate_integer_positive'),
'#attributes' => array('placeholder' => array('#')),
'#size' => 3,
'#maxlength' => 3,
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
'#field_prefix' => $prefix,
'#field_suffix' => $suffix,
);
$options = date_repeat_dow_day_options_abbr(TRUE);
$options = date_repeat_dow_day_options_ordered($options);
$element['weekly']['BYDAY'] = array(
'#type' => 'checkboxes',
'#title' => t('Repeat on', array(), array('context' => 'Date repeat')),
'#default_value' => !empty($rrule['BYDAY']) && $rrule['FREQ'] === 'WEEKLY' ? $rrule['BYDAY'] : array(),
'#options' => $options,
'#attributes' => array('class' => array('container-inline byday')),
'#multiple' => TRUE,
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
);
$DAILY_radios_default = 'INTERVAL';
if (isset($rrule['FREQ']) && $rrule['FREQ'] === 'DAILY' && !empty($rrule['BYDAY'])) {
switch (count($rrule['BYDAY'])) {
case 2:
$DAILY_radios_default = 'every_tu_th';
break;
case 3:
$DAILY_radios_default = 'every_mo_we_fr';
break;
case 5:
$DAILY_radios_default = 'every_weekday';
break;
}
}
$DAILY_every_weekday = array(
'#type' => 'item',
'#markup' => '<div>' . t('Every weekday', array(), array('context' => 'Date repeat')) . '</div>',
);
$DAILY_mo_we_fr = array(
'#type' => 'item',
'#markup' => '<div>' . t('Every Mon, Wed, Fri', array(), array('context' => 'Date repeat')) . '</div>',
);
$DAILY_tu_th = array(
'#type' => 'item',
'#markup' => '<div>' . t('Every Tue, Thu', array(), array('context' => 'Date repeat')) . '</div>',
);
$element['daily']['byday_radios'] = array(
'#type' => 'date_repeat_form_element_radios',
'#tree' => TRUE,
'#title' => t('Repeats every', array(), array('context' => 'Date repeat')),
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
'#states' => array(
'visible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'DAILY'),
),
),
'#default_value' => $DAILY_radios_default,
'#options' => array(
'INTERVAL' => t('interval'),
'every_weekday' => t('every weekday'),
'every_mo_we_fr' => t('monday wednesday friday'),
'every_tu_th' => t('tuesday thursday'),
),
'INTERVAL_child' => $DAILY_INTERVAL,
'every_weekday_child' => $DAILY_every_weekday,
'mo_we_fr_child' => $DAILY_mo_we_fr,
'tu_th_child' => $DAILY_tu_th,
'#div_classes' => array(
'container-inline interval',
'container-inline weekday',
'container-inline mo-we-fr',
'container-inline tu-th',
),
);
$MONTHLY_day_month_default = 'BYMONTHDAY_BYMONTH';
if (isset($rrule['FREQ']) && $rrule['FREQ'] === 'MONTHLY' && !empty($rrule['BYDAY'])) {
$MONTHLY_day_month_default = 'BYDAY_BYMONTH';
}
$MONTHLY_on_day_BYMONTHDAY_of_BYMONTH = array(
'#type' => 'container',
'#tree' => TRUE,
);
list($bymonthday_title, $bymonthday_suffix) = explode('@bymonthday', t('On day @bymonthday of', array(), array('context' => 'Date repeat')));
$MONTHLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTHDAY'] = array(
'#type' => 'select',
'#title' => $bymonthday_title,
'#default_value' => !empty($rrule['BYMONTHDAY']) && $rrule['FREQ'] === 'MONTHLY' ? $rrule['BYMONTHDAY'] : '',
'#options' => drupal_map_assoc(range(1, 31)) + drupal_map_assoc(range(-1, -31)),
'#multiple' => FALSE,
'#prefix' => '<div class="date-clear bymonthday">',
'#suffix' => '</div>',
'#field_suffix' => $bymonthday_suffix,
);
$MONTHLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTH'] = array(
'#type' => 'checkboxes',
'#title' => t('Bymonth', array(), array('context' => 'Date repeat')),
'#title_display' => 'invisible',
'#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $MONTHLY_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(),
'#options' => date_month_names_abbr(TRUE),
'#attributes' => array('class' => array('container-inline')),
'#multiple' => TRUE,
'#prefix' => '<div class="date-clear bymonth">',
'#suffix' => '</div>',
);
$MONTHLY_on_the_BYDAY_of_BYMONTH = array(
'#type' => 'container',
'#tree' => TRUE,
);
$MONTHLY_BYDAY_COUNT = '';
$MONTHLY_BYDAY_DAY = '';
if (isset($rrule['BYDAY']) && !empty($rrule['BYDAY']) && $rrule['FREQ'] === 'MONTHLY') {
$MONTHLY_BYDAY_COUNT = substr($rrule['BYDAY'][0], 0, -2);
$MONTHLY_BYDAY_DAY = substr($rrule['BYDAY'][0], -2);;
}
list($byday_count_title, $byday_day_title) = explode('@byday', t('On the @byday of', array(), array('context' => 'Date repeat')));
$MONTHLY_on_the_BYDAY_of_BYMONTH['BYDAY_COUNT'] = array(
'#type' => 'select',
'#title' => $byday_count_title,
'#default_value' => !empty($MONTHLY_BYDAY_COUNT) ? $MONTHLY_BYDAY_COUNT : '',
'#options' => date_order_translated(),
'#multiple' => FALSE,
'#prefix' => '<div class="date-repeat-input byday-count">',
'#suffix' => '</div>',
);
$MONTHLY_on_the_BYDAY_of_BYMONTH['BYDAY_DAY'] = array(
'#type' => 'select',
'#title' => $byday_day_title,
'#title_display' => 'after',
'#default_value' => !empty($MONTHLY_BYDAY_DAY) ? $MONTHLY_BYDAY_DAY : '',
'#options' => date_repeat_dow_day_options(TRUE),
'#multiple' => FALSE,
'#prefix' => '<div class="date-repeat-input byday-day">',
'#suffix' => '</div>',
);
$MONTHLY_on_the_BYDAY_of_BYMONTH['BYMONTH'] = array(
'#type' => 'checkboxes',
'#title' => t('Bymonth', array(), array('context' => 'Date repeat')),
'#title_display' => 'invisible',
'#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'MONTHLY' && $MONTHLY_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(),
'#options' => date_month_names_abbr(TRUE),
'#attributes' => array('class' => array('container-inline')),
'#multiple' => TRUE,
'#prefix' => '<div class="date-clear bymonth">',
'#suffix' => '</div>',
);
$element['monthly']['day_month'] = array(
'#type' => 'date_repeat_form_element_radios',
'#tree' => TRUE,
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
'#states' => array(
'visible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'MONTHLY'),
),
),
'#attributes' => array('class' => array('date-repeat-radios clearfix')),
'#default_value' => $MONTHLY_day_month_default,
'#options' => array(
'BYMONTHDAY_BYMONTH' => t('On day ... of ...'),
'BYDAY_BYMONTH' => t('On the ... of ...'),
),
'BYMONTHDAY_BYMONTH_child' => $MONTHLY_on_day_BYMONTHDAY_of_BYMONTH,
'BYDAY_BYMONTH_child' => $MONTHLY_on_the_BYDAY_of_BYMONTH,
'#div_classes' => array(
'date-repeat-radios-item date-clear clearfix bymonthday-bymonth',
'date-repeat-radios-item date-clear clearfix byday-bymonth',
),
);
$YEARLY_day_month_default = 'BYMONTHDAY_BYMONTH';
if (isset($rrule['FREQ']) && $rrule['FREQ'] === 'YEARLY' && !empty($rrule['BYDAY'])) {
$YEARLY_day_month_default = 'BYDAY_BYMONTH';
}
$YEARLY_on_day_BYMONTHDAY_of_BYMONTH = array(
'#type' => 'container',
'#tree' => TRUE,
);
list($bymonthday_title, $bymonthday_suffix) = explode('@bymonthday', t('On day @bymonthday of', array(), array('context' => 'Date repeat')));
$YEARLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTHDAY'] = array(
'#type' => 'select',
'#title' => $bymonthday_title,
'#default_value' => !empty($rrule['BYMONTHDAY']) && $rrule['FREQ'] === 'YEARLY' ? $rrule['BYMONTHDAY'] : '',
'#options' => drupal_map_assoc(range(1, 31)) + drupal_map_assoc(range(-1, -31)),
'#multiple' => FALSE,
'#prefix' => '<div class="date-clear bymonthday">',
'#suffix' => '</div>',
'#field_suffix' => $bymonthday_suffix,
);
$YEARLY_on_day_BYMONTHDAY_of_BYMONTH['BYMONTH'] = array(
'#type' => 'checkboxes',
'#title' => t('Bymonth', array(), array('context' => 'Date repeat')),
'#title_display' => 'invisible',
'#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $YEARLY_day_month_default === 'BYMONTHDAY_BYMONTH' ? $rrule['BYMONTH'] : array(),
'#options' => date_month_names_abbr(TRUE),
'#attributes' => array('class' => array('container-inline')),
'#multiple' => TRUE,
'#prefix' => '<div class="date-clear bymonth">',
'#suffix' => '</div>',
);
$YEARLY_on_the_BYDAY_of_BYMONTH = array(
'#type' => 'container',
'#tree' => TRUE,
);
$YEARLY_BYDAY_COUNT = '';
$YEARLY_BYDAY_DAY = '';
if (isset($rrule['BYDAY']) && !empty($rrule['BYDAY']) && $rrule['FREQ'] === 'YEARLY') {
$YEARLY_BYDAY_COUNT = substr($rrule['BYDAY'][0], 0, -2);
$YEARLY_BYDAY_DAY = substr($rrule['BYDAY'][0], -2);;
}
list($byday_count_title, $byday_day_title) = explode('@byday', t('On the @byday of', array(), array('context' => 'Date repeat')));
$YEARLY_on_the_BYDAY_of_BYMONTH['BYDAY_COUNT'] = array(
'#type' => 'select',
'#title' => $byday_count_title,
'#default_value' => !empty($YEARLY_BYDAY_COUNT) ? $YEARLY_BYDAY_COUNT : '',
'#options' => date_order_translated(),
'#multiple' => FALSE,
'#prefix' => '<div class="date-repeat-input byday-count">',
'#suffix' => '</div>',
);
$YEARLY_on_the_BYDAY_of_BYMONTH['BYDAY_DAY'] = array(
'#type' => 'select',
'#title' => $byday_day_title,
'#title_display' => 'after',
'#default_value' => !empty($YEARLY_BYDAY_DAY) ? $YEARLY_BYDAY_DAY : '',
'#options' => date_repeat_dow_day_options(TRUE),
'#multiple' => FALSE,
'#prefix' => '<div class="date-repeat-input byday-day">',
'#suffix' => '</div>',
);
$YEARLY_on_the_BYDAY_of_BYMONTH['BYMONTH'] = array(
'#type' => 'checkboxes',
'#title' => t('Bymonth', array(), array('context' => 'Date repeat')),
'#title_display' => 'invisible',
'#default_value' => !empty($rrule['BYMONTH']) && $rrule['FREQ'] === 'YEARLY' && $YEARLY_day_month_default === 'BYDAY_BYMONTH' ? $rrule['BYMONTH'] : array(),
'#options' => date_month_names_abbr(TRUE),
'#attributes' => array('class' => array('container-inline')),
'#multiple' => TRUE,
'#prefix' => '<div class="date-clear bymonth">',
'#suffix' => '</div>',
);
$element['yearly']['day_month'] = array(
'#type' => 'date_repeat_form_element_radios',
'#tree' => TRUE,
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
'#states' => array(
'visible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'YEARLY'),
),
),
'#attributes' => array('class' => array('date-repeat-radios clearfix')),
'#default_value' => $YEARLY_day_month_default,
'#options' => array(
'BYMONTHDAY_BYMONTH' => t('On day ... of ...'),
'BYDAY_BYMONTH' => t('On the ... of ...'),
),
'BYMONTHDAY_BYMONTH_child' => $YEARLY_on_day_BYMONTHDAY_of_BYMONTH,
'BYDAY_BYMONTH_child' => $YEARLY_on_the_BYDAY_of_BYMONTH,
'#div_classes' => array(
'date-repeat-radios-item date-clear clearfix bymonthday-bymonth',
'date-repeat-radios-item date-clear clearfix byday-bymonth',
),
);
list($prefix, $suffix) = explode('@count', t('After @count occurrences', array(), array('context' => 'Date repeat')));
$count_form_element = array(
'#type' => 'textfield',
'#title' => t('Count', array(), array('context' => 'Date repeat')),
'#default_value' => $COUNT,
'#element_validate' => array('element_validate_integer_positive'),
'#attributes' => array('placeholder' => array('#')),
'#prefix' => $prefix,
'#suffix' => $suffix,
'#size' => 10,
'#maxlength' => 10,
);
$until_form_element = array(
'#type' => 'container',
'#tree' => TRUE,
'#prefix' => '<div class="date-prefix-inline">' . t('On', array(), array('context' => 'Date repeat')) . '</div>',
'datetime' => array(
'#type' => $element['#date_repeat_widget'],
'#title' => t('Until', array(), array('context' => 'Date repeat')),
'#title_display' => 'invisible',
'#default_value' => $UNTIL,
'#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d',
'#date_timezone' => $timezone,
'#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(),
'#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3',
'#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within',
'#date_flexible' => 0,
),
'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']),
'all_day' => array('#type' => 'hidden', '#value' => 1),
'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))),
);
$range_of_repeat_default = 'COUNT';
if (!empty($UNTIL)) {
$range_of_repeat_default = 'UNTIL';
}
$element['range_of_repeat'] = array(
'#type' => 'date_repeat_form_element_radios',
'#tree' => TRUE,
'#title' => t('Stop repeating', array(), array('context' => 'Date repeat')),
'#title_display' => 'before',
'#prefix' => '<div class="date-clear range-of-repeat">',
'#suffix' => '</div>',
'#states' => array(
'invisible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'NONE'),
),
),
'#default_value' => $range_of_repeat_default,
'#options' => array(
'COUNT' => t('Count'),
'UNTIL' => t('Until'),
),
'count_child' => $count_form_element,
'until_child' => $until_form_element,
'#div_classes' => array(
'container-inline count',
"until widget-{$element['#date_repeat_widget']} label-{$element['#date_label_position']}",
),
);
$parents = $element['#array_parents'];
$instance = implode('-', $parents);
// Make sure this will work right either in the normal form or in an ajax callback from the 'Add more' button.
if (empty($form_state['num_exceptions'][$instance])) {
$form_state['num_exceptions'][$instance] = count($exceptions);
}
if ($form_state['num_exceptions'][$instance] == 0) {
$collapsed = TRUE;
}
else {
$collapsed = FALSE;
}
$element['show_exceptions'] = array(
'#type' => 'checkbox',
'#title' => t('Exclude dates', array(), array('context' => 'Date repeat')),
'#states' => array(
'invisible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'NONE'),
),
),
'#default_value' => empty($form_state['num_exceptions'][$instance]) ? 0 : 1,
);
$element['exceptions'] = array(
'#type' => 'container',
'#prefix' => '<div id="date-repeat-exceptions-' . $instance . '" class="date-repeat">',
'#suffix' => '</div>',
'#states' => array(
'visible' => array(
":input[name=\"{$element['#name']}[show_exceptions]\"]" => array('checked' => TRUE),
),
),
);
for ($i = 0; $i < max($form_state['num_exceptions'][$instance], 1) ; $i++) {
$EXCEPT = '';
if (!empty($exceptions[$i]['datetime'])) {
$ex_date = new DateObject($exceptions[$i]['datetime'], $exceptions[$i]['tz']);
date_timezone_set($ex_date, timezone_open($timezone));
$EXCEPT = date_format($ex_date, DATE_FORMAT_DATETIME);
}
$element['exceptions']['EXDATE'][$i] = array(
'#tree' => TRUE,
'datetime' => array(
'#name' => 'exceptions|' . $instance,
'#type' => $element['#date_repeat_widget'],
'#default_value' => $EXCEPT,
'#date_timezone' => !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone(),
'#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d',
'#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(),
'#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3',
'#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within',
'#date_flexible' => 0,
),
'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']),
'all_day' => array('#type' => 'hidden', '#value' => 1),
'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))),
);
}
// collect additions in the same way as exceptions - implements RDATE.
if (empty($form_state['num_additions'][$instance])) {
$form_state['num_additions'][$instance] = count($additions);
}
if ($form_state['num_additions'][$instance] == 0) {
$collapsed = TRUE;
}
else {
$collapsed = FALSE;
}
$element['show_additions'] = array(
'#type' => 'checkbox',
'#title' => t('Include dates', array(), array('context' => 'Date repeat')),
'#states' => array(
'invisible' => array(
":input[name=\"{$element['#name']}[FREQ]\"]" => array('value' => 'NONE'),
),
),
'#default_value' => empty($form_state['num_additions'][$instance]) ? 0 : 1,
);
$element['additions'] = array(
'#type' => 'container',
'#prefix' => '<div id="date-repeat-additions-' . $instance . '" class="date-repeat">',
'#suffix' => '</div>',
'#states' => array(
'visible' => array(
":input[name=\"{$element['#name']}[show_additions]\"]" => array('checked' => TRUE),
),
),
);
for ($i = 0; $i < max($form_state['num_additions'][$instance], 1) ; $i++) {
$RDATE = '';
if (!empty($additions[$i]['datetime'])) {
$rdate = new DateObject($additions[$i]['datetime'], $additions[$i]['tz']);
date_timezone_set($rdate, timezone_open($timezone));
$RDATE = date_format($rdate, DATE_FORMAT_DATETIME);
}
$element['additions']['RDATE'][$i] = array(
'#tree' => TRUE,
'datetime' => array(
'#type' => $element['#date_repeat_widget'],
'#name' => 'additions|' . $instance,
'#default_value' => $RDATE,
'#date_timezone' => !empty($element['#date_timezone']) ? $element['#date_timezone'] : date_default_timezone(),
'#date_format' => !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d',
'#date_text_parts' => !empty($element['#date_text_parts']) ? $element['#date_text_parts'] : array(),
'#date_year_range' => !empty($element['#date_year_range']) ? $element['#date_year_range'] : '-3:+3',
'#date_label_position' => !empty($element['#date_label_position']) ? $element['#date_label_position'] : 'within',
'#date_flexible' => 0,
),
'tz' => array('#type' => 'hidden', '#value' => $element['#date_timezone']),
'all_day' => array('#type' => 'hidden', '#value' => 1),
'granularity' => array('#type' => 'hidden', '#value' => serialize(array('year', 'month', 'day'))),
);
}
$element['exceptions']['exceptions_add'] = array(
'#type' => 'submit',
'#name' => 'exceptions_add|' . $instance,
'#value' => t('Add exception'),
'#submit' => array('date_repeat_add_exception'),
'#limit_validation_errors' => array(),
'#ajax' => array(
'callback' => 'date_repeat_add_exception_callback',
'wrapper' => 'date-repeat-exceptions-' . $instance,
),
);
$element['additions']['additions_add'] = array(
'#type' => 'submit',
'#name' => 'additions_add|' . $instance,
'#value' => t('Add addition'),
'#submit' => array('date_repeat_add_addition'),
'#limit_validation_errors' => array(),
'#ajax' => array(
'callback' => 'date_repeat_add_addition_callback',
'wrapper' => 'date-repeat-additions-' . $instance,
),
);
$element['#date_repeat_collapsed'] = !empty($rrule['INTERVAL']) || !empty($rrule['FREQ']) ? 0 : (!empty($element['#date_repeat_collapsed']) ? $element['#date_repeat_collapsed'] : 0);
return $element;
}
function date_repeat_add_exception_callback($form, &$form_state) {
$parents = $form_state['triggering_element']['#array_parents'];
$button_key = array_pop($parents);
$element = drupal_array_get_nested_value($form, $parents);
return $element;
}
function date_repeat_add_addition_callback($form, &$form_state) {
$parents = $form_state['triggering_element']['#array_parents'];
$button_key = array_pop($parents);
$element = drupal_array_get_nested_value($form, $parents);
return $element;
}
function date_repeat_add_exception($form, &$form_state) {
$parents = $form_state['triggering_element']['#array_parents'];
$instance = implode('-', array_slice($parents, 0, count($parents) - 2));
$form_state['num_exceptions'][$instance]++;
$form_state['rebuild'] = TRUE;
}
function date_repeat_add_addition($form, &$form_state) {
$parents = $form_state['triggering_element']['#array_parents'];
$instance = implode('-', array_slice($parents, 0, count($parents) - 2));
$form_state['num_additions'][$instance]++;
$form_state['rebuild'] = TRUE;
}
/**
* Regroup values back into a consistant array, no matter what state it is in.
*/
function date_repeat_merge($form_values, $element) {
if (empty($form_values) || !is_array($form_values)) {
return $form_values;
}
if (array_key_exists('exceptions', $form_values) || array_key_exists('additions', $form_values)) {
if (!array_key_exists('exceptions', $form_values)) $form_values['exceptions'] = array();
if (!array_key_exists('additions', $form_values)) $form_values['additions'] = array();
$form_values = array_merge($form_values, (array) $form_values['exceptions'], (array) $form_values['additions']);
unset($form_values['exceptions']);
unset($form_values['additions']);
}
if (array_key_exists('FREQ', $form_values)) {
switch ($form_values['FREQ']) {
case 'DAILY':
if (array_key_exists('daily', $form_values)) {
switch ($form_values['daily']['byday_radios']) {
case 'INTERVAL':
$form_values['INTERVAL'] = $form_values['daily']['INTERVAL_child'];
break;
case 'every_weekday':
$form_values['BYDAY'] = array('MO', 'TU', 'WE', 'TH', 'FR');
break;
case 'every_mo_we_fr':
$form_values['BYDAY'] = array('MO', 'WE', 'FR');
break;
case 'every_tu_th':
$form_values['BYDAY'] = array('TU', 'TH');
break;
}
}
break;
case 'WEEKLY':
if (array_key_exists('weekly', $form_values)) {
$form_values = array_merge($form_values, (array) $form_values['weekly']);
if (array_key_exists('BYDAY', $form_values)) {
$form_values['BYDAY'] = date_repeat_transform_checkbox_values_to_select_values($form_values['BYDAY']);
}
}
break;
case 'MONTHLY':
if (array_key_exists('monthly', $form_values)) {
switch ($form_values['monthly']['day_month']) {
case 'BYMONTHDAY_BYMONTH':
$form_values['monthly'] = array_merge($form_values['monthly'], (array) $form_values['monthly']['BYMONTHDAY_BYMONTH_child']);
break;
case 'BYDAY_BYMONTH':
$form_values['monthly']['BYDAY_BYMONTH_child']['BYDAY'] = $form_values['monthly']['BYDAY_BYMONTH_child']['BYDAY_COUNT'] . $form_values['monthly']['BYDAY_BYMONTH_child']['BYDAY_DAY'];
$form_values['monthly'] = array_merge($form_values['monthly'], (array) $form_values['monthly']['BYDAY_BYMONTH_child']);
break;
}
unset($form_values['monthly']['BYDAY_BYMONTH_child']);
unset($form_values['monthly']['BYMONTHDAY_BYMONTH_child']);
$form_values = array_merge($form_values, (array) $form_values['monthly']);
if (array_key_exists('BYMONTH', $form_values)) {
$form_values['BYMONTH'] = date_repeat_transform_checkbox_values_to_select_values($form_values['BYMONTH']);
}
if (array_key_exists('BYMONTHDAY', $form_values) && !is_array($form_values['BYMONTHDAY'])) {
$form_values['BYMONTHDAY'] = (array) $form_values['BYMONTHDAY'];
}
if (array_key_exists('BYDAY', $form_values) && !is_array($form_values['BYDAY'])) {
$form_values['BYDAY'] = (array) $form_values['BYDAY'];
}
}
break;
case 'YEARLY':
if (array_key_exists('yearly', $form_values)) {
switch ($form_values['yearly']['day_month']) {
case 'BYMONTHDAY_BYMONTH':
$form_values['yearly'] = array_merge($form_values['yearly'], (array) $form_values['yearly']['BYMONTHDAY_BYMONTH_child']);
break;
case 'BYDAY_BYMONTH':
$form_values['yearly']['BYDAY_BYMONTH_child']['BYDAY'] = $form_values['yearly']['BYDAY_BYMONTH_child']['BYDAY_COUNT'] . $form_values['yearly']['BYDAY_BYMONTH_child']['BYDAY_DAY'];
$form_values['yearly'] = array_merge($form_values['yearly'], (array) $form_values['yearly']['BYDAY_BYMONTH_child']);
break;
}
unset($form_values['yearly']['BYDAY_BYMONTH_child']);
unset($form_values['yearly']['BYMONTHDAY_BYMONTH_child']);
$form_values = array_merge($form_values, (array) $form_values['yearly']);
if (array_key_exists('BYMONTH', $form_values)) {
$form_values['BYMONTH'] = date_repeat_transform_checkbox_values_to_select_values($form_values['BYMONTH']);
}
if (array_key_exists('BYMONTHDAY', $form_values) && !is_array($form_values['BYMONTHDAY'])) {
$form_values['BYMONTHDAY'] = (array) $form_values['BYMONTHDAY'];
}
if (array_key_exists('BYDAY', $form_values) && !is_array($form_values['BYDAY'])) {
$form_values['BYDAY'] = (array) $form_values['BYDAY'];
}
}
break;
default:
break;
}
}
unset($form_values['daily']);
unset($form_values['weekly']);
unset($form_values['monthly']);
unset($form_values['yearly']);
if (array_key_exists('range_of_repeat', $form_values)) {
switch ($form_values['range_of_repeat']) {
case 'COUNT':
$form_values['COUNT'] = $form_values['count_child'];
break;
case 'UNTIL':
$form_values['UNTIL'] = $form_values['until_child'];
break;
}
}
unset($form_values['count_child']);
unset($form_values['until_child']);
if (array_key_exists('BYDAY', $form_values) && is_array($form_values['BYDAY'])) unset($form_values['BYDAY']['']);
if (array_key_exists('BYMONTH', $form_values) && is_array($form_values['BYMONTH'])) unset($form_values['BYMONTH']['']);
if (array_key_exists('BYMONTHDAY', $form_values) && is_array($form_values['BYMONTHDAY'])) unset($form_values['BYMONTHDAY']['']);
if (array_key_exists('UNTIL', $form_values) && is_array($form_values['UNTIL']['datetime'])) {
$function = $element['#date_repeat_widget'] . '_input_date';
$until_element = $element;
$until_element['#date_format'] = !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d';
$date = $function($until_element, $form_values['UNTIL']['datetime']);
$form_values['UNTIL']['datetime'] = is_object($date) ? $date->format(DATE_FORMAT_DATETIME) : '';
}
if (array_key_exists('show_exceptions', $form_values) && $form_values['show_exceptions'] === 0) {
unset($form_values['EXDATE']);
}
if (array_key_exists('EXDATE', $form_values) && is_array($form_values['EXDATE'])) {
$function = $element['#date_repeat_widget'] . '_input_date';
$exdate_element = $element;
foreach ($form_values['EXDATE'] as $delta => $value) {
if (is_array($value['datetime'])) {
$exdate_element['#date_format'] = !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d';
$date = $function($exdate_element, $form_values['EXDATE'][$delta]['datetime']);
$form_values['EXDATE'][$delta]['datetime'] = is_object($date) ? $date->format(DATE_FORMAT_DATETIME) : '';
}
}
}
if (array_key_exists('show_additions', $form_values) && $form_values['show_additions'] === 0) {
unset($form_values['RDATE']);
}
if (array_key_exists('RDATE', $form_values) && is_array($form_values['RDATE'])) {
$function = $element['#date_repeat_widget'] . '_input_date';
$rdate_element = $element;
foreach ($form_values['RDATE'] as $delta => $value) {
if (is_array($value['datetime'])) {
$rdate_element['#date_format'] = !empty($element['#date_format']) ? date_limit_format($element['#date_format'], array('year', 'month', 'day')) : 'Y-m-d';
$date = $function($rdate_element, $form_values['RDATE'][$delta]['datetime']);
$form_values['RDATE'][$delta]['datetime'] = is_object($date) ? $date->format(DATE_FORMAT_DATETIME) : '';
}
}
}
return $form_values;
}
/**
* Build a RRULE out of the form values.
*/
function date_repeat_rrule_validate($element, &$form_state) {
if (date_hidden_element($element)) {
return;
}
$parents = $element['#parents'];
array_pop($parents);
$field_values = drupal_array_get_nested_value($form_state['values'], $parents);
if ($field_values['show_repeat_settings'] === 0 || $field_values['rrule']['FREQ'] === 'NONE') {
form_set_value($element, NULL, $form_state);
return;
}
// Clean the buttons off of the form. Needed to avoid errors when
// the date is used on a user object, which then passes the form
// through form_state_values_clean().
foreach ($form_state['buttons'] as $delta => $item) {
if (!empty($item['#ajax']['callback']) && in_array($item['#ajax']['callback'], array('date_repeat_add_exception_callback', 'date_repeat_add_addition_callback'))) {
unset($form_state['buttons'][$delta]);
}
}
module_load_include('inc', 'date_api', 'date_api_ical');
$item = drupal_array_get_nested_value($form_state['values'], $element['#parents']);
$item = date_repeat_merge($item, $element);
$rrule = date_api_ical_build_rrule($item);
form_set_value($element, $rrule, $form_state);
}
/**
* Theme the exception list as a table so the buttons line up
*/
function theme_date_repeat_current_exceptions($vars) {
$rows = $vars['rows'];
$rows_info = array();
foreach ($rows as $key => $value) {
if (substr($key, 0, 1) != '#') {
$rows_info[] = array(drupal_render($value['action']), drupal_render($value['display']));
}
}
return theme('table', array('header' => array(t('Delete'), t('Current exceptions')), 'rows' => $rows_info));
}
/**
* Theme the exception list as a table so the buttons line up
*/
function theme_date_repeat_current_additions($rows = array()) {
$rows_info = array();
foreach ($rows as $key => $value) {
if (substr($key, 0, 1) != '#') {
$rows_info[] = array(drupal_render($value['action']), drupal_render($value['display']));
}
}
return theme('table', array('header' => array(t('Delete'), t('Current additions')), 'rows' => $rows_info));
}
/**
* Wrapper fieldset for repeat rule.
*/
function theme_date_repeat_rrule($vars) {
$element = $vars['element'];
$class = $element['#date_repeat_collapsed'] ? array('date-no-float', 'collapsible', 'collapsed') : array('date-no-float', 'collapsible');
$id = drupal_html_id('repeat-settings-fieldset');
$parents = $element['#parents'];
$selector = "{$parents[0]}[{$parents[1]}][{$parents[2]}][show_repeat_settings]";
$fieldset = array(
'#type' => 'item',
'#title' => t('Repeat settings'),
'#title_display' => 'invisible',
'#attributes' => array('class' => $class),
'#markup' => $element['#children'],
'#states' => array(
'visible' => array(
":input[name=\"{$selector}\"]" => array('checked' => TRUE),
),
),
'#id' => $id,
);
return drupal_render($fieldset);
}
function date_repeat_filter_non_zero_value($value) {
return $value !== 0;
}
/**
* Helper function for transforming the return value of checkbox(es) element.
*
* Can be used for transforming the returned value of checkbox(es) element
* to the format of returned value of multiple select element.
*/
function date_repeat_transform_checkbox_values_to_select_values($values) {
return array_filter($values, 'date_repeat_filter_non_zero_value');
}

View File

@@ -0,0 +1,455 @@
<?php
/**
* @file
* Test Date Repeat calculations.
*/
class DateRepeatTestCase extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => t('Date Repeat'),
'description' => t('Test Date Repeat functions to create arrays of dates from iCal rules.') ,
'group' => t('Date'),
);
}
/**
* Implements setUp().
*/
public function setUp() {
// Load the date_repeat module.
parent::setUp('date_api', 'date_repeat');
}
public function testDateRepeat() {
require_once('./' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc');
require_once('./' . drupal_get_path('module', 'date_repeat') . '/date_repeat_calc.inc');
// Examples adapted from http://www.faqs.org/rfcs/rfc2445.html and
// http://www.kanzaki.com/docs/ical/rrule.html.
// Invalid value:
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=NONE;INTERVAL=0;COUNT=10";
$dates = date_repeat_calc($rule, $start, $end, array());
// should be (1997 9:00 AM EDT)September 2-11
$shouldbe = '';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
// Daily for 10 occurrences:
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=DAILY;COUNT=10";
$dates = date_repeat_calc($rule, $start, $end, array());
// should be (1997 9:00 AM EDT)September 2-11
$shouldbe = '1997-09-02 09:00:00, 1997-09-03 09:00:00, 1997-09-04 09:00:00, 1997-09-05 09:00:00, 1997-09-06 09:00:00, 1997-09-07 09:00:00, 1997-09-08 09:00:00, 1997-09-09 09:00:00, 1997-09-10 09:00:00, 1997-09-11 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Daily until September 24, 1997:
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=DAILY;UNTIL=19970924T000000Z";
$dates = date_repeat_calc($rule, $start, $end, array());
// should be (1997 9:00 AM EDT)September 2-23
$shouldbe = '1997-09-02 09:00:00, 1997-09-03 09:00:00, 1997-09-04 09:00:00, 1997-09-05 09:00:00, 1997-09-06 09:00:00, 1997-09-07 09:00:00, 1997-09-08 09:00:00, 1997-09-09 09:00:00, 1997-09-10 09:00:00, 1997-09-11 09:00:00, 1997-09-12 09:00:00, 1997-09-13 09:00:00, 1997-09-14 09:00:00, 1997-09-15 09:00:00, 1997-09-16 09:00:00, 1997-09-17 09:00:00, 1997-09-18 09:00:00, 1997-09-19 09:00:00, 1997-09-20 09:00:00, 1997-09-21 09:00:00, 1997-09-22 09:00:00, 1997-09-23 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every other day - until September 30:
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=DAILY;INTERVAL=2";
$dates = date_repeat_calc($rule, $start, $end, array());
// should be (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30;
$shouldbe = '1997-09-02 09:00:00, 1997-09-04 09:00:00, 1997-09-06 09:00:00, 1997-09-08 09:00:00, 1997-09-10 09:00:00, 1997-09-12 09:00:00, 1997-09-14 09:00:00, 1997-09-16 09:00:00, 1997-09-18 09:00:00, 1997-09-20 09:00:00, 1997-09-22 09:00:00, 1997-09-24 09:00:00, 1997-09-26 09:00:00, 1997-09-28 09:00:00, 1997-09-30 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every 10 days, 2 occurrences:
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=DAILY;INTERVAL=10;COUNT=2";
$dates = date_repeat_calc($rule, $start, $end, array());
// should be (1997 9:00 AM EDT)September 2,12
$shouldbe = '1997-09-02 09:00:00, 1997-09-12 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Weekly for 3 occurrences
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=WEEKLY;COUNT=3";
$dates = date_repeat_calc($rule, $start, $end, array());
// should be (1997 9:00 AM EDT)September 2,9,16
$shouldbe = '1997-09-02 09:00:00, 1997-09-09 09:00:00, 1997-09-16 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Weekly until September 24, 1997
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=WEEKLY;UNTIL=19970924T000000Z";
// ==> (1997 9:00 AM EDT)September 2,9,16,23
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-02 09:00:00, 1997-09-09 09:00:00, 1997-09-16 09:00:00, 1997-09-23 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every other week - forever:
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU";
// should be (1997 9:00 AM EDT)September 2,16,30
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-02 09:00:00, 1997-09-16 09:00:00, 1997-09-30 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Weekly on Tuesday and Thursday for 4 weeks:
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=WEEKLY;COUNT=8;WKST=SU;BYDAY=TU,TH";
// should be(1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-02 09:00:00, 1997-09-04 09:00:00, 1997-09-09 09:00:00, 1997-09-11 09:00:00, 1997-09-16 09:00:00, 1997-09-18 09:00:00, 1997-09-23 09:00:00, 1997-09-25 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every other week on Tuesday and Thursday, for 5 occurrences:
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=5;WKST=SU;BYDAY=TU,TH";
// should be (1997 9:00 AM EDT)September 2,4,16,18,30
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-02 09:00:00, 1997-09-04 09:00:00, 1997-09-16 09:00:00, 1997-09-18 09:00:00, 1997-09-30 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every other week on Monday, Wednesday and Friday until September 24, 1997,
$start = "1997-09-02 09:00:00";
$end = "1997-09-30 09:00:00";
$rule = "RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19970924T000000Z;WKST=SU;BYDAY=MO,WE,FR";
// should be (1997 9:00 AM EDT)September 2,3,5,15,17,19
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-02 09:00:00, 1997-09-03 09:00:00, 1997-09-05 09:00:00, 1997-09-15 09:00:00, 1997-09-17 09:00:00, 1997-09-19 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Monthly on the 1st Friday for 2 occurrences:
$start = "1997-09-05 09:00:00";
$end = "1997-10-31 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;COUNT=2;BYDAY=1FR";
// should be (1997 9:00 AM EDT)September 5;October 3
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-05 09:00:00, 1997-10-03 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Monthly on the 1st Friday until December 24, 1997:
$start = "1997-09-05 09:00:00";
$end = "1998-10-01 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR";
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-05 09:00:00, 1997-10-03 09:00:00, 1997-11-07 09:00:00, 1997-12-05 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every other month on the 1st and last Sunday of the month for 10 occurrences:
$start = "1997-09-07 09:00:00";
$end = "1998-10-01 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU";
// ==> (1997 9:00 AM EDT)September 7,28
// (1997 9:00 AM EST)November 2,30
// (1998 9:00 AM EST)January 4,25;March 1,29
// (1998 9:00 AM EDT)May 3,31
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-07 09:00:00, 1997-09-28 09:00:00, 1997-11-02 09:00:00, 1997-11-30 09:00:00, 1998-01-04 09:00:00, 1998-01-25 09:00:00, 1998-03-01 09:00:00, 1998-03-29 09:00:00, 1998-05-03 09:00:00, 1998-05-31 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Monthly on the second to last Monday of the month for 6 months:
$start = "1997-09-22 09:00:00";
$end = "1998-10-01 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO";
//==> (1997 9:00 AM EDT)September 22;October 20
// (1997 9:00 AM EST)November 17;December 22
// (1998 9:00 AM EST)January 19;February 16
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-22 09:00:00, 1997-10-20 09:00:00, 1997-11-17 09:00:00, 1997-12-22 09:00:00, 1998-01-19 09:00:00, 1998-02-16 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every Tuesday, every other month:
$start = "1997-09-02 09:00:00";
$end = "1998-02-01 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU";
// ==> (1997 9:00 AM EDT)September 2,9,16,23,30
// (1997 9:00 AM EST)November 4,11,18,25
// (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-02 09:00:00, 1997-09-09 09:00:00, 1997-09-16 09:00:00, 1997-09-23 09:00:00, 1997-09-30 09:00:00, 1997-11-04 09:00:00, 1997-11-11 09:00:00, 1997-11-18 09:00:00, 1997-11-25 09:00:00, 1998-01-06 09:00:00, 1998-01-13 09:00:00, 1998-01-20 09:00:00, 1998-01-27 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Yearly in June and July for 10 occurrences:
$start = "1997-06-10 09:00:00";
$end = "2002-01-01 09:00:00";
$rule = "RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7";
// ==> (1997 9:00 AM EDT)June 10;July 10
// (1998 9:00 AM EDT)June 10;July 10
// (1999 9:00 AM EDT)June 10;July 10
// (2000 9:00 AM EDT)June 10;July 10
// (2001 9:00 AM EDT)June 10;July 10
// Note: Since none of the BYDAY, BYMONTHDAY or BYYEARDAY components
// are specified, the day is gotten from DTSTART
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-06-10 09:00:00, 1997-07-10 09:00:00, 1998-06-10 09:00:00, 1998-07-10 09:00:00, 1999-06-10 09:00:00, 1999-07-10 09:00:00, 2000-06-10 09:00:00, 2000-07-10 09:00:00, 2001-06-10 09:00:00, 2001-07-10 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every other year on January, February, and March for 10 occurrences:
$start = "1997-03-10 09:00:00";
$end = "2004-01-01 09:00:00";
$rule = "RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3";
// ==> (1997 9:00 AM EST)March 10
// (1999 9:00 AM EST)January 10;February 10;March 10
// (2001 9:00 AM EST)January 10;February 10;March 10
// (2003 9:00 AM EST)January 10;February 10;March 10
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-03-10 09:00:00, 1999-01-10 09:00:00, 1999-02-10 09:00:00, 1999-03-10 09:00:00, 2001-01-10 09:00:00, 2001-02-10 09:00:00, 2001-03-10 09:00:00, 2003-01-10 09:00:00, 2003-02-10 09:00:00, 2003-03-10 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//An example where the days generated makes a difference because of WKST:
$start = "1997-08-05 09:00:00";
$end = "2004-01-01 09:00:00";
$rule = "RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO";
// ==> (1997 EDT)Aug 5,10,19,24
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-08-05 09:00:00, 1997-08-10 09:00:00, 1997-08-19 09:00:00, 1997-08-24 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//changing only WKST from MO to SU, yields different results...
$start = "1997-08-05 09:00:00";
$end = "2004-01-01 09:00:00";
$rule = "RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU";
// Result: 1997 EDT August 5,17,19,31;
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-08-05 09:00:00, 1997-08-17 09:00:00, 1997-08-19 09:00:00, 1997-08-31 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every 18 months on the 10th thru 15th of the month for 10 occurrences:
$start = "1997-09-10 09:00:00";
$end = "2004-01-01 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14,15";
// ==> (1997 9:00 AM EDT)September 10,11,12,13,14,15
// (1999 9:00 AM EST)March 10,11,12,13
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-10 09:00:00, 1997-09-11 09:00:00, 1997-09-12 09:00:00, 1997-09-13 09:00:00, 1997-09-14 09:00:00, 1997-09-15 09:00:00, 1999-03-10 09:00:00, 1999-03-11 09:00:00, 1999-03-12 09:00:00, 1999-03-13 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Monthly on the third to the last day of the month, forever:
$start = "1997-09-28 09:00:00";
$end = "1998-03-01 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;BYMONTHDAY=-3";
// ==> (1997 9:00 AM EDT)September 28
// (1997 9:00 AM EST)October 29;November 28;December 29
// (1998 9:00 AM EST)January 29;February 26
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-28 09:00:00, 1997-10-29 09:00:00, 1997-11-28 09:00:00, 1997-12-29 09:00:00, 1998-01-29 09:00:00, 1998-02-26 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every Thursday in March, forever:
// ==> (1997 9:00 AM EST)March 13,20,27
// (1998 9:00 AM EST)March 5,12,19,26
// (1999 9:00 AM EST)March 4,11,18,25
$start = "1997-03-13 09:00:00";
$end = "1999-03-31 09:00:00";
$rule = "RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH";
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-03-13 09:00:00, 1997-03-20 09:00:00, 1997-03-27 09:00:00, 1998-03-05 09:00:00, 1998-03-12 09:00:00, 1998-03-19 09:00:00, 1998-03-26 09:00:00, 1999-03-04 09:00:00, 1999-03-11 09:00:00, 1999-03-18 09:00:00, 1999-03-25 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every Thursday, but only during June, July, and August, forever:
// ==> (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31;August 7,14,21,28
// (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30;August 6,13,20,27
// (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29;August 5,12,19,26
$start = "1997-06-05 09:00:00";
$end = "1999-08-31 09:00:00";
$rule = "RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8";
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-06-05 09:00:00, 1997-06-12 09:00:00, 1997-06-19 09:00:00, 1997-06-26 09:00:00, 1997-07-03 09:00:00, 1997-07-10 09:00:00, 1997-07-17 09:00:00, 1997-07-24 09:00:00, 1997-07-31 09:00:00, 1997-08-07 09:00:00, 1997-08-14 09:00:00, 1997-08-21 09:00:00, 1997-08-28 09:00:00, 1998-06-04 09:00:00, 1998-06-11 09:00:00, 1998-06-18 09:00:00, 1998-06-25 09:00:00, 1998-07-02 09:00:00, 1998-07-09 09:00:00, 1998-07-16 09:00:00, 1998-07-23 09:00:00, 1998-07-30 09:00:00, 1998-08-06 09:00:00, 1998-08-13 09:00:00, 1998-08-20 09:00:00, 1998-08-27 09:00:00, 1999-06-03 09:00:00, 1999-06-10 09:00:00, 1999-06-17 09:00:00, 1999-06-24 09:00:00, 1999-07-01 09:00:00, 1999-07-08 09:00:00, 1999-07-15 09:00:00, 1999-07-22 09:00:00, 1999-07-29 09:00:00, 1999-08-05 09:00:00, 1999-08-12 09:00:00, 1999-08-19 09:00:00, 1999-08-26 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Monthly on the 2nd and 15th of the month for 10 occurrences:
// ==> (1997 9:00 AM EDT)September 2,15;October 2,15
// (1997 9:00 AM EST)November 2,15;December 2,15
// (1998 9:00 AM EST)January 2,15
$start = "1997-09-02 09:00:00";
$end = "1998-01-31 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15";
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-02 09:00:00, 1997-09-15 09:00:00, 1997-10-02 09:00:00, 1997-10-15 09:00:00, 1997-11-02 09:00:00, 1997-11-15 09:00:00, 1997-12-02 09:00:00, 1997-12-15 09:00:00, 1998-01-02 09:00:00, 1998-01-15 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Monthly on the first and last day of the month for 10 occurrences:
// ==> (1997 9:00 AM EDT)September 30;October 1
// (1997 9:00 AM EST)October 31;November 1,30;December 1,31
// (1998 9:00 AM EST)January 1,31;February 1
$start = "1997-09-30 09:00:00";
$end = "1998-03-31 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1";
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-30 09:00:00, 1997-10-01 09:00:00, 1997-10-31 09:00:00, 1997-11-01 09:00:00, 1997-11-30 09:00:00, 1997-12-01 09:00:00, 1997-12-31 09:00:00, 1998-01-01 09:00:00, 1998-01-31 09:00:00, 1998-02-01 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every Friday the 13th, forever:
$rule = "EXDATE;TZID=US-Eastern:19970902T090000";
// ==> (1998 9:00 AM EST)February 13;March 13;November 13
// (1999 9:00 AM EDT)August 13
// (2000 9:00 AM EDT)October 13
$start = "1997-09-02 09:00:00";
$end = "2000-12-31 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13";
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-02 09:00:00, 1998-02-13 09:00:00, 1998-03-13 09:00:00, 1998-11-13 09:00:00, 1999-08-13 09:00:00, 2000-10-13 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//The first Saturday that follows the first Sunday of the month, forever:
// ==> (1997 9:00 AM EDT)September 13;October 11
// (1997 9:00 AM EST)November 8;December 13
// (1998 9:00 AM EST)January 10;February 7;March 7
// (1998 9:00 AM EDT)April 11;May 9;June 13...
$start = "1997-09-13 09:00:00";
$end = "1998-06-30 09:00:00";
$rule = "RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13";
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-09-13 09:00:00, 1997-10-11 09:00:00, 1997-11-08 09:00:00, 1997-12-13 09:00:00, 1998-01-10 09:00:00, 1998-02-07 09:00:00, 1998-03-07 09:00:00, 1998-04-11 09:00:00, 1998-05-09 09:00:00, 1998-06-13 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every four years, the first Tuesday after a Monday in November,
//forever (U.S. Presidential Election day):
// ==> (1996 9:00 AM EST)November 5
// (2000 9:00 AM EST)November 7
// (2004 9:00 AM EST)November 2
$start = "1996-11-05 09:00:00";
$end = "2004-11-30 09:00:00";
$rule = "RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4,5,6,7,8";
$shouldbe = '1996-11-05 09:00:00, 2000-11-07 09:00:00, 2004-11-02 09:00:00';
$dates = date_repeat_calc($rule, $start, $end, array());
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every 20th Monday of the year, forever:
$start = "1997-05-19 09:00:00";
$end = "2000-01-01 09:00:00";
$rule = "RRULE:FREQ=YEARLY;BYDAY=20MO";
// ==> (1997 9:00 AM EDT)May 19
// (1998 9:00 AM EDT)May 18
// (1999 9:00 AM EDT)May 17
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-05-19 09:00:00, 1998-05-18 09:00:00, 1999-05-17 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
//Every Sunday in January, every other year, forever:
$start = "1997-01-05 09:00:00";
$end = "2001-02-01 09:00:00";
$rule = 'RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU';
// ==> (1997 9:00 AM EDT)January 5,12,19,26
// (1999 9:00 AM EDT)January 3,10,17,24,31
// (2001 9:00 AM EDT)January 7,14,21,28
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '1997-01-05 09:00:00, 1997-01-12 09:00:00, 1997-01-19 09:00:00, 1997-01-26 09:00:00, 1999-01-03 09:00:00, 1999-01-10 09:00:00, 1999-01-17 09:00:00, 1999-01-24 09:00:00, 1999-01-31 09:00:00, 2001-01-07 09:00:00, 2001-01-14 09:00:00, 2001-01-21 09:00:00, 2001-01-28 09:00:00';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
return;
//Every Thanksgiving, forever:
$start = "1997-01-01 09:00:00";
$end = "2001-02-01 09:00:00";
$rule = 'RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=4TH';
// ==> (1997 9:00 AM EDT)Nov
// (1999 9:00 AM EDT)Nov
// (2001 9:00 AM EDT)Nov
$dates = date_repeat_calc($rule, $start, $end, array());
$shouldbe = '';
$result = implode(', ', $dates);
$this->assertEqual($result, $shouldbe, $rule . '; Starting ' . $start . '; results: ' . $result);
// TODO:
// BYYEARDAY, BYSETPOS,
// BYHOUR, BYMINUTE, HOURLY, MINUTELY, SECONDLY
// have not yet been implemented in date_repeat.
//Every 3rd year on the 1st, 100th and 200th day for 10 occurrences:
$date = "DTSTART;TZID=US-Eastern:19970101T090000";
$rule = "RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200";
// ==> (1997 9:00 AM EST)January 1
// (1997 9:00 AM EDT)April 10;July 19
// (2000 9:00 AM EST)January 1
// (2000 9:00 AM EDT)April 9;July 18
// (2003 9:00 AM EST)January 1
// (2003 9:00 AM EDT)April 10;July 19
// (2006 9:00 AM EST)January 1
//Monday of week number 20 (where the default start of the week is Monday), forever:
$date = "DTSTART;TZID=US-Eastern:19970512T090000";
$rule = "RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO";
// ==> (1997 9:00 AM EDT)May 12
// (1998 9:00 AM EDT)May 11
// (1999 9:00 AM EDT)May 17
//The 3rd instance into the month of one of Tuesday, Wednesday or
//Thursday, for the next 3 months:
$date = "DTSTART;TZID=US-Eastern:19970904T090000";
$rule = "RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3";
// ==> (1997 9:00 AM EDT)September 4;October 7
// (1997 9:00 AM EST)November 6
//The 2nd to last weekday of the month:
$date = "DTSTART;TZID=US-Eastern:19970929T090000";
$rule = "RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2";
// ==> (1997 9:00 AM EDT)September 29
// (1997 9:00 AM EST)October 30;November 27;December 30
// (1998 9:00 AM EST)January 29;February 26;March 30
//Every 3 hours from 9:00 AM to 5:00 PM on a specific day:
$date = "DTSTART;TZID=US-Eastern:19970902T090000";
$rule = "RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z";
// ==> (September 2, 1997 EDT)09:00,12:00,15:00
//Every 15 minutes for 6 occurrences:
$date = "DTSTART;TZID=US-Eastern:19970902T090000";
$rule = "RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6";
// ==> (September 2, 1997 EDT)09:00,09:15,09:30,09:45,10:00,10:15
//Every hour and a half for 4 occurrences:
$date = "DTSTART;TZID=US-Eastern:19970902T090000";
$rule = "RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4";
// ==> (September 2, 1997 EDT)09:00,10:30;12:00;13:30
//Every 20 minutes from 9:00 AM to 4:40 PM every day:
$date = "DTSTART;TZID=US-Eastern:19970902T090000";
$rule = "RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40";
// or
$rule = "RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16";
// ==> (September 2, 1997 EDT)9:00,9:20,9:40,10:00,10:20,16:00,16:20,16:40
// (September 3, 1997 EDT)9:00,9:20,9:40,10:00,10:20,16:00,16:20,16:40
}
}

View File

@@ -0,0 +1,520 @@
<?php
/**
* @file
* Test Date Repeat form.
*/
class DateRepeatFormTestCase extends DrupalWebTestCase {
protected $privileged_user;
public static function getInfo() {
return array(
'name' => t('Date Repeat Form'),
'description' => t('Test Date Repeat form.') ,
'group' => t('Date'),
);
}
/**
* Implements setUp().
*/
public function setUp() {
// Load the date_repeat module.
parent::setUp('field', 'field_ui', 'date_api', 'date_repeat', 'date', 'date_popup', 'date_repeat_field');
// Create and log in our privileged user.
$this->privileged_user = $this->drupalCreateUser(array(
'administer content types', 'administer nodes', 'bypass node access', 'view date repeats'
));
$this->drupalLogin($this->privileged_user);
variable_set('date_format_short', 'Y-m-d H:i');
}
public function testDateRepeatForm() {
$edit = array();
$edit['name'] = 'Date';
$edit['type'] = 'date';
$this->drupalPost('admin/structure/types/add', $edit, t('Save content type'));
$this->assertText('The content type Date has been added.', 'Content type added.');
$display_all_day = FALSE;
// Testing options.
$widget_options = array(
'date_select' => 'select',
'date_text' => 'text',
'date_popup' => 'popup');
foreach ($widget_options as $widget => $options) {
// Daily tests
// Creates date field stored as a datetime.
$this->createDateField($type = 'datetime', $widget, $display_all_day);
$form_edit = $this->dateForm($options, 'daily_1');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_2');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_3');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_4');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_1', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_2', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_3', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_4', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_1', FALSE, 'exclude');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_1', FALSE, 'include');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'daily_1', FALSE, 'exclude_include');
$this->verifyDateForm($form_edit);
// Weekly tests
$form_edit = $this->dateForm($options, 'weekly_1');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'weekly_2');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'weekly_3');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'weekly_4');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'weekly_1', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'weekly_2', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'weekly_3', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'weekly_4', TRUE);
$this->verifyDateForm($form_edit);
// Monthly tests
$form_edit = $this->dateForm($options, 'monthly_1');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_2');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_3');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_4');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_5');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_6');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_1', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_2', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_3', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_4', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_5', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'monthly_6', TRUE);
$this->verifyDateForm($form_edit);
// Yearly tests
$form_edit = $this->dateForm($options, 'yearly_1');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_2');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_3');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_4');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_5');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_6');
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_1', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_2', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_3', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_4', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_5', TRUE);
$this->verifyDateForm($form_edit);
$form_edit = $this->dateForm($options, 'yearly_6', TRUE);
$this->verifyDateForm($form_edit);
$this->deleteDateField();
}
}
function dateForm($options, $test_id = NULL, $is_count = FALSE, $exclude_include = NULL) {
// Tests that date field functions properly.
$edit = array();
$edit['title'] = $this->randomName(8);
$edit['body[und][0][value]'] = $this->randomName(16);
switch ($options) {
case 'select':
$edit['field_test[und][0][value][year]'] = '2010';
$edit['field_test[und][0][value][month]'] = '10';
$edit['field_test[und][0][value][day]'] = '7';
$edit['field_test[und][0][value][hour]'] = '10';
$edit['field_test[und][0][value][minute]'] = '30';
break;
case 'text':
$edit['field_test[und][0][value][date]'] = '2010-10-07 10:30';
break;
case 'popup':
$edit['field_test[und][0][value][date]'] = '2010-10-07';
$edit['field_test[und][0][value][time]'] = '10:30';
break;
}
// Tests that Date repeat settings function properly
if ($test_id !== NULL) {
$edit['field_test[und][0][show_repeat_settings]'] = TRUE;
$count = 5;
switch ($test_id) {
// Daily test cases
case 'daily_1':
$edit['field_test[und][0][rrule][FREQ]'] = 'DAILY';
$edit['field_test[und][0][rrule][daily][byday_radios]'] = 'INTERVAL';
$edit['field_test[und][0][rrule][daily][INTERVAL_child]'] = 2;
break;
case 'daily_2':
$edit['field_test[und][0][rrule][FREQ]'] = 'DAILY';
$edit['field_test[und][0][rrule][daily][byday_radios]'] = 'every_weekday';
break;
case 'daily_3':
$edit['field_test[und][0][rrule][FREQ]'] = 'DAILY';
$edit['field_test[und][0][rrule][daily][byday_radios]'] = 'every_mo_we_fr';
break;
case 'daily_4':
$edit['field_test[und][0][rrule][FREQ]'] = 'DAILY';
$edit['field_test[und][0][rrule][daily][byday_radios]'] = 'every_tu_th';
break;
// Weekly test cases
case 'weekly_1':
$edit['field_test[und][0][rrule][FREQ]'] = 'WEEKLY';
$edit['field_test[und][0][rrule][weekly][BYDAY][MO]'] = TRUE;
$edit['field_test[und][0][rrule][weekly][BYDAY][WE]'] = TRUE;
$edit['field_test[und][0][rrule][weekly][BYDAY][TH]'] = TRUE;
break;
case 'weekly_2':
$edit['field_test[und][0][rrule][FREQ]'] = 'WEEKLY';
$edit['field_test[und][0][rrule][weekly][INTERVAL]'] = 1;
$edit['field_test[und][0][rrule][weekly][BYDAY][MO]'] = TRUE;
$edit['field_test[und][0][rrule][weekly][BYDAY][FR]'] = TRUE;
break;
case 'weekly_3':
$edit['field_test[und][0][rrule][FREQ]'] = 'WEEKLY';
$edit['field_test[und][0][rrule][weekly][INTERVAL]'] = 2;
$edit['field_test[und][0][rrule][weekly][BYDAY][TU]'] = TRUE;
$edit['field_test[und][0][rrule][weekly][BYDAY][TH]'] = TRUE;
break;
case 'weekly_4':
$edit['field_test[und][0][rrule][FREQ]'] = 'WEEKLY';
$edit['field_test[und][0][rrule][weekly][INTERVAL]'] = 10;
$edit['field_test[und][0][rrule][weekly][BYDAY][MO]'] = TRUE;
$edit['field_test[und][0][rrule][weekly][BYDAY][TU]'] = TRUE;
$edit['field_test[und][0][rrule][weekly][BYDAY][WE]'] = TRUE;
$edit['field_test[und][0][rrule][weekly][BYDAY][TH]'] = TRUE;
$edit['field_test[und][0][rrule][weekly][BYDAY][FR]'] = TRUE;
break;
// Monthly test cases
case 'monthly_1':
$edit['field_test[und][0][rrule][FREQ]'] = 'MONTHLY';
$edit['field_test[und][0][rrule][monthly][day_month]'] = 'BYDAY_BYMONTH';
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYDAY_COUNT]'] = '+1';
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYDAY_DAY]'] = 'FR';
break;
case 'monthly_2':
$edit['field_test[und][0][rrule][FREQ]'] = 'MONTHLY';
$edit['field_test[und][0][rrule][monthly][day_month]'] = 'BYDAY_BYMONTH';
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYDAY_COUNT]'] = '+1';
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYDAY_DAY]'] = 'SU';
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYMONTH][1]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYMONTH][3]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYMONTH][5]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYMONTH][7]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYMONTH][9]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYMONTH][11]'] = TRUE;
break;
case 'monthly_3':
$edit['field_test[und][0][rrule][FREQ]'] = 'MONTHLY';
$edit['field_test[und][0][rrule][monthly][day_month]'] = 'BYDAY_BYMONTH';
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYDAY_COUNT]'] = '-2';
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYDAY_DAY]'] = 'MO';
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYMONTH][6]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYDAY_BYMONTH_child][BYMONTH][12]'] = TRUE;
break;
case 'monthly_4':
$edit['field_test[und][0][rrule][FREQ]'] = 'MONTHLY';
$edit['field_test[und][0][rrule][monthly][day_month]'] = 'BYMONTHDAY_BYMONTH';
$edit['field_test[und][0][rrule][monthly][BYMONTHDAY_BYMONTH_child][BYMONTHDAY]'] = '10';
break;
case 'monthly_5':
$edit['field_test[und][0][rrule][FREQ]'] = 'MONTHLY';
$edit['field_test[und][0][rrule][monthly][day_month]'] = 'BYMONTHDAY_BYMONTH';
$edit['field_test[und][0][rrule][monthly][BYMONTHDAY_BYMONTH_child][BYMONTHDAY]'] = '10';
$edit['field_test[und][0][rrule][monthly][BYMONTHDAY_BYMONTH_child][BYMONTH][1]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYMONTHDAY_BYMONTH_child][BYMONTH][2]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYMONTHDAY_BYMONTH_child][BYMONTH][3]'] = TRUE;
break;
case 'monthly_6':
$edit['field_test[und][0][rrule][FREQ]'] = 'MONTHLY';
$edit['field_test[und][0][rrule][monthly][day_month]'] = 'BYMONTHDAY_BYMONTH';
$edit['field_test[und][0][rrule][monthly][BYMONTHDAY_BYMONTH_child][BYMONTHDAY]'] = '-5';
$edit['field_test[und][0][rrule][monthly][BYMONTHDAY_BYMONTH_child][BYMONTH][2]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYMONTHDAY_BYMONTH_child][BYMONTH][4]'] = TRUE;
$edit['field_test[und][0][rrule][monthly][BYMONTHDAY_BYMONTH_child][BYMONTH][6]'] = TRUE;
break;
// Yearly test cases
case 'yearly_1':
$edit['field_test[und][0][rrule][FREQ]'] = 'YEARLY';
$edit['field_test[und][0][rrule][yearly][day_month]'] = 'BYDAY_BYMONTH';
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYDAY_COUNT]'] = '+1';
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYDAY_DAY]'] = 'FR';
break;
case 'yearly_2':
$edit['field_test[und][0][rrule][FREQ]'] = 'YEARLY';
$edit['field_test[und][0][rrule][yearly][INTERVAL]'] = 2;
$edit['field_test[und][0][rrule][yearly][day_month]'] = 'BYDAY_BYMONTH';
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYDAY_COUNT]'] = '+1';
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYDAY_DAY]'] = 'SU';
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYMONTH][1]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYMONTH][3]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYMONTH][5]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYMONTH][7]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYMONTH][9]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYMONTH][11]'] = TRUE;
break;
case 'yearly_3':
$edit['field_test[und][0][rrule][FREQ]'] = 'YEARLY';
$edit['field_test[und][0][rrule][yearly][INTERVAL]'] = 3;
$edit['field_test[und][0][rrule][yearly][day_month]'] = 'BYDAY_BYMONTH';
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYDAY_COUNT]'] = '-2';
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYDAY_DAY]'] = 'MO';
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYMONTH][6]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYDAY_BYMONTH_child][BYMONTH][12]'] = TRUE;
break;
case 'yearly_4':
$edit['field_test[und][0][rrule][FREQ]'] = 'YEARLY';
$edit['field_test[und][0][rrule][yearly][day_month]'] = 'BYMONTHDAY_BYMONTH';
$edit['field_test[und][0][rrule][yearly][BYMONTHDAY_BYMONTH_child][BYMONTHDAY]'] = '10';
break;
case 'yearly_5':
$edit['field_test[und][0][rrule][FREQ]'] = 'YEARLY';
$edit['field_test[und][0][rrule][yearly][INTERVAL]'] = 2;
$edit['field_test[und][0][rrule][yearly][day_month]'] = 'BYMONTHDAY_BYMONTH';
$edit['field_test[und][0][rrule][yearly][BYMONTHDAY_BYMONTH_child][BYMONTHDAY]'] = '10';
$edit['field_test[und][0][rrule][yearly][BYMONTHDAY_BYMONTH_child][BYMONTH][1]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYMONTHDAY_BYMONTH_child][BYMONTH][2]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYMONTHDAY_BYMONTH_child][BYMONTH][3]'] = TRUE;
break;
case 'yearly_6':
$edit['field_test[und][0][rrule][FREQ]'] = 'YEARLY';
$edit['field_test[und][0][rrule][yearly][INTERVAL]'] = 3;
$edit['field_test[und][0][rrule][yearly][day_month]'] = 'BYMONTHDAY_BYMONTH';
$edit['field_test[und][0][rrule][yearly][BYMONTHDAY_BYMONTH_child][BYMONTHDAY]'] = '-5';
$edit['field_test[und][0][rrule][yearly][BYMONTHDAY_BYMONTH_child][BYMONTH][2]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYMONTHDAY_BYMONTH_child][BYMONTH][4]'] = TRUE;
$edit['field_test[und][0][rrule][yearly][BYMONTHDAY_BYMONTH_child][BYMONTH][6]'] = TRUE;
break;
}
// Test COUNT or UNTIL (default)
if ($is_count) {
$edit['field_test[und][0][rrule][range_of_repeat]'] = 'COUNT';
$edit['field_test[und][0][rrule][count_child]'] = $count;
}
else {
$edit['field_test[und][0][rrule][range_of_repeat]'] = 'UNTIL';
$date = array(
'year' => '2011',
'month' => '10',
'day' => '07'
);
$edit += $this->formatDateForRRULEInputs('field_test[und][0][rrule][until_child]', $options, $date);
}
// Test date exceptions and/or additions
if ($exclude_include !== NULL) {
$exclude_include_edit = array();
switch ($exclude_include) {
case 'exclude':
$exclude_include_edit['field_test[und][0][rrule][show_exceptions]'] = TRUE;
$date = array(
'year' => '2010',
'month' => '10',
'day' => '07'
);
$exclude_include_edit += $this->formatDateForRRULEInputs('field_test[und][0][rrule][exceptions][EXDATE][0]', $options, $date);
break;
case 'include':
$exclude_include_edit['field_test[und][0][rrule][show_additions]'] = TRUE;
$date = array(
'year' => '2013',
'month' => '10',
'day' => '07'
);
$exclude_include_edit += $this->formatDateForRRULEInputs('field_test[und][0][rrule][additions][RDATE][0]', $options, $date);
break;
case 'exclude_include':
$exclude_include_edit['field_test[und][0][rrule][show_exceptions]'] = TRUE;
$date = array(
'year' => '2010',
'month' => '10',
'day' => '07'
);
$exclude_include_edit += $this->formatDateForRRULEInputs('field_test[und][0][rrule][exceptions][EXDATE][0]', $options, $date);
$exclude_include_edit['field_test[und][0][rrule][show_additions]'] = TRUE;
$date = array(
'year' => '2013',
'month' => '10',
'day' => '07'
);
$exclude_include_edit += $this->formatDateForRRULEInputs('field_test[und][0][rrule][additions][RDATE][0]', $options, $date);
break;
}
$edit += $exclude_include_edit;
}
}
$this->drupalPost('node/add/date', $edit, t('Save'));
$this->assertText($edit['body[und][0][value]'], 'Test node has been created');
// Return the settings for later use in verification
return $edit;
}
function verifyDateForm($edit) {
$title = $edit['title'];
$node = $this->drupalGetNodeByTitle($title);
$this->drupalGet("node/{$node->nid}/edit");
foreach ($edit as $field => $value) {
$this->assertFieldByName($field, $value);
}
$this->drupalPost("node/{$node->nid}/delete", NULL, t('Delete'));
$this->assertRaw(t('Date %title has been deleted.', array('%title' => $title)), t('Deleted Date content.'));
}
function createDateField($type, $widget, $display_all_day = FALSE) {
$edit = array();
$edit['fields[_add_new_field][label]'] = 'Test';
$edit['fields[_add_new_field][field_name]'] = 'test';
$edit['fields[_add_new_field][weight]'] = '-4';
$edit['fields[_add_new_field][type]'] = $type;
$edit['fields[_add_new_field][widget_type]'] = $widget;
$label = $edit['fields[_add_new_field][label]'];
$field_edit = array();
$field_edit['field[settings][repeat]'] = 1;
$instance_edit = array();
switch ($widget) {
case 'select':
case 'popup':
$instance_edit['instance[widget][settings][year_range][years_back]'] = '-5';
$instance_edit['instance[widget][settings][year_range][years_forward]'] = '+5';
break;
}
if ($display_all_day) {
$instance_edit['instance[widget][settings][display_all_day]'] = TRUE;
}
// First step : 'Add new field' on the 'Manage fields' page.
$this->drupalPost('admin/structure/types/manage/date/fields', $edit, t('Save'));
$this->assertRaw(t('These settings apply to the %label field everywhere it is used.', array('%label' => $label)), t('Field settings page was displayed.'));
// Second step : 'Field settings' form.
$this->drupalPost(NULL, $field_edit, t('Save field settings'));
$this->assertRaw(t('Updated field %label field settings.', array('%label' => $label)), t('Redirected to instance and widget settings page.'));
// Third step : 'Instance settings' form.
$this->drupalPost(NULL, $instance_edit, t('Save settings'));
$this->assertRaw(t('Saved %label configuration.', array('%label' => $label)), t('Redirected to "Manage fields" page.'));
// Check that the field appears in the overview form.
$this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', $label, t('Field was created and appears in the overview page.'));
}
function deleteDateField() {
$this->drupalGet('admin/structure/types/manage/date/fields');
$this->clickLink('delete');
$this->drupalPost(NULL, NULL, t('Delete'));
$this->assertText(t('The field Test has been deleted from the Date content type.'), t('Removed date field.'));
}
function formatDateForRRULEInputs($form_field_name, $date_widget_type, $date) {
$return = array();
switch ($date_widget_type) {
case 'select':
$return["{$form_field_name}[datetime][year]"] = $date['year'];
$return["{$form_field_name}[datetime][month]"] = $date['month'];
$return["{$form_field_name}[datetime][day]"] = ltrim($date['day'], '0');
break;
case 'text':
case 'popup':
//$return["{$field_name}[datetime][date]"] = '2011-10-07';
$return["{$form_field_name}[datetime][date]"] = "{$date['year']}-{$date['month']}-{$date['day']}";
break;
}
return $return;
}
}

View File

@@ -0,0 +1,4 @@
Date Repeat Field
The functionality to integrate the Date Repeat API into date fields is being moved into this module,
which can then be enabled or disabled, depending on whether repeating date fields are needed.

View File

@@ -0,0 +1,109 @@
.date-repeat-input {
float: left; /* LTR */
margin-right: 5px; /* LTR */
width: auto;
}
.date-repeat-input select {
min-width: 7em;
}
.date-repeat fieldset {
clear: both;
float: none;
}
.date-repeat-radios {
margin-bottom: 1em;
}
.date-repeat-radios input[type=radio] {
float: left;
margin: 0.75em 0.75em 0 0;
}
.date-repeat-radios .form-wrapper {
float: left;
}
.date-repeat-radios .form-type-checkboxes .form-type-checkbox {
width: 15%;
float: left;
margin: 0;
}
.date-repeat-radios .date-repeat-radios-item {
margin-bottom: 1em;
}
.weekly .form-type-checkboxes .form-type-checkbox {
float: left;
margin-right: 10px;
}
.date-repeat-input.byday-count label,
.date-repeat-input.byday-count select,
.date-clear.bymonthday label,
.date-clear.bymonthday select {
display: inline;
}
.date-repeat-input.byday-day label,
.date-clear.bymonthday .field-suffix {
font-weight: bold;
}
.range-of-repeat .form-radios > div {
margin-top: 0.5em;
}
.range-of-repeat .count input[type=text] {
margin: 0 0.5em;
}
.range-of-repeat .until .form-wrapper {
margin: 0 0.5em;
display: inline-block;
vertical-align: middle;
}
.range-of-repeat .until .form-radio,
.range-of-repeat .until .date-prefix-inline {
margin: 0 0 1.4em 0;
vertical-align: middle;
}
.range-of-repeat .until.widget-date_popup.label-above .form-radio,
.range-of-repeat .until.widget-date_popup.label-above .date-prefix-inline {
margin: 0;
vertical-align: middle;
}
.range-of-repeat .until.widget-date_select.label-within .form-radio,
.range-of-repeat .until.widget-date_select.label-within .date-prefix-inline {
margin: 0;
vertical-align: middle;
}
.range-of-repeat .until.widget-date_select.label-above .form-radio,
.range-of-repeat .until.widget-date_select.label-above .date-prefix-inline {
margin: 1.4em 0 0 0;
vertical-align: middle;
}
.range-of-repeat .until .form-type-date-text .date-padding {
padding: 0;
}
.range-of-repeat .until.widget-date_select.label-within .date-padding {
padding: 0;
}
.range-of-repeat .until.widget-date_select.label-above .date-padding {
padding: 0;
}
.range-of-repeat .until .form-type-date-select,
.range-of-repeat .until .form-type-date-popup,
.range-of-repeat .until .form-type-date-text {
border: none;
margin: 0;
}

View File

@@ -0,0 +1,158 @@
<?php
/*
* @file
* Handling of devel generate functionality for repeating dates.
*/
/**
* Implements hook_date_field_insert().
*
* A substitute for hook_devel_generate to handle repeating dates.
*/
function date_repeat_field_date_field_insert(&$items, $context) {
$entity_type = $context['entity_type'];
$entity = $context['entity'];
$field = $context['field'];
$instance = $context['instance'];
$langcode = $context['langcode'];
// The first value was already created by the regular Devel Generate code.
// Skipping doing anything if there is no value for this field.
if (empty($items)) {
return;
}
$item = $items[0];
// Unset any previous dates past the first one.
$count = count($items);
for ($i = 1; $i < $count; $i++) {
unset($items[$i]);
}
// Compute repeating date values.
module_load_include('inc', 'date_repeat', 'date_repeat_calc');
module_load_include('inc', 'date_api', 'date_api_ical');
$increment = $instance['widget']['settings']['increment'];
$timezone = date_get_timezone($field['settings']['tz_handling'], $item['timezone']);
$timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
switch ($field['type']) {
case 'date':
$format = DATE_FORMAT_ISO;
break;
case 'datestamp':
$format = DATE_FORMAT_UNIX;
break;
case 'datetime':
$format = DATE_FORMAT_DATETIME;
break;
}
$start = new dateObject($item['value'], $timezone_db, $format);
$start2 = new dateObject($item['value2'], $timezone_db, $format);
// Create a repeating date rule.
$duration = $start->difference($start2);
$form_values = array();
// Create the default case more frequently than case 1 or 2.
$which = mt_rand(0, 10);
$max_items = mt_rand(3, 10);
$intervals = array_keys(date_repeat_interval_options());
unset($intervals[0]);
$interval = $intervals[mt_rand(1, 3)];
switch ($which) {
case 1:
$mo = mt_rand(1, 28);
$options = array('YEARLY', 'MONTHLY');
$freq = date_content_generate_key($options);
$freq = $options[$freq];
$form_values['FREQ'] = $freq;
// Make sure we'll find a match in our range.
if ($freq == 'YEARLY') {
$interval = 1;
}
$form_values['BYMONTHDAY'] = array($mo);
break;
case 2:
$mo = mt_rand(1, 12);
$options = array('YEARLY', 'MONTHLY');
$freq = date_content_generate_key($options);
$freq = $options[$freq];
$form_values['FREQ'] = $freq;
// Make sure we'll find a match in our range.
if ($freq == 'YEARLY') {
$interval = 1;
}
$form_values['BYMONTH'] = array($mo);
break;
default:
$dows = array_keys(date_content_repeat_dow_options());
$day = date_content_generate_key($dows);
$dow = $dows[$day];
$options = array('MONTHLY', 'DAILY', 'WEEKLY');
$freq = date_content_generate_key($options);
$freq = $options[$freq];
$form_values['FREQ'] = $freq;
$form_values['BYDAY'] = array($dow);
break;
}
$form_values['INTERVAL'] = $interval;
switch ($freq) {
case 'YEARLY':
$period = 'year';
break;
case 'MONTHLY':
$period = 'month';
break;
case 'WEEKLY':
$period = 'week';
break;
default:
$period = 'day';
break;
}
$form_values['UNTIL'] = array();
$form_values['COUNT'] = $max_items;
$rrule = date_api_ical_build_rrule($form_values);
$items[0]['rrule'] = $rrule;
$values = date_repeat_build_dates($rrule, $form_values, $field, $item);
$items += $values;
}
function date_content_generate_key($array) {
$keys = array_keys($array);
$min = array_shift($keys);
$max = array_pop($keys);
return mt_rand($min, $max);
}
/**
* Helper function for BYDAY options.
*
* Creates options like -1SU and 2TU
* Omit options that won't find many matches, like 5th Sunday.
*/
function date_content_repeat_dow_options() {
$options = array();
foreach (date_repeat_dow_count_options() as $count_key => $count_value) {
foreach (date_repeat_dow_day_options() as $dow_key => $dow_value) {
if ($count_key != 5 && $count_key != -5) {
$options[$count_key . $dow_key] = $count_value . ' ' . $dow_value;
}
}
}
return $options;
}

View File

@@ -0,0 +1,15 @@
name = Date Repeat Field
description = Creates the option of Repeating date fields and manages Date fields that use the Date Repeat API.
dependencies[] = date_api
dependencies[] = date
dependencies[] = date_repeat
stylesheets[all][] = date_repeat_field.css
package = Date/Time
core = 7.x
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,714 @@
<?php
/**
* @file
* 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.
*/
/**
* Implements hook_theme().
*/
function date_repeat_field_theme() {
$themes = array(
'date_repeat_display' => array(
'variables' => array(
'field' => NULL,
'item' => NULL,
'entity_type' => NULL,
'entity' => NULL,
'dates' => NULL
),
'function' => 'theme_date_repeat_display',
),
);
return $themes;
}
/**
* Theme the human-readable description for a Date Repeat rule.
*
* TODO -
* add in ways to store the description in the date so it isn't regenerated
* over and over and find a way to allow description to be shown or hidden.
*/
function theme_date_repeat_display($vars) {
$field = $vars['field'];
$item = $vars['item'];
$entity = !empty($vars['node']) ? $vars['node'] : NULL;
$output = '';
if (!empty($item['rrule'])) {
$output = date_repeat_rrule_description($item['rrule']);
$output = '<div>' . $output . '</div>';
}
return $output;
}
/**
* Implements hook_menu().
*
* Add menu tabs to display pages with details about repeating date values.
*/
function date_repeat_field_menu() {
$items = array();
$values = date_repeat_field_bundles();
foreach ($values as $entity_type => $bundles) {
if (module_exists('field_collection') && $entity_type == 'field_collection_item') {
foreach ($bundles as $bundle => $fields) {
$field = field_info_field($bundle);
if ($field['type'] == 'field_collection') {
$path = field_collection_field_get_path($field);
$count = count(explode('/', $path));
$items[$path . '/%field_collection_item/repeats'] = array(
'title' => 'Repeats',
'page callback' => 'date_repeat_field_page',
'page arguments' => array($entity_type, $count),
'access callback' => 'date_repeat_field_show',
'access arguments' => array($entity_type, $count),
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
);
}
}
}
else {
$path = $entity_type . '/%' . $entity_type;
$items[$path . '/repeats'] = array(
'title' => 'Repeats',
'page callback' => 'date_repeat_field_page',
'page arguments' => array($entity_type, 1),
'access callback' => 'date_repeat_field_show',
'access arguments' => array($entity_type, 1),
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
);
}
}
return $items;
}
/**
* Implements hook_permission().
*/
function date_repeat_field_permission() {
return array('view date repeats' => array(
'title' => t('View Repeating Dates'),
'description' => t('Allow user to see a page with all the times a date repeats.'),
));
}
/**
* See if the user can access repeat date info for this field.
*/
function date_repeat_field_show($entity_type = 'node', $entity = NULL) {
$bundle = date_get_entity_bundle($entity_type, $entity);
foreach (field_info_fields() as $field_name => $field) {
if (in_array($field['type'], array('date', 'datestamp', 'datetime'))
&& array_key_exists($entity_type, $field['bundles'])
&& in_array($bundle, $field['bundles'][$entity_type])
&& date_is_repeat_field($field)) {
return user_access('view date repeats');
}
}
return FALSE;
}
/**
* A page to list all values for a repeating date.
*/
function date_repeat_field_page($entity_type = 'node', $entity = NULL) {
$bundle = date_get_entity_bundle($entity_type, $entity);
$info = entity_get_info($entity_type);
$key = $info['entity keys']['id'];
drupal_set_title(t('Repeats'));
$entity->date_repeat_show_all = TRUE;
$entity->content = array();
$output = '';
foreach (field_info_fields() as $field_name => $field) {
if (in_array($field['type'], array('date', 'datestamp', 'datetime')) && date_is_repeat_field($field)) {
foreach ($field['bundles'] as $field_entity_type => $bundles) {
foreach ($bundles as $field_bundle) {
if ($entity_type == $field_entity_type && $bundle == $field_bundle) {
$data = field_view_field($entity_type, $entity, $field_name);
$output .= drupal_render($data);
}
}
}
}
}
return $output;
}
/**
* Return an array of all entity types and bundles that have repeating date fields.
*/
function date_repeat_field_bundles() {
$values = array();
foreach (field_info_fields() as $field_name => $field) {
if (in_array($field['type'], array('date', 'datestamp', 'datetime')) && $field['settings']['repeat']) {
foreach ($field['bundles'] as $entity_type => $bundles) {
foreach ($bundles as $bundle) {
$values[$entity_type][$bundle][] = $field_name;
}
}
}
}
return $values;
}
function date_is_repeat_field($field, $instance = NULL) {
if (is_string($field)) {
$field = field_info_field($field);
}
if (!isset($field['settings']['repeat'])) {
return FALSE;
}
$value = $field['settings']['repeat'];
// This might be either a field form or a real field.
if (is_array($value)) {
return $value['#value'];
}
else {
return $value;
}
}
/*
* Implements hook_date_field_insert_alter().
*/
function date_repeat_field_date_field_insert_alter(&$items, $context) {
$entity = $context['entity'];
$field = $context['field'];
$instance = $context['instance'];
$langcode = $context['langcode'];
// If an RRULE with a frequency of NONE made it this far, unset it.
if (!empty($items[0]['rrule']) && strpos($items[0]['rrule'], 'FREQ=NONE')) {
$items[0]['rrule'] = NULL;
}
// We can't use hook_devel_generate() because we need custom handling for
// repeating date fields. So we wait until the entity is inserted, then
// intervene here to fix it.
if (!empty($entity->devel_generate) && !empty($field['settings']['repeat'])) {
module_load_include('inc', 'date_repeat_field', 'date_repeat_field.devel_generate');
date_repeat_field_date_field_insert($items, $context);
}
}
/*
* Implements hook_date_field_update_alter().
*/
function date_repeat_field_date_field_update_alter(&$items, $context) {
// If an RRULE with a frequency of NONE made it this far, unset it.
if (!empty($items[0]['rrule']) && strpos($items[0]['rrule'], 'FREQ=NONE')) {
$items[0]['rrule'] = NULL;
}
// If you have a repeating date field on a user and don't check the box to repeat it,
// we end up with $items[0]['rrule'] = array('additions' => '', 'exceptions' => ''));
// This will clean it up by getting rid of those bogus values.
// @TODO Figure out where that's coming from. It doesn't happen on nodes.
if (!empty($items[0]['rrule']) && is_array($items[0]['rrule'])) {
$items[0]['rrule'] = NULL;
}
}
/**
* Implements hook_field_widget_form_alter().
*/
function date_repeat_field_field_widget_form_alter(&$element, &$form_state, $context) {
$field = $context['field'];
$instance = $context['instance'];
$items = $context['items'];
$delta = $context['delta'];
if (in_array($field['type'], array('date', 'datetime', 'datestamp'))) {
if (!empty($field['settings']['repeat'])) {
$element['#element_validate'][] = 'date_repeat_field_widget_validate';
$element['show_repeat_settings'] = array(
'#type' => 'checkbox',
'#title' => t('Repeat'),
'#weight' => $instance['widget']['weight'] + .3,
'#prefix' => '<div class="date-clear">',
'#suffix' => '</div>',
'#default_value' => isset($items[$delta]['rrule']) && !empty($items[$delta]['rrule']) ? 1 : 0,
);
}
}
}
/**
* Validation for date repeat form element.
*
* Create multiple values from the RRULE results.
* Lots more work needed here.
*/
function date_repeat_field_widget_validate($element, &$form_state) {
$field = field_widget_field($element, $form_state);
if (empty($field['settings']['repeat'])) {
return;
}
$field_name = $element['#field_name'];
$delta = $element['#delta'];
$langcode = $element['#language'];
// If the widget has been hidden by #access, the RRULE will still be in its
// original string form here. Nothing to process.
if (date_hidden_element($element)) {
// If this was a hidden repeating date, we lost all the repeating values in the widget processing.
// Add them back here if that happened since we are skipping the re-creation of those values.
if (!empty($form_state['storage']['date_items'][$field_name])) {
array_pop($element['#parents']);
form_set_value($element, $form_state['storage']['date_items'][$field_name][$langcode], $form_state);
}
return;
}
module_load_include('inc', 'date_repeat', 'date_repeat_form');
$instance = field_widget_instance($element, $form_state);
// Here 'values' returns an array of input values, which includes the original RRULE, as a string.
// and 'input' returns an array of the form elements created by the repeating date widget, with
// RRULE values as an array of the selected elements and their chosen values.
$item = drupal_array_get_nested_value($form_state['values'], $element['#parents'], $input_exists);
$input = drupal_array_get_nested_value($form_state['input'], $element['#parents'], $input_exists);
$rrule_values = date_repeat_merge($input['rrule'], $element['rrule']);
// If no repeat information was set, treat this as a normal, non-repeating value.
if ($rrule_values['FREQ'] == 'NONE' || empty($input['show_repeat_settings'])) {
$item['rrule'] = NULL;
form_set_value($element, $item, $form_state);
return;
}
// If no start date was set, clean up the form and return.
if (empty($item['value'])) {
form_set_value($element, NULL, $form_state);
return;
}
// Require the UNTIL date for now.
// The RRULE has already been created by this point, so go back
// to the posted values to see if this was filled out.
$error_field_base = implode('][', $element['#parents']);
$error_field_until = $error_field_base . '][rrule][until_child][datetime][';
if (!empty($item['rrule']) && $rrule_values['range_of_repeat'] === 'UNTIL' && empty($rrule_values['UNTIL']['datetime'])) {
switch ($instance['widget']['type']) {
case 'date_text':
case 'date_popup':
form_set_error($error_field_until . 'date', t("Missing value in 'Range of repeat'. (UNTIL).", array(), array('context' => 'Date repeat')));
break;
case 'date_select':
form_set_error($error_field_until . 'year', t("Missing value in 'Range of repeat': Year (UNTIL)", array(), array('context' => 'Date repeat')));
form_set_error($error_field_until . 'month', t("Missing value in 'Range of repeat': Month (UNTIL)", array(), array('context' => 'Date repeat')));
form_set_error($error_field_until . 'day', t("Missing value in 'Range of repeat': Day (UNTIL)", array(), array('context' => 'Date repeat')));
break;
}
}
$error_field_count = $error_field_base . '][rrule][count_child';
if (!empty($item['rrule']) && $rrule_values['range_of_repeat'] === 'COUNT' && empty($rrule_values['COUNT'])) {
form_set_error($error_field_count, t("Missing value in 'Range of repeat'. (COUNT).", array(), array('context' => 'Date repeat')));
}
if (form_get_errors()) {
return;
}
// If the rule, the start date, or the end date have changed, re-calculate
// the repeating dates, wipe out the previous values, and populate the
// field with the new values.
// TODO
// Is it right to not do anything unless there are changes? Will that
// confuse anyone? Commenting that out for now...
$rrule = $item['rrule'];
if (!empty($rrule)
//&& ($rrule != $element['rrule']['#prev_rrule']
//|| $item['value'] != $element['rrule']['#prev_value']
//|| $item['value2'] != $element['rrule']['#prev_value2'])
) {
// Avoid undefined index problems on dates that don't have all parts.
$possible_items = array('value', 'value2', 'timezone', 'offset', 'offset2');
foreach ($possible_items as $key) {
if (empty($item[$key])) {
$item[$key] = '';
}
}
// We only collect a date for UNTIL, but we need it to be inclusive,
// so force it to a full datetime element at the last possible second of the day.
if (!empty($rrule_values['UNTIL'])) {
$rrule_values['UNTIL']['datetime'] .= ' 23:59:59';
$rrule_values['UNTIL']['granularity'] = serialize(drupal_map_assoc(array('year', 'month', 'day', 'hour', 'minute', 'second')));
$rrule_values['UNTIL']['all_day'] = 0;
}
$value = date_repeat_build_dates($rrule, $rrule_values, $field, $item);
// Unset the delta value of the parents.
array_pop($element['#parents']);
// Set the new delta values for this item to the array of values returned by the repeat rule.
form_set_value($element, $value, $form_state);
}
}
/**
* Implements the form after_build().
*
* Remove the 'Add more' elements from a repeating date form.
*/
function date_repeat_after_build(&$element, &$form_state) {
foreach ($form_state['storage']['repeat_fields'] as $field_name => $parents) {
// Remove unnecessary items in the form added by the Add more handling.
$value = drupal_array_get_nested_value($element, $parents);
$langcode = $value['#language'];
unset($value[$langcode]['add_more'], $value[$langcode]['#suffix'], $value[$langcode]['#prefix'], $value[$langcode][0]['_weight']);
$value[$langcode]['#cardinality'] = 1;
$value[$langcode]['#max_delta'] = 1;
drupal_array_set_nested_value($element, $parents, $value);
}
return $element;
}
/**
* Helper function to build repeating dates from a $node_field.
*
* Pass in either the RRULE or the $form_values array for the RRULE,
* whichever is missing will be created when needed.
*/
function date_repeat_build_dates($rrule = NULL, $rrule_values = NULL, $field, $item) {
include_once(DRUPAL_ROOT . '/' . drupal_get_path('module', 'date_api') . '/date_api_ical.inc');
$field_name = $field['field_name'];
if (empty($rrule)) {
$rrule = date_api_ical_build_rrule($rrule_values);
}
elseif (empty($rrule_values)) {
$rrule_values = date_ical_parse_rrule(NULL, $rrule);
}
// By the time we get here, the start and end dates have been
// adjusted back to UTC, but we want localtime dates to do
// things like '+1 Tuesday', so adjust back to localtime.
$timezone = date_get_timezone($field['settings']['tz_handling'], $item['timezone']);
$timezone_db = date_get_timezone_db($field['settings']['tz_handling']);
$start = new DateObject($item['value'], $timezone_db, date_type_format($field['type']));
$start->limitGranularity($field['settings']['granularity']);
if ($timezone != $timezone_db) {
date_timezone_set($start, timezone_open($timezone));
}
if (!empty($item['value2']) && $item['value2'] != $item['value']) {
$end = new DateObject($item['value2'], date_get_timezone_db($field['settings']['tz_handling']), date_type_format($field['type']));
$end->limitGranularity($field['settings']['granularity']);
date_timezone_set($end, timezone_open($timezone));
}
else {
$end = $start;
}
$duration = $start->difference($end);
$start_datetime = date_format($start, DATE_FORMAT_DATETIME);
if (!empty($rrule_values['UNTIL']['datetime'])) {
$end = date_ical_date($rrule_values['UNTIL'], $timezone);
$end_datetime = date_format($end, DATE_FORMAT_DATETIME);
}
elseif (!empty($rrule_values['COUNT'])) {
$end_datetime = NULL;
}
else {
// No UNTIL and no COUNT?
return array();
}
// Split the RRULE into RRULE, EXDATE, and RDATE parts.
$parts = date_repeat_split_rrule($rrule);
$parsed_exceptions = (array) $parts[1];
$exceptions = array();
foreach ($parsed_exceptions as $exception) {
$date = date_ical_date($exception, $timezone);
$exceptions[] = date_format($date, 'Y-m-d');
}
$parsed_rdates = (array) $parts[2];
$additions = array();
foreach ($parsed_rdates as $rdate) {
$date = date_ical_date($rdate, $timezone);
$additions[] = date_format($date, 'Y-m-d');
}
$dates = date_repeat_calc($rrule, $start_datetime, $end_datetime, $exceptions, $timezone, $additions);
$value = array();
foreach ($dates as $delta => $date) {
// date_repeat_calc always returns DATE_DATETIME dates, which is
// not necessarily $field['type'] dates.
// Convert returned dates back to db timezone before storing.
$date_start = new DateObject($date, $timezone, DATE_FORMAT_DATETIME);
$date_start->limitGranularity($field['settings']['granularity']);
date_timezone_set($date_start, timezone_open($timezone_db));
$date_end = clone($date_start);
date_modify($date_end, '+' . $duration . ' seconds');
$value[$delta] = array(
'value' => date_format($date_start, date_type_format($field['type'])),
'value2' => date_format($date_end, date_type_format($field['type'])),
'offset' => date_offset_get($date_start),
'offset2' => date_offset_get($date_end),
'timezone' => $timezone,
'rrule' => $rrule,
);
}
return $value;
}
/**
* Implements hook_date_combo_process_alter().
*
* This hook lets us make changes to the date_combo element.
*/
function date_repeat_field_date_combo_process_alter(&$element, &$form_state, $context) {
$field = $context['field'];
$instance = $context['instance'];
$field_name = $element['#field_name'];
$delta = $element['#delta'];
// Add a date repeat form element, if needed.
// We delayed until this point so we don't bother adding it to hidden fields.
if (date_is_repeat_field($field, $instance)) {
$item = $element['#value'];
$element['rrule'] = array(
'#type' => 'date_repeat_rrule',
'#theme_wrappers' => array('date_repeat_rrule'),
'#default_value' => isset($item['rrule']) ? $item['rrule'] : '',
'#date_timezone' => $element['#date_timezone'],
'#date_format' => date_limit_format(date_input_format($element, $field, $instance), $field['settings']['granularity']),
'#date_text_parts' => (array) $instance['widget']['settings']['text_parts'],
'#date_increment' => $instance['widget']['settings']['increment'],
'#date_year_range' => $instance['widget']['settings']['year_range'],
'#date_label_position' => $instance['widget']['settings']['label_position'],
'#prev_value' => isset($item['value']) ? $item['value'] : '',
'#prev_value2' => isset($item['value2']) ? $item['value2'] : '',
'#prev_rrule' => isset($item['rrule']) ? $item['rrule'] : '',
'#date_repeat_widget' => str_replace('_repeat', '', $instance['widget']['type']),
'#date_repeat_collapsed' => $instance['widget']['settings']['repeat_collapsed'],
'#date_flexible' => 0,
'#weight' => $instance['widget']['weight'] + .4,
);
}
}
/**
* Implements hook_date_combo_pre_validate_alter().
*
* This hook lets us alter the element or the form_state before the rest
* of the date_combo validation gets fired.
*/
function date_repeat_field_date_combo_pre_validate_alter(&$element, &$form_state, $context) {
// Just a placeholder for now.
}
/**
* Implements hook_field_info_alter().
*
* This Field API hook lets us add a new setting to the fields.
*/
function date_repeat_field_field_info_alter(&$info) {
$info['date']['settings'] += array(
'repeat' => 0,
);
$info['datetime']['settings'] += array(
'repeat' => 0,
);
$info['datestamp']['settings'] += array(
'repeat' => 0,
);
}
/**
* Implements hook_field_formatter_info_alter().
*
* This hook lets us add settings to the formatters.
*/
function date_repeat_field_field_formatter_info_alter(&$info) {
if (isset($info['date_default'])) {
$info['date_default']['settings'] += array(
'show_repeat_rule' => 'show',
);
}
}
/**
* Implements hook_field_widget_info_alter().
*
* This Field API hook lets us add a new setting to the widgets.
*/
function date_repeat_field_field_widget_info_alter(&$info) {
$info['date_text']['settings'] += array(
'repeat_collapsed' => 0,
);
$info['date_select']['settings'] += array(
'repeat_collapsed' => 0,
);
if (module_exists('date_popup')) {
$info['date_popup']['settings'] += array(
'repeat_collapsed' => 0,
);
}
}
/**
* Implements hook_date_field_settings_form_alter().
*
* This hook lets us alter the field settings form.
*/
function date_repeat_field_date_field_settings_form_alter(&$form, $context) {
$field = $context['field'];
$instance = $context['instance'];
$has_data = $context['has_data'];
$form['repeat'] = array(
'#type' => 'select',
'#title' => t('Repeating date'),
'#default_value' => $field['settings']['repeat'],
'#options' => array(0 => t('No'), 1 => t('Yes')),
'#attributes' => array('class' => array('container-inline')),
'#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."),
'#disabled' => $has_data,
);
}
/**
* Implements hook_form_FORM_ID_alter() for field_ui_field_edit_form().
*/
function date_repeat_field_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
$field = $form['#field'];
$instance = $form['#instance'];
if (!in_array($field['type'], array('date', 'datetime', 'datestamp'))) {
return;
}
// If using repeating dates, override the Field module's handling of the multiple values option.
if (date_is_repeat_field($field, $instance)) {
$form['field']['cardinality']['#disabled'] = TRUE;
$form['field']['cardinality']['#value'] = FIELD_CARDINALITY_UNLIMITED;
}
}
/**
* Implements hook_date_field_instance_settings_form_alter().
*
* This hook lets us alter the field instance settings form.
*/
function date_repeat_field_date_field_instance_settings_form_alter(&$form, $context) {
// Just a placeholder for now.
}
/**
* Implements hook_date_field_widget_settings_form_alter().
*
* This hook lets us alter the field widget settings form.
*/
function date_repeat_field_date_field_widget_settings_form_alter(&$form, $context) {
$field = $context['field'];
$instance = $context['instance'];
if (date_is_repeat_field($field, $instance)) {
$form['repeat_collapsed'] = array(
'#type' => 'value',
'#default_value' => 1,
'#options' => array(
0 => t('Expanded', array(), array('context' => 'Date repeat')),
1 => t('Collapsed', array(), array('context' => 'Date repeat'))
),
'#title' => t('Repeat display', array(), array('context' => 'Date repeat')),
'#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')),
'#fieldset' => 'date_format',
);
}
}
/**
* Implements hook_date_field_foramatter_settings_form_alter().
*
* This hook lets us alter the field formatter settings form.
*/
function date_repeat_field_date_field_formatter_settings_form_alter(&$form, &$form_state, $context) {
$field = $context['field'];
$instance = $context['instance'];
$view_mode = $context['view_mode'];
$display = $instance['display'][$view_mode];
$formatter = $display['type'];
$settings = $display['settings'];
if ($formatter == 'date_default') {
$form['show_repeat_rule'] = array(
'#title' => t('Repeat rule:'),
'#type' => 'select',
'#options' => array(
'show' => t('Show repeat rule'),
'hide' => t('Hide repeat rule')),
'#default_value' => $settings['show_repeat_rule'],
'#access' => $field['settings']['repeat'],
'#weight' => 5,
);
}
}
/**
* Implements hook_date_field_foramatter_settings_summary_alter().
*
* This hook lets us alter the field formatter settings summary.
*/
function date_repeat_field_date_field_formatter_settings_summary_alter(&$summary, $context) {
$field = $context['field'];
$instance = $context['instance'];
$view_mode = $context['view_mode'];
$display = $instance['display'][$view_mode];
$formatter = $display['type'];
$settings = $display['settings'];
if (isset($settings['show_repeat_rule']) && !empty($field['settings']['repeat'])) {
if ($settings['show_repeat_rule'] == 'show') {
$summary[] = t('Show repeat rule');
}
else {
$summary[] = t('Hide repeat rule');
}
}
}

View File

@@ -0,0 +1,182 @@
<?php
/**
* @file
* A form to change the type of date used in date fields.
*/
/**
* Form constructor for the date type change form.
*
* @see date_tools_change_type_form_validate()
* @see date_tools_change_type_form_submit()
*/
function date_tools_change_type_form() {
$form = array();
// This is broken, still needs to be adjusted for the D6->D7 changes.
drupal_set_message(t('This operation does not yet work for the Drupal 7 version.'), 'error');
return $form;
$fields = content_fields();
$date_options = array();
$type_options = array();
$labels = array();
foreach (date_field_info() as $type => $info) {
$type_options[$type] = $info['label'] . ': ' . $info['description'];
$labels[$type] = $info['label'];
}
// Get the available date fields.
foreach ($fields as $field_name => $field) {
if ($field['type'] == 'date' || $field['type'] == 'datestamp' || $field['type'] == 'datetime') {
$date_options[$labels[$field['type']]][$field_name] = t('Field @label (@field_name)', array('@label' => $field['widget']['label'], '@field_name' => $field_name, '@type' => $labels[$field['type']]));
}
}
if (sizeof($date_options) < 1) {
drupal_set_message(t('There are no date fields in this database.'));
return $form;
}
$form['date_field'] = array(
'#type' => 'select',
'#options' => $date_options,
'#title' => t('Date field'),
'#default_value' => '',
'#description' => t('The date field which whose type should be changed.'),
);
$form['type'] = array(
'#type' => 'radios',
'#options' => $type_options,
'#default_value' => '',
'#required' => TRUE,
'#description' => t('The type of date to change the field to.'),
'#prefix' => '<strong>' . t('New type:') . '</strong>',
);
$form['submit'] = array('#type' => 'submit', '#value' => t('Change'));
return $form;
}
/**
* Form validation handler for date_tools_change_type_form().
*
* @see date_tools_change_type_form_submit()
*/
function date_tools_change_type_form_validate($form, &$form_state) {
$field_name = $form_state['values']['date_field'];
$new_type = $form_state['values']['type'];
$field = content_fields($field_name);
$old_type = $field['type'];
if ($new_type == $old_type) {
form_set_error('type', t('The current type is the same as the chosen type. There is nothing to change.'));
}
}
/**
* Form submission handler for date_tools_change_type_form().
*
* @see date_tools_change_type_form_validate()
*/
function date_tools_change_type_form_submit($form, &$form_state) {
$field_name = $form_state['values']['date_field'];
$new_type = $form_state['values']['type'];
$field = content_fields($field_name);
$old_type = $field['type'];
if ($new_type == $old_type) {
return;
}
$db_info = content_database_info($field);
$table = $db_info['table'];
$columns = $db_info['columns'];
$labels = array();
foreach (date_field_info() as $type => $info) {
$labels[$type] = $info['label'];
}
// Is there any data in this field? If not, we can
// skip some steps.
$has_data = db_query("SELECT COUNT(*) FROM {" . $table . "}")->fetchField();
// Create a backup copy of the original values.
// The values are going to get corrupted when we
// change the column type.
if ($has_data) {
$temp_table = $table . '_temp';
db_query("CREATE TABLE {" . $temp_table . "} SELECT * FROM {" . $table . "}");
}
// Change the field definition to the new type.
$field['type'] = $new_type;
require_once './' . drupal_get_path('module', 'content') . '/includes/content.crud.inc';
content_field_instance_update($field, FALSE);
content_clear_type_cache();
// If there's no data to update, we're finished.
if (!$has_data) {
drupal_set_message(t('The field @field_name has been changed from @old_type to @new_type.', array(
'@field_name' => $field['widget']['label'], '@old_type' => $labels[$old_type], '@new_type' => $labels[$new_type])));
return;
}
// Replace old values with modified values, massaging the original values as
// necessary for the new type.
require_once './' . drupal_get_path('module', 'date_api') . '/date_api_sql.inc';
$date_handler = new date_sql_handler();
$date_handler->granularity = $field['granularity'];
$date_handler->date_type = $old_type;
$new_columns = array();
$old_columns = array('nid', 'vid');
$new_columns[] = $temp_table . '.nid AS nid';
$new_columns[] = $temp_table . '.vid AS vid';
if ($field->multiple) {
$new_columns[] = $temp_table . '.delta AS delta';
$old_columns[] = 'delta';
}
foreach ($columns as $column => $info) {
if ($column != 'value' && $column != 'value2') {
continue;
}
$old_columns[] = $info['column'];
$db_field = $date_handler->sql_field($temp_table . '.' . $info['column'], 0);
switch ($old_type) {
case 'date':
switch ($new_type) {
case 'datestamp':
$new_columns[] = $date_handler->sql_format('U', $db_field) . ' AS ' . $info['column'];
break;
case 'datetime':
$new_columns[] = $date_handler->sql_format('Y-m-d H:i:s', $db_field) . ' AS ' . $info['column'];
break;
}
break;
case 'datestamp':
switch ($new_type) {
case 'date':
$new_columns[] = $date_handler->sql_format('Y-m-d/TH:i:s', $db_field) . ' AS ' . $info['column'];
break;
case 'datetime':
$new_columns[] = $date_handler->sql_format('Y-m-d H:i:s', $db_field) . ' AS ' . $info['column'];
break;
}
break;
case 'datetime':
switch ($new_type) {
case 'date':
$new_columns[] = $date_handler->sql_format('Y-m-d/TH:i:s', $db_field) . ' AS ' . $info['column'];
break;
case 'datestamp':
$new_columns[] = $date_handler->sql_format('U', $db_field) . ' AS ' . $info['column'];
break;
}
break;
}
}
// Make sure database timezone is set to UTC.
$date_handler->set_db_timezone();
// Make the replacement.
$sql = 'REPLACE INTO {' . $table . '} (' . implode(', ', $old_columns) . ') ' . ' SELECT ' . implode(', ', $new_columns) . ' FROM {' . $temp_table . '}';
db_query($sql);
db_query("DROP TABLE {" . $temp_table . "}");
drupal_set_message(t('The field @field_name has been changed from @old_type to @new_type.', array('@field_name' => $field['widget']['label'], '@old_type' => $labels[$old_type], '@new_type' => $labels[$new_type])));
}

View File

@@ -0,0 +1,14 @@
name = Date Tools
description = Tools to import and auto-create dates and calendars.
dependencies[] = date
package = Date/Time
core = 7.x
configure = admin/config/date/tools
files[] = tests/date_tools.test
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,96 @@
<?php
/**
* @file
* @todo.
*/
/**
* Implements hook_help().
*/
function date_tools_help($section, $arg) {
switch ($section) {
case 'admin/config/date/tools':
return '<p>' . t('<h2>Tools for Dates and Calendars</h2>') . '</p>';
case 'admin/config/date/tools/change':
return '<p>' . t('Change a date field from one type to another. Very experimental, use at your own risk!') . '</p>';
case 'admin/config/date/tools/date_wizard':
$output = t("Fill out the following form to auto-create a date content type, with a datetime field and matching pre-configured calendar. If the calendar module is enabled and the option to create a calendar is chosen, a calendar and upcoming events block will be created, an ical feed will be added to the calendar. Nodes created from this new content type will include a link to the calendar, and the calendar will have a link to the 'add new date' form. You can also add new date fields to an existing content type by entering the existing content type name instead of creating a new one.") .
'</p><p>' .
t('Only a limited set of options are displayed here to make this easy to set up. Once the date has been created you will be able to make other changes to the date settings and add other fields to your new content type on the Manage fields screen. You can also make changes to the calendar on the Views edit page.') .
'</p>';
return $output;
}
}
/**
* Implements hook_permission().
*/
function date_tools_permission() {
return array(
'administer date tools' => array(
'title' => t('Administer date tools'),
),
);
}
/**
* Implements hook_menu().
*/
function date_tools_menu() {
$items = array();
$items['admin/config/date/tools'] = array(
'title' => 'Date Tools',
'description' => 'Date Wizard and other tools to manage and create dates and calendars. ',
'access arguments' => array('administer date tools'),
'page callback' => 'date_tools_page',
);
$items['admin/config/date/tools/about'] = array(
'title' => 'About',
'description' => 'Date Wizard and other tools to manage and create dates and calendars. ',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -5,
'page callback' => 'date_tools_page',
'access arguments' => array('administer date tools'),
);
$items['admin/config/date/tools/date_wizard'] = array(
'title' => 'Date wizard',
'description' => 'Easy creation of date content types and calendars. ',
'type' => MENU_LOCAL_TASK,
'weight' => 1,
'page callback' => 'drupal_get_form',
'page arguments' => array('date_tools_wizard_form'),
'access arguments' => array('administer date tools'),
'file' => 'date_tools.wizard.inc',
);
/**
$items['admin/config/date/tools/change'] = array(
'title' => 'Change type',
'access arguments' => array('administer date tools'),
'page callback' => 'drupal_get_form',
'page arguments' => array('date_tools_change_type_form'),
'type' => MENU_LOCAL_TASK,
'weight' => 3,
'file' => 'date_tools.change_type.inc',
);
*/
return $items;
}
/**
* Main Date Tools page
*/
function date_tools_page() {
$content = '';
$content .= t('Dates and calendars can be complicated to set up. The !date_wizard makes it easy to create a simple date content type and related calendar. ', array('!date_wizard' => l(t('Date wizard'), 'admin/config/date/tools/date_wizard')));
return $content;
}

View File

@@ -0,0 +1,392 @@
<?php
/**
* @file
* The Date Wizard code.
*/
/**
* @todo.
*/
function date_tools_wizard_form() {
$form = array();
$form['type'] = array(
'#type' => 'fieldset',
'#title' => t('Content type'),
);
$form['type']['bundle'] = array(
'#type' => 'textfield',
'#default_value' => 'date',
'#title' => t('Content type name'),
'#description' => t('Machine-readable name. Allowed values: (a-z, 0-9, _). If this is not an existing content type, the content type will be created.'),
);
$form['type']['name'] = array(
'#type' => 'textfield',
'#default_value' => t('Date'),
'#title' => t('Content type label'),
'#description' => t('The human-readable name for this content type. Only needed when creating a new content type.'),
);
$form['type']['type_description'] = array(
'#type' => 'textarea',
'#default_value' => t('A date content type that is linked to a Views calendar.'),
'#title' => t('Content type description'),
'#description' => t('A description for the content type. Only needed when creating a new content type.'),
);
$form['field'] = array(
'#type' => 'fieldset',
'#title' => t('Date field'),
);
$form['field']['field_name'] = array(
'#type' => 'textfield',
'#default_value' => 'date',
'#field_prefix' => 'field_',
'#title' => t('Date field name'),
'#description' => t('Machine-readable name. Allowed values: (a-z, 0-9, _) Must not be an existing field name.'),
);
$form['field']['label'] = array(
'#tree' => TRUE,
'#type' => 'textfield',
'#default_value' => t('Date'),
'#title' => t('Date field label'),
'#description' => t('The human-readable label for this field.'),
);
$form['field']['widget_type'] = array(
'#type' => 'select',
'#options' => date_tools_wizard_widget_types(),
'#default_value' => 'date_select',
'#title' => t('Date widget type'),
);
$form['field']['repeat'] = array(
'#type' => 'select',
'#default_value' => 0,
'#options' => array(0 => t('No'), 1 => t('Yes')),
'#title' => t('Show repeating date options'),
'#access' => module_exists('date_repeat_field'),
);
$form['field']['advanced'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#title' => t('Advanced options'),
);
$form['field']['advanced']['todate'] = array(
'#type' => 'select',
'#default_value' => 'optional',
'#options' => array('' => t('Never'), 'optional' => t('Optional'), 'required' => t('Required')),
'#title' => t('End Date'),
'#description' => t("Display a matching second date field as a 'End date'."),
);
$form['field']['advanced']['field_type'] = array(
'#type' => 'select',
'#options' => date_tools_wizard_field_types(),
'#default_value' => 'datetime',
'#title' => t('Date field type'),
'#description' => t("The recommend type is Datetime, except for historical dates or dates with only year or month granularity. Older or incomplete dates should use the Date type (an ISO date)."),
);
$form['field']['advanced']['granularity'] = array(
'#type' => 'select',
'#options' => date_granularity_names(),
'#default_value' => array('month', 'day', 'year', 'hour', 'minute'),
'#title' => t('Granularity'),
'#multiple' => TRUE,
);
$form['field']['advanced']['year_range'] = array(
'#type' => 'textfield',
'#default_value' => '-1:+1',
'#title' => t('Year range'),
'#description' => t("Range of allowed years, oldest to newest. '-1:+1 means oldest date is one year back, newest is one year forward from current year."),
);
$form['field']['advanced']['tz_handling'] = array(
'#type' => 'select',
'#options' => date_tools_wizard_tz_handling(),
'#default_value' => 'site',
'#title' => t('Date timezone handling'),
'#description' => t("Timezone handling should be set to 'none' for granularity without time elements."),
);
$form['calendar'] = array(
'#type' => 'select',
'#default_value' => module_exists('calendar'),
'#options' => array(0 => t('No'), 1 => t('Yes')),
'#title' => t('Create a calendar for this date field'),
'#access' => module_exists('calendar'),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
/**
* @todo.
*/
function date_tools_wizard_form_validate(&$form, &$form_state) {
$bundle = $form_state['values']['bundle'];
$field_name = 'field_' . $form_state['values']['field_name'];
$existing_type = db_query("SELECT type FROM {node_type} WHERE type=:bundle", array(':bundle' => $bundle))->fetchField();
$existing_instance = db_query("SELECT field_name FROM {field_config_instance} WHERE field_name=:field_name AND bundle=:bundle AND entity_type=:entity_type", array(':field_name' => $field_name, ':bundle' => $bundle, ':entity_type' => 'node'))->fetchField();
if ($existing_type) {
drupal_set_message(t('This content type name already exists, adding new field to existing content type.'));
}
if (!preg_match('!^[a-z0-9_]+$!', $bundle)) {
form_set_error('bundle', t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
}
if (!empty($form_state['values']['calendar']) && !empty($form_state['values']['blocks']) && strlen($bundle) > 12) {
form_set_error('bundle', t('The content type name must be no more than 12 characters long when using it to create a calendar and blocks.'));
}
if ($existing_instance) {
form_set_error('field_name', t('This field name already exists.'));
}
if (strlen($field_name) > 32) {
form_set_error('field_name', t('The field name must be no more than 26 characters long.'));
}
if (!date_has_time($form_state['values']['granularity']) && $form_state['values']['tz_handling'] != 'none') {
form_set_error('tz_handling', t('Timezone handling must be none for granularity without time.'));
}
}
/**
* @todo.
*/
function date_tools_wizard_form_submit(&$form, &$form_state) {
$view_name = date_tools_wizard_build($form_state['values']);
menu_rebuild();
if (!empty($form_state['values']['calendar']) && !empty($view_name)) {
$form_state['redirect'] = 'admin/structure/views/template/' . $view_name . '/add';
}
else {
$form_state['redirect'] = 'admin/structure/types/manage/' . str_replace('_', '-', $form_state['values']['bundle']) . '/fields';
}
}
/**
* @todo.
*/
function date_tools_wizard_build($form_values) {
extract($form_values);
$field_name = 'field_' . $field_name;
$base_table = 'node';
// Create a node type if it doesn't already exist.
// This makes it possible to add additional date fields to
// an existing type.
$types = node_type_get_names();
$type_settings = array();
if (!array_key_exists($bundle, $types)) {
date_tools_wizard_create_content_type($name, $bundle, $type_description, $type_settings);
drupal_set_message(t('Your content type @name has been created.', array('@name' => $name)));
}
else {
$types = node_type_get_types();
$type = $types[$bundle];
if (!empty($type_settings)) {
foreach ($type_settings as $key => $setting) {
$type->$key = $setting;
}
node_type_save($type);
}
$name = $type->name;
}
$field = array(
'field_name' => $field_name,
'type' => $field_type,
'cardinality' => $repeat ? FIELD_CARDINALITY_UNLIMITED : 1,
'settings' => array(
'granularity' => $granularity,
'tz_handling' => $tz_handling,
'timezone_db' => date_get_timezone_db($tz_handling),
'repeat' => $repeat,
'todate' => !empty($todate) ? $todate : 'optional',
),
);
$instance = array(
'entity_type' => 'node',
'field_name' => $field_name,
'label' => $label,
'bundle' => $bundle,
// Move the date right below the title.
'weight' => -4,
'widget' => array(
'type' => $widget_type,
// Increment for minutes and seconds, can be 1, 5, 10, 15, or 30.
'settings' => array(
'increment' => 15,
// The number of years to go back and forward in drop-down year
// selectors.
'year_range' => !empty($year_range) ? $year_range : '-0:+1',
'input_format' => date_default_format($widget_type),
'text_parts' => array(),
'label_position' => 'above',
'repeat_collapsed' => 0,
),
'weight' => -4,
),
'settings' => array(
'default_value' => 'now',
'default_value2' => 'blank',
),
);
$instance['display'] = array(
'default' => array(
'label' => 'above',
'type' => 'date_default',
'settings' => array(
'format_type' => 'long',
'show_repeat_rule' => 'show',
'multiple_number' => '',
'multiple_from' => '',
'multiple_to' => '',
'fromto' => 'both',
),
'module' => 'date',
'weight' => 0 ,
),
'teaser' => array(
'label' => 'above',
'type' => 'date_default',
'weight' => 0,
'settings' => array(
'format_type' => 'long',
'show_repeat_rule' => 'show',
'multiple_number' => '',
'multiple_from' => '',
'multiple_to' => '',
'fromto' => 'both',
),
'module' => 'date',
),
);
$field = field_create_field($field);
$instance = field_create_instance($instance);
$view_name = 'calendar_node_' . $field_name;
field_info_cache_clear(TRUE);
field_cache_clear(TRUE);
drupal_set_message(t('Your date field @name has been created.', array('@name' => $label)));
return $view_name;
}
/**
* @todo.
*/
function date_tools_wizard_include() {
module_load_include('inc', 'node', 'content_types');
module_load_include('inc', 'node', 'node.pages');
module_load_include('inc', 'field', 'field.crud');
module_load_include('inc', 'date', 'date_admin');
}
/**
* @todo.
*/
function date_tools_wizard_field_types() {
$field_types = array();
foreach (date_field_info() as $name => $info) {
$field_types[$name] = $info['label'];
}
return $field_types;
}
/**
* @todo.
*/
function date_tools_wizard_widget_types() {
$widget_types = array();
foreach (date_field_widget_info() as $name => $info) {
if (!strstr($name, '_repeat')) {
$widget_types[$name] = $info['label'];
}
}
return $widget_types;
}
/**
* @todo.
*/
function date_tools_wizard_tz_handling() {
include_once drupal_get_path('module', 'date') . '/date_admin.inc';
return date_timezone_handling_options();
}
/**
* @todo.
*/
function date_tools_wizard_create_content_type($name, $bundle, $description, $type_settings = array()) {
date_tools_wizard_include();
// Create the content type.
$values = array(
'name' => $name,
'type' => $bundle,
'description' => $description,
'title_label' => 'Title',
'body_label' => 'Body',
'min_word_count' => '0',
'help' => '',
'node_options' =>
array(
'status' => 1,
'promote' => 1,
'sticky' => 0,
'revision' => 0,
),
'language_content_type' => '0',
'old_type' => $bundle,
'orig_type' => '',
'base' => 'node_content',
'custom' => '1',
'modified' => '1',
'locked' => '0',
'url_str' => str_replace('_', '-', $bundle),
);
// Allow overrides of these values.
foreach ($type_settings as $key => $value) {
$values[$key] = $value;
}
node_type_save((object) $values);
// Add the body field.
$trim_length = variable_get('teaser_length', 600);
$field = field_info_field('body');
$instance = array(
'field_name' => 'body',
'entity_type' => 'node',
'bundle' => $bundle,
'label' => t('Description'),
'widget' => array(
'type' => 'text_textarea_with_summary',
'settings' => array(
'rows' => 20,
'summary_rows' => 5,
),
'weight' => -4,
'module' => 'text',
),
'settings' => array('display_summary' => TRUE),
'display' => array(
'default' => array(
'label' => 'hidden',
'type' => 'text_default',
),
'teaser' => array(
'label' => 'hidden',
'type' => 'text_summary_or_trimmed',
'trim_length' => $trim_length,
),
),
);
field_create_instance($instance);
}

View File

@@ -0,0 +1,105 @@
<?php
/**
* @file
* Tests for Date Tools.
*/
class DateToolsTestCase extends DrupalWebTestCase {
protected $privileged_user;
/**
* @todo.
*/
public static function getInfo() {
return array(
'name' => 'Date Tools',
'description' => 'Test Date Wizard and other Date Tools.',
'group' => 'Date',
);
}
/**
* @todo.
*/
public function setUp() {
// Load the date_api module.
parent::setUp('field', 'field_ui', 'date_api', 'date', 'date_popup', 'date_tools');
// Create and log in our privileged user.
$this->privileged_user = $this->drupalCreateUser(
array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools')
);
$this->drupalLogin($this->privileged_user);
variable_set('date_format_long', 'D, m/d/Y - H:i');
}
/**
* Creates a date field using the Date Wizard.
*/
public function testTools() {
$form_values = array('label' => 'Test', 'field_type' => 'datetime', 'widget_type' => 'date_select', 'todate' => '');
$this->createDateWizard($form_values);
$this->dateForm($options = 'select');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for the Date Wizard datetime field using the date_select widget.');
$this->deleteDateField();
}
/**
* Tests that date field functions properly.
*/
function dateForm($options) {
$edit = array();
$edit['title'] = $this->randomName(8);
$edit['body[und][0][value]'] = $this->randomName(16);
if ($options == 'select') {
$edit['field_test[und][0][value][year]'] = '2010';
$edit['field_test[und][0][value][month]'] = '10';
$edit['field_test[und][0][value][day]'] = '7';
$edit['field_test[und][0][value][hour]'] = '10';
$edit['field_test[und][0][value][minute]'] = '30';
}
elseif ($options == 'text') {
$edit['field_test[und][0][value][date]'] = '10/07/2010 - 10:30';
}
elseif ($options == 'popup') {
$edit['field_test[und][0][value][date]'] = '10/07/2010';
$edit['field_test[und][0][value][time]'] = '10:30';
}
$this->drupalPost('node/add/story', $edit, t('Save'));
$this->assertText($edit['body[und][0][value]'], 'Test node has been created');
}
/**
* @todo.
*/
function deleteDateField() {
$this->drupalGet('admin/structure/types/manage/story/fields');
$this->clickLink('delete');
$this->drupalPost(NULL, NULL, t('Delete'));
$this->assertText('The field Test has been deleted from the Story content type.', 'Removed date field.');
}
/**
* Creates a date field using the Date Wizard.
*/
function createDateWizard($edit) {
$edit += array(
'bundle' => 'story',
'name' => 'Story',
'type_description' => 'A test content type.',
'field_name' => 'test',
'label' => 'Test',
'widget_type' => 'date_select',
'todate' => 'optional',
'field_type' => 'date',
'granularity[]' => array('year', 'month', 'day', 'hour', 'minute'),
'tz_handling' => 'site',
'year_range' => '2010:+2',
);
$this->drupalPost('admin/config/date/tools/date_wizard', $edit, t('Save'));
$this->assertText('Your date field Test has been created.', 'Create a date field using the Date Wizard.');
}
}

View File

@@ -0,0 +1,142 @@
/* Pager plugin css */
div.date-views-pager {
margin-left: auto;
margin-right: auto;
}
div.date-views-pager li {
float: left;
text-align: left;
width: 20%;
}
div.date-views-pager div.current-page {
font-weight: bold;
text-align: center;
}
div.date-views-pager div.next-month,
div.date-views-pager div.next-year {
text-align: right;
}
/* Views filter form css */
.views-group-box div.date-views-filter-fieldset {
margin-left: 0;
}
.views-group-box fieldset.date-views-filter-fieldset {
padding-top: 0;
}
.views-group-box fieldset.date-views-filter-fieldset .container-inline-date > .form-item {
border-width: 0;
margin: 9px 3px 0 3px;
display: block;
}
.views-group-box fieldset.date-views-filter-fieldset .container-inline-date .date-padding {
padding: 0;
}
.date-views-filter-wrapper {
min-width: 250px;
}
.date-views-filter input {
float: left !important; /* LTR */
margin-right: 2px !important; /* LTR */
min-width: 12em;
padding: 0 !important;
width: 12em;
}
/**
* Style Header
*/
/* Give the navigation bar a little extra padding below so it will clear the new contextual links overlay of the teasers below it. */
.date-nav {
clear: both;
padding-bottom: 1.5em;
width: 100%;
}
.date-nav div.date-heading h3 {
margin: 0;
padding: 0;
}
.date-nav-wrapper .clear-block {
margin-bottom: 10px;
}
.date-nav-wrapper {
position: relative;
margin-top: 5px;
width: 100%;
}
.date-nav-wrapper .date-nav {
background-color: transparent;
border: 0px;
height: 30px;
height: auto;
min-height: 30px;
position: relative;
margin-bottom: 10px;
}
.date-nav-wrapper .date-prev a,
.date-nav-wrapper .date-next a {
text-decoration: none;
color: inherit;
font-size: 12px;
}
.date-nav-wrapper.date-nav a:hover {
text-decoration: underline;
}
.date-nav-wrapper .date-prev {
padding: 5px 0;
position: absolute;
left: 0px;
text-align: left;
top: 0px;
width: auto;
z-index: 1;
font-size: 12px;
}
.date-nav-wrapper .date-prev {
left: 0;
right: auto;
}
.date-nav-wrapper .date-prev a {
margin-left: 10px;
font-weight: bold;
}
.date-nav-wrapper .date-heading {
position: relative;
width: 100%;
top: 0px;
text-align: center;
z-index: 0;
}
.date-nav-wrapper .date-heading h3 {
line-height: 30px;
font-size: 1.7em;
}
.date-nav-wrapper .date-next {
padding: 5px 0;
position: absolute;
right: 0px;
text-align: right;
top: 0px;
width: auto;
z-index: 1;
font-size: 12px;
}
.date-nav-wrapper .date-next a {
margin-right: 10px;
font-weight: bold;
}

View File

@@ -0,0 +1,21 @@
name = Date Views
description = Views integration for date fields and date functionality.
package = Date/Time
dependencies[] = date_api
dependencies[] = views
core = 7.x
php = 5.2
files[] = includes/date_views_argument_handler.inc
files[] = includes/date_views_argument_handler_simple.inc
files[] = includes/date_views_filter_handler.inc
files[] = includes/date_views_filter_handler_simple.inc
files[] = includes/date_views.views_default.inc
files[] = includes/date_views.views.inc
files[] = includes/date_views_plugin_pager.inc
; Information added by drupal.org packaging script on 2012-08-13
version = "7.x-2.6"
core = "7.x"
project = "date"
datestamp = "1344850024"

View File

@@ -0,0 +1,429 @@
<?php
/**
* Implements hook_views_api().
*
* This one is used as the base to reduce errors when updating.
*/
function date_views_theme() {
$path = drupal_get_path('module', 'date_views');
$base = array(
'file' => 'theme.inc',
'path' => "$path/theme",
);
return array(
'date_nav_title' => $base + array('variables' => array('granularity' => NULL, 'view' => NULL, 'link' => NULL, 'format' => NULL)),
'date_views_filter_form' => $base + array('template' => 'date-views-filter-form', 'render element' => 'form'),
'date_calendar_day' => $base + array('variables' => array('date' => NULL)),
'date_views_pager' => $base + array(
'variables' => array('plugin' => NULL, 'input' => NULL),
// Register a pattern so that it can work like all views templates.
'pattern' => 'date_views_pager__',
'template' => 'date-views-pager',
),
);
}
function date_views_views_api() {
return array(
'api' => 3,
'path' => drupal_get_path('module', 'date_views') . '/includes',
);
}
/**
* Wrapper function to make sure this function will always work.
*/
function date_views_views_fetch_fields($base, $type) {
if (!module_exists('views')) {
return array();
}
module_load_include('inc', 'views', 'includes/admin');
return views_fetch_fields($base, $type);
}
/**
* Identify all potential date/timestamp fields and cache the data.
*/
function date_views_fields($base = 'node', $reset = FALSE) {
static $fields = array();
$empty = array('name' => array(), 'alias' => array());
module_load_include('inc', 'date_views', 'includes/date_views_fields');
if (empty($fields[$base]) || $reset) {
$cid = 'date_views_fields_' . $base;
if (!$reset && $cached = cache_get($cid, 'cache_views')) {
$fields[$base] = $cached->data;
}
else {
$fields[$base] = _date_views_fields($base);
}
}
// Make sure that empty values will be arrays in he expected format.
return !empty($fields) && !empty($fields[$base]) ? $fields[$base] : $empty;
}
/**
* Implements hook_date_views_entities().
* Map extra Views tables to the entity that holds its date fields,
* needed for Views tables other than the primary tables identified in entity_info().
*/
function date_views_date_views_extra_tables() {
return array(
'node_revision' => 'node',
);
}
/**
* Helper function to map entity types to the Views base table they use,
* to make it easier to infer the entity type from a base table.
*
* Views has a new handler called views_handler_field_entity() that loads
* entities, and you can use something like the following to get the
* entity type from a view, but not all our base tables contain the
* entity information we need, (i.e. revisions) so it won't work here
* and we resort to creating information from entity_get_info().
*
* // A method to get the entity type for a base table.
* $table_data = views_fetch_data($base_table);
* if (!isset($table_data['table']['base']['entity type'])) {
* return FALSE;
* }
* $entity_type = $table_data['table']['base']['entity type'];
*/
function date_views_base_tables() {
$base_tables = &drupal_static(__FILE__, array());
if (empty($base_tables)) {
// First we get the base tables we can learn about from entity_info.
$entity_info = entity_get_info();
foreach ($entity_info as $entity_type => $info) {
if (!empty($info['base table'])) {
$base_tables[$info['base table']] = $entity_type;
}
if (!empty($info['revision table'])) {
$base_tables[$info['revision table']] = $entity_type;
}
}
// Then we let other modules tell us about other entity tables that hold date fields.
$base_tables += module_invoke_all('date_views_extra_tables');
}
return $base_tables;
}
/**
* Implements hook_date_views_fields().
*
* All modules that create custom fields that use the
* 'views_handler_field_date' handler can provide
* additional information here about the type of
* date they create so the date can be used by
* the Date API views date argument and date filter.
*/
function date_views_date_views_fields($field) {
$values = array(
// The type of date: DATE_UNIX, DATE_ISO, DATE_DATETIME.
'sql_type' => DATE_UNIX,
// Timezone handling options: 'none', 'site', 'date', 'utc' .
'tz_handling' => 'site',
// Needed only for dates that use 'date' tz_handling.
'timezone_field' => '',
// Needed only for dates that use 'date' tz_handling.
'offset_field' => '',
// Array of "table.field" values for related fields that should be
// loaded automatically in the Views SQL.
'related_fields' => array(),
// Granularity of this date field's db data.
'granularity' => array('year', 'month', 'day', 'hour', 'minute', 'second'),
);
switch ($field) {
case 'users.created':
case 'users.access':
case 'users.login':
case 'node.created':
case 'node.changed':
case 'node_revision.timestamp':
case 'file_managed.timestamp':
case 'comment.timestamp':
return $values;
}
}
/**
* A version of date_real_url that formats links correctly for the new Date pager.
*/
function date_pager_url($view, $date_type = NULL, $date_arg = NULL, $force_view_url = FALSE, $absolute = TRUE) {
// If someone adds a pager without a matching argument, there is not date information to work with.
if (empty($view->date_info) || !isset($view->date_info->date_arg_pos)) {
return '';
}
$args = $view->args;
$pos = $view->date_info->date_arg_pos;
// The View arguments array is indexed numerically but is not necessarily
// in numerical order. Sort the arguments to ensure the correct order.
ksort($args);
// If there are empty arguments before the date argument,
// pad them with the wildcard so the date argument will be in
// the right position.
if (count($args) < $pos) {
foreach ($view->argument as $name => $argument) {
if ($argument->position == $pos) {
break;
}
$args[] = $argument->options['exception']['value'];
}
}
if (!empty($date_type)) {
switch ($date_type) {
case 'year':
$args[$pos] = date_pad($view->date_info->year, 4);
break;
case 'week':
$args[$pos] = date_pad($view->date_info->year, 4) . '-W' . date_pad($view->date_info->week);
break;
case 'day':
$args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month) . '-' . date_pad($view->date_info->day);
break;
default:
$args[$pos] = date_pad($view->date_info->year, 4) . '-' . date_pad($view->date_info->month);
break;
}
}
elseif (!empty($date_arg)) {
$args[$pos] = $date_arg;
}
else {
$args = $view->args;
}
// Is this an embedded or a block view?
// Return the pager query value.
if (!$force_view_url &&
(!empty($view->preview) || !empty($view->date_info->block_identifier))) {
$url = $args[$pos];
$key = date_block_identifier($view);
if (!empty($key)) {
return url($_GET['q'], array(
'query' => date_views_querystring($view, array($key => $url)),
'absolute' => $absolute));
}
}
// Normal views may need querystrings appended to them
// if they use exposed filters.
return url($view->get_url($args), array(
'query' => date_views_querystring($view),
'absolute' => $absolute));
}
function date_block_identifier($view) {
if (!empty($view->block_identifier)) {
return $view->block_identifier;
}
return isset($view->date_info->block_identifier) ? $view->date_info->block_identifier : NULL;
}
/**
* Implements hook_field_views_data_alter().
*
* Create a Views field for each date column we care about
* to supplement the generic 'entity_id' and 'revision_id'
* fields that are automatically created.
*
* Also use friendlier labels to distinguish the start date
* and end date in listings (for fields that use both).
*/
function date_views_field_views_data_alter(&$result, $field, $module) {
if ($module == 'date') {
$has_end_date = !empty($field['settings']['todate']);
if ($has_end_date) {
$labels = field_views_field_label($field['field_name']);
$label = array_shift($labels);
}
foreach ($result as $table => $data) {
$additional = array();
$field_name = $field['field_name'];
foreach ($data as $column => $value) {
// The old 'entity_id' and 'revision_id' values got rewritten in Views.
// The old values are still there with a 'moved to' key, so ignore them.
if (array_key_exists('field', $value) && !array_key_exists('moved to', $value['field'])) {
$result[$table][$column]['field']['is date'] = TRUE;
// Not sure yet if we still need a custom field handler in D7 now that custom formatters are available.
// Might still need it to handle grouping of multiple value dates.
//$result[$table][$column]['field']['handler'] = 'date_handler_field_date';
//$result[$table][$column]['field']['add fields to query'] = TRUE;
}
// For filters, arguments, and sorts, determine if this column is for
// the start date ('value') or the end date ('value2').
$this_column = NULL;
foreach (array_keys($field['columns']) as $candidate_column) {
if ($column == $field['field_name'] . '_' . $candidate_column) {
$this_column = $candidate_column;
break;
}
}
// Only alter the date fields, not timezone, rrule, offset, etc.
if ($this_column != 'value' && $this_column != 'value2') {
continue;
}
// We will replace the label with a friendlier name in the case of
// arguments, filters, and sorts (but only if this field uses an end
// date).
$replace_label = FALSE;
if (array_key_exists('argument', $value)) {
$result[$table][$column]['argument']['handler'] = 'date_views_argument_handler_simple';
$result[$table][$column]['argument']['empty field name'] = t('Undated');
$result[$table][$column]['argument']['is date'] = TRUE;
$replace_label = $has_end_date;
}
if (array_key_exists('filter', $value)) {
$result[$table][$column]['filter']['handler'] = 'date_views_filter_handler_simple';
$result[$table][$column]['filter']['empty field name'] = t('Undated');
$result[$table][$column]['filter']['is date'] = TRUE;
$replace_label = $has_end_date;
}
if (array_key_exists('sort', $value)) {
$result[$table][$column]['sort']['is date'] = TRUE;
$replace_label = $has_end_date;
}
if ($replace_label) {
// Determine if this column is for the start date ('value') or the
// end date ('value2').
$this_column = NULL;
foreach (array_keys($field['columns']) as $candidate_column) {
if ($column == $field['field_name'] . '_' . $candidate_column) {
$this_column = $candidate_column;
break;
}
}
// Insert the phrase "start date" or "end date" after the label, so
// users can distinguish them in listings (compared to the default
// behavior of field_views_field_default_views_data(), which only
// uses the 'value2' column name to distinguish them).
switch ($this_column) {
case 'value':
// Insert a deliberate double space before 'start date' in the
// translatable string. This is a hack to get it to appear right
// before 'end date' in the listing (i.e., in a non-alphabetical,
// but more user friendly, order).
$result[$table][$column]['title'] = t('@label - start date (!name)', array('@label' => $label, '!name' => $field['field_name']));
$result[$table][$column]['title short'] = t('@label - start date', array('@label' => $label));
break;
case 'value2':
$result[$table][$column]['title'] = t('@label - end date (!name:!column)', array('@label' => $label, '!name' => $field['field_name'], '!column' => $this_column));
$result[$table][$column]['title short'] = t('@label - end date:!column', array('@label' => $label, '!column' => $this_column));
break;
}
}
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter() for views_ui_edit_form().
*/
function date_views_form_views_ui_edit_form_alter(&$form, &$form_state, $form_id) {
// This CSS is needed for the configuration form provided by the Date filter
// (date_views_filter_handler_simple), but we have to add it here so that
// it's already on the edit form the first time a Date filter is being added
// to the View. See http://drupal.org/node/1239228#comment-4885288.
$form['#attached']['css'][] = drupal_get_path('module', 'date_views') . '/css/date_views.css';
}
/**
* The instanceof function makes this work for any handler that was derived
* from 'views_handler_filter_date' or 'views_handler_argument_date',
* which includes core date fields like the node updated field.
*
* The test for $handler->min_date tells us that this is an argument that
* not only is derived from the views date handler but also has been processed
* by the Date Views filter or argument code.
*/
function date_views_handler_is_date($handler, $type = 'argument') {
switch ($type) {
case 'filter':
return $handler instanceof views_handler_filter_date && !empty($handler->min_date);
case 'argument':
return $handler instanceof views_handler_argument_date && !empty($handler->min_date);
}
return FALSE;
}
/**
* Validation hook for exposed filters that use the select widget.
* This is to ensure the the user completes all parts of the date
* not just some parts. Only needed for the select widget.
*/
function date_views_select_validate(&$form, &$form_state) {
// If there are no values just return.
if (empty($form['value']) && empty($form['min'])) {
return;
}
$granularity = (!empty($form['min']['#date_format'])) ? date_format_order($form['min']['#date_format']) : date_format_order($form['value']['#date_format']);
$filled = array();
$value = drupal_array_get_nested_value($form_state['input'], $form['#parents']);
foreach ($granularity as $part) {
if (!empty($value['value'][$part])) {
$filled[] = $part;
}
}
if (!empty($filled) && count($filled) != count($granularity)) {
$unfilled = array_diff($granularity, $filled);
foreach ($unfilled as $part) {
switch ($part) {
case 'year':
form_error($form['value'][$part], t('Please choose a year.'), $form_state);
break;
case 'month':
form_error($form['value'][$part], t('Please choose a month.'), $form_state);
break;
case 'day':
form_error($form['value'][$part], t('Please choose a day.'), $form_state);
break;
case 'hour':
form_error($form['value'][$part], t('Please choose an hour.'), $form_state);
break;
case 'minute':
form_error($form['value'][$part], t('Please choose a minute.'), $form_state);
break;
case 'second':
form_error($form['value'][$part], t('Please choose a second.'), $form_state);
break;
}
}
}
}
/**
* Implements hook_date_formatter_view_alter().
*
* If we are displaying a date from a view, see if we have information about
* which multiple value to display. If so, set the date_id in the entity.
*/
function date_views_date_formatter_pre_view_alter(&$entity, &$variables) {
// Some views have no row index.
if (!empty($entity->view) && isset($entity->view->row_index)) {
$field = $variables['field'];
$date_id = 'date_id_' . $field['field_name'];
$date_delta = 'date_delta_' . $field['field_name'];
$date_item = $entity->view->result[$entity->view->row_index];
if (!empty($date_item->$date_id)) {
$entity->date_id = 'date.' . $date_item->$date_id . '.' . $field['field_name'] . '.' . $date_item->$date_delta . '.0';
}
}
}

View File

@@ -0,0 +1,6 @@
<?php
/**
* @file
* Empty file to avoid fatal error if it doesn't exist.
* Formerly the attachment for the Date Browser.
*/

View File

@@ -0,0 +1,198 @@
<?php
/**
* @file
* Defines date-related Views data and plugins:
*
* Date argument:
* A generic date argument that has an option to select one or more
* Views date fields to filter on, automatically adds them to the view,
* and then filters the view by the value of the selected field(s).
* The flexible argument will accept and evaluate most ISO date
* and period formats, like 2009-05-01, 2008-W25, P1W.
*
* Date filter:
* A generic date filter that has an option to select a
* Views date field to filter on, with a choice of a widget to use
* for the filter form and an option to set the default value to
* a set date or something like 'now +90 days' . If the operator is
* set to 'between' or 'not between' you can set a default value for
* both the start and end dates.
*
* Current date argument default
* Adds a default option to set the argument to the current date
* when the argument is empty.
*
* Date navigation attachment
* Navigation that can be attached to any display to create back/next
* links by date, requires the date argument and uses the current
* date argument default to set a starting point for the view.
*/
/**
* Implements hook_views_plugins
*/
function date_views_views_plugins() {
$path = drupal_get_path('module', 'date_views');
$views_path = drupal_get_path('module', 'views');
module_load_include('inc', 'date_views', 'theme/theme');
return array(
'module' => 'date_views', // This just tells our themes are elsewhere.
'display' => array(
// Display plugin for date navigation.
'date_nav' => array(
'title' => t('Date browser'),
'help' => t('Date back/next navigation to attach to other displays. Requires the Date argument.'),
'handler' => 'date_plugin_display_attachment',
'parent' => 'attachment',
'path' => "$path/includes",
'theme' => 'views_view',
'use ajax' => TRUE,
'admin' => t('Date browser'),
'help topic' => 'date-browser',
),
),
'pager' => array(
'date_views_pager' => array(
'title' => t('Page by date'),
'help' => t('Page using the value of a date field.'),
'handler' => 'date_views_plugin_pager',
'path' => "$path/includes",
'help topic' => 'date-views-pager',
'uses options' => TRUE,
),
),
'style' => array(
'date_nav' => array(
'title' => t('Date browser style'),
'help' => t('Creates back/next navigation.'),
'handler' => 'date_navigation_plugin_style',
'path' => "$path/includes",
'theme' => 'date_navigation',
'theme file' => 'theme.inc',
'theme path' => "$path/theme",
'uses row plugin' => FALSE,
'uses fields' => FALSE,
'uses options' => TRUE,
'type' => 'date_nav',
'even empty' => TRUE,
),
),
);
}
/**
* Implements hook_views_data()
*/
function date_views_views_data() {
$data = array();
$tables = date_views_base_tables();
foreach ($tables as $base_table => $entity) {
// The flexible date argument.
$data[$base_table]['date_argument'] = array(
'group' => t('Date'),
'title' => t('Date (!base_table)', array('!base_table' => $base_table)),
'help' => t('Filter any Views !base_table date field by a date argument, using any common ISO date/period format (i.e. YYYY, YYYY-MM, YYYY-MM-DD, YYYY-W99, YYYY-MM-DD--P3M, P90D, etc). ', array('!base_table' => $base_table)),
'argument' => array(
'handler' => 'date_views_argument_handler',
'empty field name' => t('Undated'),
'is date' => TRUE,
//'skip base' => $base_table,
),
);
// The flexible date filter.
$data[$base_table]['date_filter'] = array(
'group' => t('Date'),
'title' => t('Date (!base_table)', array('!base_table' => $base_table)),
'help' => t('Filter any Views !base_table date field.', array('!base_table' => $base_table)),
'filter' => array(
'handler' => 'date_views_filter_handler',
'empty field name' => t('Undated'),
'is date' => TRUE,
//'skip base' => $base_table,
),
);
}
return $data;
}
/**
* Implements hook_views_data_alter().
*/
function date_views_views_data_alter(&$data) {
// Mark all the core date handlers as date fields.
// This will identify all handlers that directly use the _date handlers,
// will not pick up any that extend those handlers.
foreach ($data as $module => &$table) {
foreach ($table as $id => &$field) {
foreach (array('field', 'sort', 'filter', 'argument') as $type) {
if (isset($field[$type]) && isset($field[$type]['handler']) && ($field[$type]['handler'] == 'views_handler_' . $type . '_date')) {
$field[$type]['is date'] = TRUE;
}
}
}
}
}
/**
* Central function for setting up the right timezone values
* in the SQL date handler.
*
* The date handler will use this information to decide if the
* database value needs a timezone conversion.
*
* In Views, we will always be comparing to a local date value,
* so the goal is to convert the database value to the right
* value to compare to the local value.
*/
function date_views_set_timezone(&$date_handler, &$view, $field) {
switch ($field['tz_handling']) {
case 'date' :
$date_handler->db_timezone = 'UTC';
$date_handler->local_timezone_field = $field['timezone_field'];
$date_handler->offset_field = $field['offset_field'];
break;
case 'none':
$date_handler->db_timezone = date_default_timezone();
$date_handler->local_timezone = date_default_timezone();
break;
case 'utc':
$date_handler->db_timezone = 'UTC';
$date_handler->local_timezone = 'UTC';
break;
default :
$date_handler->db_timezone = 'UTC';
$date_handler->local_timezone = date_default_timezone();
break;
}
}
function date_views_querystring($view, $extra_params = array()) {
$query_params = array_merge($_GET, $extra_params);
// Allow NULL params to be removed from the query string.
foreach ($extra_params AS $key => $value) {
if (!isset($value)) {
unset($query_params[$key]);
}
}
// Filter the special "q" and "view" variables out of the query string.
$exclude = array('q');
// If we don't explicitly add a value for "view", filter it out.
if (empty($extra_params['view'])) {
$exclude[] = 'view';
}
// Clear out old date pager settings if not explicitly added.
if (!empty($view->date_info->pager_id) && empty($extra_params[$view->date_info->pager_id])) {
$exclude[] = $view->date_info->pager_id;
}
$query = drupal_get_query_parameters($query_params, $exclude);
// To prevent an empty query string from adding a "?" on to the end of a URL,
// we return NULL.
return !empty($query) ? $query : NULL;
}

View File

@@ -0,0 +1,200 @@
<?php
/**
* @file
* Date API views argument handler.
* This argument combines multiple date arguments into a single argument
* where all fields are controlled by the same date and can be combined with either AND or OR.
*/
/**
* Date API argument handler.
*/
class date_views_argument_handler extends date_views_argument_handler_simple {
/**
* Get granularity and use it to create the formula and a format
* for the results.
*/
function init(&$view, &$options) {
parent::init($view, $options);
if (empty($this->view->date_info)) {
$this->view->date_info = new stdClass();
}
if (empty($this->view->date_info->date_fields)) {
$this->view->date_info->date_fields = array();
}
$this->view->date_info->date_fields = array_merge($this->view->date_info->date_fields, $this->options['date_fields']);
}
/**
* Default value for the date_fields option.
*/
function option_definition() {
$options = parent::option_definition();
$options['date_fields'] = array('default' => array());
$options['date_method'] = array('default' => 'OR');
$options['date_group'] = array('default' => 'date');
return $options;
}
/**
* Add a form element to select date_fields for this argument.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
$fields = date_views_fields($this->base_table);
$options = array();
foreach ($fields['name'] as $name => $field) {
$options[$name] = $field['label'];
}
$form['date_fields'] = array(
'#title' => t('Date field(s)'),
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => $this->options['date_fields'],
'#multiple' => TRUE,
'#description' => t("Select one or more date fields to filter with this argument."),
);
$form['date_method'] = array(
'#title' => t('Method'),
'#type' => 'radios',
'#options' => array('OR' => t('OR'), 'AND' => t('AND')),
'#default_value' => $this->options['date_method'],
'#description' => t('Method of handling multiple date fields in the same query. Return items that have any matching date field (date = field_1 OR field_2), or only those with matches in all selected date fields (date = field_1 AND field_2). '),
);
$form['date_group'] = array(
'#type' => 'hidden',
'#value' => $this->options['date_group'],
);
}
function options_validate(&$form, &$form_state) {
// Views will whine if we don't have something for the these values even though we removed the option for summaries.
$form_state['values']['options']['summary']['format'] = 'none';
$form_state['values']['options']['summary']['options']['none'] = array();
// It is very important to call the parent function here:
parent::options_validate($form, $form_state);
if ($form_state['values']['form_id'] == 'views_ui_config_item_form') {
$check_fields = array_filter($form_state['values']['options']['date_fields']);
if (empty($check_fields)) {
form_error($form['date_fields'], t('You must select at least one date field for this argument.'));
}
}
}
function options_submit(&$form, &$form_state) {
// It is very important to call the parent function here:
parent::options_submit($form, $form_state);
if ($form_state['values']['form_id'] == 'views_ui_config_item_form') {
$form_state['values']['options']['date_fields'] = array_filter($form_state['values']['options']['date_fields']);
}
}
// Update the summary values to show selected granularity.
function admin_summary() {
$fields = date_views_fields($this->base_table);
if (!empty($this->options['date_fields'])) {
$output = array();
foreach ($this->options['date_fields'] as $field) {
if (array_key_exists($field, $fields['name'])) {
$output[] = $fields['name'][$field]['label'];
}
}
return implode('<br />' . $this->options['date_method'] . ' ', $output);
}
else {
return parent::admin_summary();
}
}
/**
* Provide a list of default behaviors for this argument if the argument
* is not present.
*
* Override this method to provide additional (or fewer) default behaviors.
*/
function default_actions($which = NULL) {
$defaults = parent::default_actions();
// There is no easy way to do summary queries on multiple fields, so remove that option.
unset($defaults['summary']);
if ($which) {
if (!empty($defaults[$which])) {
return $defaults[$which];
}
}
else {
return $defaults;
}
}
/**
* Set up the query for this argument.
*
* The argument sent may be found at $this->argument.
*/
function query($group_by = FALSE) {
// @TODO Not doing anything with $group_by yet, need to figure out what has to be done.
if ($this->date_forbid()) {
return;
}
$this->get_query_fields();
$this->query->set_where_group($this->options['date_method'], $this->options['date_group']);
$this->granularity = $this->date_handler->arg_granularity($this->argument);
$format = $this->date_handler->views_formats($this->granularity, 'sql');
$this->placeholders = array();
if (!empty($this->query_fields)) {
// Use set_where_group() with the selected date_method
// of 'AND' or 'OR' to create the where clause.
foreach ($this->query_fields as $count => $query_field) {
$field = $query_field['field'];
$this->date_handler = $query_field['date_handler'];
$this->field = $field['field_name'];
$this->real_field = $field['field_name'];
$this->table = $field['table_name'];
$this->original_table = $field['table_name'];
if ($field['table_name'] != $this->table || !empty($this->relationship)) {
$this->table = $this->query->ensure_table($field['table_name'], $this->relationship);
}
// $this->table_alias gets set when the first field is processed if otherwise empty.
// For subsequent fields, we need to be sure it is emptied again.
elseif (empty($this->relationship)) {
$this->table_alias = NULL;
}
parent::query($group_by);
$this->placeholders = array_merge($this->placeholders, $this->date_handler->placeholders);
}
}
}
/**
* Collect information about our fields we will need to create the right query.
*/
function get_query_fields() {
$fields = date_views_fields($this->base_table);
$fields = $fields['name'];
$this->query_fields = array();
foreach ($this->options['date_fields'] as $delta => $name) {
if (array_key_exists($name, $fields) && $field = $fields[$name]) {
$date_handler = new date_sql_handler($field['sql_type'], date_default_timezone());
$date_handler->granularity = $this->options['granularity'];
$date_handler->db_timezone = date_get_timezone_db($field['tz_handling']);
$date_handler->local_timezone = date_get_timezone($field['tz_handling']);
date_views_set_timezone($date_handler, $this, $field);
$this->query_fields[] = array('field' => $field, 'date_handler' => $date_handler);
}
}
}
}

View File

@@ -0,0 +1,345 @@
<?php
/**
* @file
* Date API views argument handler.
*/
/**
* Date API argument handler.
*/
class date_views_argument_handler_simple extends views_handler_argument_date {
/**
* Get granularity and use it to create the formula and a format
* for the results.
*/
function init(&$view, &$options) {
parent::init($view, $options);
// Add a date handler.
module_load_include('inc', 'date_api', 'date_api_sql');
$this->date_handler = new date_sql_handler(DATE_UNIX);
if (!empty($this->definition['field_name'])) {
$field = field_info_field($this->definition['field_name']);
if (!empty($field) && !empty($field['type'])) {
$this->date_handler->date_type = $field['type'];
$this->original_table = $this->definition['table'];
}
$this->date_handler->db_timezone = date_get_timezone_db($field['settings']['tz_handling']);
$this->date_handler->local_timezone = date_get_timezone($field['settings']['tz_handling']);
}
$this->date_handler->granularity = $this->options['granularity'];
// This value needs to be initialized so it exists even if the query doesn't run.
$this->date_handler->placeholders = array();
$this->format = $this->date_handler->views_formats($this->date_handler->granularity, 'display');
$this->sql_format = $this->date_handler->views_formats($this->date_handler->granularity, 'sql');
// $this->arg_format is the format the parent date handler will use to create a default argument.
$this->arg_format = $this->format();
// Identify the base table for this field.
// It will be used to call for the right query field options.
$this->base_table = $this->table;
}
function format() {
if (!empty($this->options['granularity'])) {
return $this->date_handler->views_formats($this->options['granularity']);
}
else {
return !empty($this->options[$this->option_name]) ? $this->options[$this->option_name] : 'Y-m';
}
}
/**
* Set the empty argument value to the current date,
* formatted appropriately for this argument.
*/
function get_default_argument($raw = FALSE) {
$is_default = FALSE;
if (!$raw && $this->options['default_argument_type'] == 'date') {
$granularity = $this->options['granularity'];
if ($granularity == 'week') {
$now = date_now();
$week = date_week(date_format($now, 'Y-m-d'));
$value = date_format($now, 'Y') . '-W' . $week;
}
else {
$value = date($this->arg_format, REQUEST_TIME);
}
drupal_alter('date_default_argument', $this, $value);
return $value;
}
// Let the parent argument handle options like node created date.
return parent::get_default_argument($raw);
}
/**
* Default value for the date_fields option.
*/
function option_definition() {
$options = parent::option_definition();
$options['year_range'] = array('default' => '-3:+3');
$options['granularity'] = array('default' => 'month');
$options['default_argument_type'] = array('default' => 'date');
$options['add_delta'] = array('default' => '');
$options['use_fromto'] = array('default' => '');
$options['title_format'] = array('default' => '');
$options['title_format_custom'] = array('default' => '');
return $options;
}
/**
* Add a form element to select date_fields for this argument.
*/
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
// Add an option to control the format of the summary.
$options = array(
'' => t('Default format'),
'custom' => t('Custom format'),
);
$example_month = date_format_date(date_example_date(), 'custom', $this->date_handler->views_formats('month', 'display'));
$example_day = date_format_date(date_example_date(), 'custom', $this->date_handler->views_formats('day', 'display'));
$form['title_format'] = array(
'#type' => 'select',
'#title' => t('Date format options'),
'#default_value' => $this->options['title_format'],
'#options' => $options,
'#description' => t('The date format used in titles and summary links for this argument. The default format is based on the granularity of the filter, i.e. month: @example_month, day: @example_day.', array('@example_month' => $example_month, '@example_day' => $example_day)),
'#attributes' => array('class' => array('dependent-options')),
'#states' => array(
'visible' => array(
':input[name="options[default_action]"]' => array('value' => 'summary')
),
),
);
$form['title_format_custom'] = array(
'#type' => 'textfield',
'#title' => t('Custom summary date format'),
'#default_value' => $this->options['title_format_custom'],
'#description' => t("A custom format for the title and summary date format. Define a php date format string like 'm-d-Y H:i' (see <a href=\"@link\">http://php.net/date</a> for more details).", array('@link' => 'http://php.net/date')),
'#attributes' => array('class' => array('dependent-options')),
'#states' => array(
'visible' => array(
':input[name="options[title_format]"]' => array('value' => 'custom')
),
),
);
$options = $this->date_handler->date_parts();
unset($options['second'], $options['minute']);
$options += array('week' => t('Week', array(), array('context' => 'datetime')));
$form['granularity'] = array(
'#title' => t('Granularity'),
'#type' => 'radios',
'#options' => $options,
'#default_value' => $this->options['granularity'],
'#multiple' => TRUE,
'#description' => t("Select the type of date value to be used in defaults, summaries, and navigation. For example, a granularity of 'month' will set the default date to the current month, summarize by month in summary views, and link to the next and previous month when using date navigation."),
);
$form['year_range'] = array(
'#title' => t('Date year range'),
'#type' => 'textfield',
'#default_value' => $this->options['year_range'],
'#description' => t("Set the allowable minimum and maximum year range for this argument, either a -X:+X offset from the current year, like '-3:+3' or an absolute minimum and maximum year, like '2005:2010' . When the argument is set to a date outside the range, the page will be returned as 'Page not found (404)' ."),
);
$form['use_fromto'] = array(
'#type' => 'radios',
'#title' => t('Dates to compare'),
'#default_value' => $this->options['use_fromto'],
'#options' => array('' => t('Start/End date range'), 'no' => t('Only this field')),
'#description' => t("If selected the view will check if any value starting with the 'Start' date and ending with the 'End' date matches the view criteria. Otherwise the view will be limited to the specifically selected fields. Comparing to the whole Start/End range is the recommended setting when using this filter in a Calendar. When using the Start/End option, it is not necessary to add both the Start and End fields to the filter, either one will do."),
);
$access = TRUE;
if (!empty($this->definition['field_name'])) {
$field = field_info_field($this->definition['field_name']);
$access = $field['cardinality'] != 1;
}
$form['add_delta'] = array(
'#type' => 'radios',
'#title' => t('Add multiple value identifier'),
'#default_value' => $this->options['add_delta'],
'#options' => array('' => t('No'), 'yes' => t('Yes')),
'#description' => t('Add an identifier to the view to show which multiple value date fields meet the filter criteria. Note: This option may introduce duplicate values into the view. Required when using multiple value fields in a Calendar or any time you want the node view of multiple value dates to display only the values that match the view filters.'),
// Only let mere mortals tweak this setting for multi-value fields
'#access' => $access,
);
}
function options_validate(&$form, &$form_state) {
// It is very important to call the parent function here:
parent::options_validate($form, $form_state);
if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) {
form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9'));
}
}
/**
* Provide a link to the next level of the view from the summary.
*/
function summary_name($data) {
$value = $data->{$this->name_alias};
if (empty($value) && !empty($this->definition['empty field name'])) {
return $this->definition['empty field name'];
}
elseif (empty($value)) {
return $this->options['wildcard_substitution'];
}
$format = !empty($this->options['title_format_custom']) && !empty($this->options['title_format_custom']) ? $this->options['title_format_custom'] : $this->date_handler->views_formats($this->options['granularity'], 'display');
$range = $this->date_handler->arg_range($value);
return date_format_date($range[0], 'custom', $format);
}
/**
* Provide a title for the view based on the argument value.
*/
function title() {
$format = !empty($this->options['title_format_custom']) && !empty($this->options['title_format_custom']) ? $this->options['title_format_custom'] : $this->date_handler->views_formats($this->options['granularity'], 'display');
$range = $this->date_handler->arg_range($this->argument);
return date_format_date($range[0], 'custom', $format);
}
/**
* Provide the argument to use to link from the summary to the next level;
* this will be called once per row of a summary, and used as part of
* $view->get_url().
*
* @param $data
* The query results for the row.
*/
function summary_argument($data) {
$format = $this->date_handler->views_formats($this->options['granularity'], 'sql');
$value = $data->{$this->name_alias};
if (empty($value)) {
return $this->options['exception']['value'];
}
$range = $this->date_handler->arg_range($value);
return date_format_date($range[0], 'custom', $format);
}
/**
* Inject a test for valid date range before the summary query.
*/
function summary_query() {
// @TODO The summary values are computed by the database. Unless the database has
// built-in timezone handling it will use a fixed offset, which will not be
// right for all dates. The only way I can see to make this work right is to
// store the offset for each date in the database so it can be added to the base
// date value before the database formats the result. Because this is a huge
// architectural change, it won't go in until we start a new branch.
$this->formula = $this->date_handler->sql_format($this->sql_format, $this->date_handler->sql_field("***table***.$this->real_field"));
$this->ensure_my_table();
// Now that our table is secure, get our formula.
$formula = $this->get_formula();
// Add the field, give it an alias that does NOT match the actual field name or grouping won't work right.
$this->base_alias = $this->name_alias = $this->query->add_field(NULL, $formula, $this->field . '_summary');
$this->query->set_count_field(NULL, $formula, $this->field);
return $this->summary_basics(FALSE);
}
/**
* Inject a test for valid date range before the regular query.
* Override the parent query to be able to control the $group.
*/
function query($group_by = FALSE) {
// @TODO Not doing anything with $group_by yet, need to figure out what has to be done.
if ($this->date_forbid()) {
return;
}
// See if we need to reset granularity based on an argument value.
// Make sure we don't try to reset to some bogus value if someone has typed in an unexpected argument.
$granularity = $this->date_handler->arg_granularity($this->argument);
if (!empty($granularity)) {
$this->date_handler->granularity = $granularity;
$this->format = $this->date_handler->views_formats($this->date_handler->granularity, 'display');
$this->sql_format = $this->date_handler->views_formats($this->date_handler->granularity, 'sql');
}
$this->granularity = $this->date_handler->granularity;
$this->ensure_my_table();
$group = !empty($this->options['date_group']) ? $this->options['date_group'] : 0;
// If requested, add the delta field to the view so we can later find the value that matched our query.
if (!empty($this->options['add_delta']) && (substr($this->real_field, -6) == '_value' || substr($this->real_field, -7) == '_value2')) {
$this->query->add_field($this->table_alias, 'delta');
$real_field_name = str_replace(array('_value', '_value2'), '', $this->real_field);
$this->query->add_field($this->table_alias, 'entity_id', 'date_id_' . $real_field_name);
$this->query->add_field($this->table_alias, 'delta', 'date_delta_' . $real_field_name);
}
$format = $this->date_handler->granularity == 'week' ? DATE_FORMAT_DATETIME : $this->sql_format;
$view_min = date_format($this->min_date, $format);
$view_max = date_format($this->max_date, $format);
$view_min_placeholder = $this->placeholder();
$view_max_placeholder = $this->placeholder();
$this->date_handler->placeholders = array($view_min_placeholder => $view_min, $view_max_placeholder => $view_max);
// Are we comparing this field only or the Start/End date range to the view criteria?
if (!empty($this->options['use_fromto'])) {
// The simple case, match the field to the view range.
$field = $this->date_handler->sql_field($this->table_alias . '.' . $this->real_field, NULL, $this->min_date);
$field = $this->date_handler->sql_format($format, $field);
$this->query->add_where_expression($group, "$field >= $view_min_placeholder AND $field <= $view_max_placeholder", array($view_min_placeholder => $view_min, $view_max_placeholder => $view_max));
}
else {
// Look for the intersection of the range of the date field with the range of the view.
// Get the Start/End values for this field. Retrieve using the original table name.
// Swap the current table name (adjusted for relationships) into the query.
// @TODO We may be able to use Views substitutions here, investigate that later.
$fields = date_views_fields($this->base_table);
$fields = $fields['name'];
$fromto = $fields[$this->original_table . '.' . $this->real_field]['fromto'];
$value_min = str_replace($this->original_table, $this->table_alias, $fromto[0]);
$value_max = str_replace($this->original_table, $this->table_alias, $fromto[1]);
$field_min = $this->date_handler->sql_field($value_min, NULL, $this->min_date);
$field_min = $this->date_handler->sql_format($format, $field_min);
$field_max = $this->date_handler->sql_field($value_max, NULL, $this->max_date);
$field_max = $this->date_handler->sql_format($format, $field_max);
$this->query->add_where_expression($group, "$field_max >= $view_min_placeholder AND $field_min <= $view_max_placeholder", array($view_min_placeholder => $view_min, $view_max_placeholder => $view_max));
}
}
/**
* Add a callback to determine if we have moved outside the valid date range for this argument.
*/
function date_forbid() {
if (empty($this->argument)) {
return TRUE;
}
$this->date_range = $this->date_handler->arg_range($this->argument);
$this->min_date = $this->date_range[0];
$this->max_date = $this->date_range[1];
$this->limit = date_range_years($this->options['year_range']);
$group = !empty($this->options['date_group']) ? $this->options['date_group'] : 0;
// See if we're outside the allowed date range for our argument.
if (date_format($this->min_date, 'Y') < $this->limit[0] || date_format($this->max_date, 'Y') > $this->limit[1]) {
$this->forbid = TRUE;
$this->view->build_info['fail'] = TRUE;
return TRUE;
}
return FALSE;
}
}

View File

@@ -0,0 +1,169 @@
<?php
/**
* @file
* Helper for identifying Date API fields for views.
*/
/**
* Identify all potential date/timestamp fields.
*
* @return
* array with fieldname, type, and table.
* @see
* date_views_date_views_fields() which implements
* the hook_date_views_fields() for the core date fields.
*/
function _date_views_fields($base = 'node') {
// Make sure $base is never empty.
if (empty($base)) {
$base = 'node';
}
$cid = 'date_views_fields_' . $base;
cache_clear_all($cid, 'cache_views');
// We use fields that provide filter handlers as our universe of possible
// fields of interest.
$all_fields = date_views_views_fetch_fields($base, 'filter');
// Iterate over all the fields that Views knows about.
$fields = array();
foreach ((array) $all_fields as $alias => $val) {
// Set up some default values.
$granularity = array('year', 'month', 'day', 'hour', 'minute', 'second');
$tz_handling = 'site';
$related_fields = array();
$timezone_field = '';
$offset_field = '';
$rrule_field = '';
$delta_field = '';
$sql_type = DATE_UNIX;
$type = '';
$name = $alias;
$tmp = explode('.', $name);
$field_name = $tmp[1];
$table_name = $tmp[0];
// Unset the date filter to avoid ugly recursion and broken values.
if ($field_name == 'date_filter') {
continue;
}
$fromto = array($name, $name);
// If we don't have a filter handler, we don't need to do anything more.
if (!$handler = views_get_handler($table_name, $field_name, 'filter')) {
continue;
}
$handler = views_get_handler($table_name, $field_name, 'filter');
$handler_name = $handler->definition['handler'];
// We don't care about anything but date handlers
if (empty($handler->definition['is date'])) {
continue;
}
$is_field = FALSE;
// For Field module fields, get the date type.
$custom = array();
if (isset($handler->definition['field_name'])) {
$field = field_info_field($handler->definition['field_name']);
$is_field = TRUE;
switch ($field['type']) {
case 'date':
$sql_type = DATE_ISO;
break;
case 'datestamp':
break;
case 'datetime':
$sql_type = DATE_DATETIME;
break;
default:
// If this is not a date field, nothing more to do.
continue;
}
$revision = in_array($base, array('node_revision')) ? FIELD_LOAD_REVISION : FIELD_LOAD_CURRENT;
$db_info = date_api_database_info($field, $revision);
$name = $table_name . "." . $field_name;
$granularity = !empty($field['granularity']) ? $field['granularity'] : array('year', 'month', 'day', 'hour', 'minute', 'second');
$fromto = array(
$table_name . '.' . $db_info['columns'][$table_name]['value'],
$table_name . '.' . (!empty($field['settings']['todate']) ? $db_info['columns'][$table_name]['value2'] : $db_info['columns'][$table_name]['value']),
);
if (isset($field['settings']['tz_handling'])) {
$tz_handling = $field['settings']['tz_handling'];
$db_info = date_api_database_info($field, $revision);
if ($tz_handling == 'date') {
$offset_field = $table_name . '.' . $db_info['columns'][$table_name]['offset'];
}
$related_fields = array(
$table_name . '.' . $db_info['columns'][$table_name]['value']
);
if (isset($db_info['columns'][$table_name]['value2'])) {
$related_fields = array_merge($related_fields, array($table_name . '.' . $db_info['columns'][$table_name]['value2']));
}
if (isset($db_info['columns'][$table_name]['timezone'])) {
$related_fields = array_merge($related_fields, array($table_name . '.' . $db_info['columns'][$table_name]['timezone']));
$timezone_field = $table_name . '.' . $db_info['columns'][$table_name]['timezone'];
}
if (isset($db_info['columns'][$table_name]['rrule'])) {
$related_fields = array_merge($related_fields, array($table_name . '.' . $db_info['columns'][$table_name]['rrule']));
$rrule_field = $table_name . '.' . $db_info['columns'][$table_name]['rrule'];
}
}
// Get the delta value into the query.
if ($field['cardinality'] != 1) {
array_push($related_fields, "$table_name.delta");
$delta_field = $table_name . '_delta';
}
}
// Allow custom modules to provide date fields.
else {
foreach (module_implements('date_views_fields') as $module) {
$function = $module . '_date_views_fields';
if ($custom = $function("$table_name.$field_name")) {
$type = 'custom';
break;
}
}
}
// Don't do anything if this is not a date field we can handle.
if (!empty($type) || empty($custom)) {
$alias = str_replace('.', '_', $alias);
$fields['name'][$name] = array(
'is_field' => $is_field,
'sql_type' => $sql_type,
'label' => $val['group'] . ': ' . $val['title'],
'granularity' => $granularity,
'fullname' => $name,
'table_name' => $table_name,
'field_name' => $field_name,
'query_name' => $alias,
'fromto' => $fromto,
'tz_handling' => $tz_handling,
'offset_field' => $offset_field,
'timezone_field' => $timezone_field,
'rrule_field' => $rrule_field,
'related_fields' => $related_fields,
'delta_field' => $delta_field,
);
// Allow the custom fields to over-write values.
if (!empty($custom)) {
foreach ($custom as $key => $val) {
$fields['name'][$name][$key] = $val;
}
}
$fields['name'][$name]['real_field_name'] = $field_name;
$fields['alias'][$alias] = $fields['name'][$name];
}
}
cache_set($cid, $fields, 'cache_views');
return $fields;
}

View File

@@ -0,0 +1,177 @@
<?php
/**
* @file
* A flexible, configurable date filter.
* This filter combines multiple date filters into a single filter
* where all fields are controlled by the same date and can be combined with either AND or OR.
*/
class date_views_filter_handler extends date_views_filter_handler_simple {
function init(&$view, &$options) {
parent::init($view, $options);
if (empty($this->view->date_info)) {
$this->view->date_info = new stdClass();
}
if (empty($this->view->date_info->date_fields)) {
$this->view->date_info->date_fields = array();
}
$this->view->date_info->date_fields = array_merge($this->view->date_info->date_fields, $this->options['date_fields']);
}
// Set default values for the date filter.
function option_definition() {
$options = parent::option_definition();
$options['date_fields'] = array('default' => array());
$options['date_method'] = array('default' => 'OR');
$options['date_group'] = array('default' => 'date');
return $options;
}
function op_between($field) {
$this->date_combine_conditions('op_between');
}
function op_simple($field) {
$this->date_combine_conditions('op_simple');
}
/**
* Combines multiple date WHERE expressions into a single WHERE expression.
*
* @param string $function
* The function name to use to add individual conditions. Either 'op_simple'
* or 'op_between'.
*/
protected function date_combine_conditions($function) {
$this->get_query_fields();
if (empty($this->query_fields)) {
return;
}
// Create a custom filter group for the conditions.
$this->query->set_where_group($this->options['date_method'], $this->options['date_group']);
// Add each condition to the custom filter group.
foreach ((array) $this->query_fields as $query_field) {
$field = $query_field['field'];
$this->date_handler = $query_field['date_handler'];
// Respect relationships when determining the table alias.
if ($field['table_name'] != $this->table || !empty($this->relationship)) {
$this->related_table_alias = $this->query->ensure_table($field['table_name'], $this->relationship);
}
$table_alias = !empty($this->related_table_alias) ? $this->related_table_alias : $field['table_name'];
$field_name = $table_alias . '.' . $field['field_name'];
// Call the appropriate function, either 'op_between' or 'op_simple'.
parent::$function($field_name);
}
// Gather all of the condition strings and their placeholders.
$conditions = array();
$placeholders = array();
foreach ($this->query->where[$this->options['date_group']]['conditions'] as $condition) {
$conditions[] = $condition['field'];
$placeholders += $condition['value'];
}
// Remove the conditions from the custom filter group.
unset($this->query->where[$this->options['date_group']]);
// Combine all of the conditions into one string.
$conditions = implode(' ' . $this->options['date_method'] . ' ', $conditions);
// Add it to the filter group chosen in the Views UI.
$this->query->add_where_expression($this->options['group'], $conditions, $placeholders);
}
function extra_options_form(&$form, &$form_state) {
parent::extra_options_form($form, $form_state);
$fields = date_views_fields($this->base_table);
$options = array();
foreach ($fields['name'] as $name => $field) {
$options[$name] = $field['label'];
}
$form['date_fields'] = array(
'#title' => t('Date field(s)'),
'#type' => 'checkboxes',
'#options' => $options,
'#default_value' => $this->options['date_fields'],
'#multiple' => FALSE,
'#description' => t('Select date field(s) to filter.'),
'#required' => TRUE,
);
$form['date_method'] = array(
'#title' => t('Method'),
'#type' => 'radios',
'#options' => array('OR' => t('OR'), 'AND' => t('AND')),
'#default_value' => $this->options['date_method'],
'#description' => t('Method of handling multiple date fields in the same query. Return items that have any matching date field (date = field_1 OR field_2), or only those with matches in all selected date fields (date = field_1 AND field_2).'),
);
}
function extra_options_validate($form, &$form_state) {
$check_fields = array_filter($form_state['values']['options']['date_fields']);
if (empty($check_fields)) {
form_error($form['date_fields'], t('You must select at least one date field for this filter.'));
}
}
function extra_options_submit($form, &$form_state) {
$form_state['values']['options']['date_fields'] = array_filter($form_state['values']['options']['date_fields']);
}
// Update the summary values to provide
// meaningful information for each option.
function admin_summary() {
if (empty($this->options['date_fields'])) {
return t('Missing date fields!');
}
$handler = $this->date_handler;
$fields = date_views_fields($this->view->base_table);
if (!empty($this->options['date_fields'])) {
$output = array();
foreach ($this->options['date_fields'] as $field) {
if (array_key_exists($field, $fields['name'])) {
$output[] = $fields['name'][$field]['label'];
}
}
}
$field = implode(' ' . $this->options['date_method'] . ' ', $output);
$output = "$field " . check_plain($this->operator) . ' ';
$parts = $handler->date_parts();
$widget_options = $this->widget_options();
// If the filter is exposed, display the granularity.
if ($this->options['exposed']) {
return t('(@field) <strong>Exposed</strong> @widget @format', array('@field' => $field, '@format' => $parts[$handler->granularity], '@widget' => $widget_options[$this->options['form_type']]));
}
// If not exposed, display the value.
if (in_array($this->operator, $this->operator_values(2))) {
$min = check_plain(!empty($this->options['default_date']) ? $this->options['default_date'] : $this->options['value']['min']);
$max = check_plain(!empty($this->options['default_to_date']) ? $this->options['default_to_date'] : $this->options['value']['max']);
$output .= t('@min and @max', array('@min' => $min, '@max' => $max));
}
else {
$output .= check_plain(!empty($this->options['default_date']) ? $this->options['default_date'] : $this->options['value']['value']);
}
return $output;
}
function get_query_fields() {
$fields = date_views_fields($this->base_table);
$fields = $fields['name'];
$this->query_fields = array();
foreach ((array) $this->options['date_fields'] as $delta => $name) {
if (array_key_exists($name, $fields) && $field = $fields[$name]) {
$date_handler = new date_sql_handler($field['sql_type'], date_default_timezone());
$date_handler->granularity = $this->options['granularity'];
$date_handler->db_timezone = date_get_timezone_db($field['tz_handling']);
$date_handler->local_timezone = date_get_timezone($field['tz_handling']);
$this->query_fields[] = array('field' => $field, 'date_handler' => $date_handler);
}
}
}
}

View File

@@ -0,0 +1,494 @@
<?php
/**
* @file
* A standard Views filter for a single date field, using Date API form selectors and sql handling.
*/
class date_views_filter_handler_simple extends views_handler_filter_date {
var $date_handler = NULL;
var $offset = NULL;
function init(&$view, &$options) {
parent::init($view, $options);
module_load_include('inc', 'date_api', 'date_api_sql');
$this->date_handler = new date_sql_handler(DATE_UNIX);
if (!empty($this->definition['field_name'])) {
$field = field_info_field($this->definition['field_name']);
if (!empty($field) && !empty($field['type'])) {
$this->date_handler->date_type = $field['type'];
}
$this->date_handler->db_timezone = date_get_timezone_db($field['settings']['tz_handling']);
$this->date_handler->local_timezone = date_get_timezone($field['settings']['tz_handling']);
}
$this->form_submitted = FALSE;
$this->date_handler->granularity = isset($options['granularity']) ? $options['granularity'] : 'day';
$this->format = $this->date_handler->views_formats($this->options['granularity'], 'sql');
// Identify the base table for this field.
// It will be used to call for the right query field options.
$this->base_table = $this->table;
}
// Set default values for the date filter.
function option_definition() {
$options = parent::option_definition();
$options['granularity'] = array('default' => 'day');
$options['form_type'] = array('default' => 'date_select');
$options['default_date'] = array('default' => '');
$options['default_to_date'] = array('default' => '');
$options['year_range'] = array('default' => '-3:+3');
$options['add_delta'] = array('default' => '');
return $options;
}
/**
* Helper function to find a default value.
*/
function date_default_value($prefix, $options = NULL) {
$default_date = '';
if (empty($options)) {
$options = $this->options;
}
// If this is a remembered value, use the value from the SESSION.
if (!empty($this->options['expose']['remember'])) {
$display_id = ($this->view->display_handler->is_defaulted('filters')) ? 'default' : $this->view->current_display;
if (!empty($_SESSION['views'][$this->view->name][$display_id]['date_filter'][$prefix])) {
return $_SESSION['views'][$this->view->name][$display_id]['date_filter'][$prefix];
}
}
// This is a date that needs to be constructed from options like 'now' .
$default_option = $prefix == 'max' ? $options['default_to_date'] : $options['default_date'];
if (!empty($default_option)) {
str_replace('now', 'today', $default_option);
$date = date_create($default_option, date_default_timezone_object());
$default_date = !empty($date) ? $date->format($this->format) : '';
// The format for our filter is in ISO format, but the widget will need it in datetime format.
$default_date = str_replace('T', ' ', $default_date);
}
// This a fixed date.
else {
$default_date = $options['value'][$prefix];
}
return $default_date;
}
/**
* Helper function to see if we need to swap in the default value.
*
* Views exposed filters treat everything as submitted, so if it's an empty value we have to
* see if anything actually was submitted. If nothing has really been submitted, we need
* to swap in our default value logic.
*/
function get_filter_value($prefix, $input) {
// All our date widgets provide datetime values but we use ISO in our SQL
// for consistency between the way filters and arguments work (arguments
// cannot contain spaces).
if (empty($input)) {
if (empty($this->options['exposed'])) {
return str_replace(' ', 'T', $this->date_default_value($prefix));
}
elseif (isset($this->options['expose']['identifier']) && !isset($_GET[$this->options['expose']['identifier']])) {
return str_replace(' ', 'T', $this->date_default_value($prefix));
}
}
return str_replace(' ', 'T', $input);
}
function accept_exposed_input($input) {
if (!empty($this->options['exposed'])) {
$element_input = $input[$this->options['expose']['identifier']];
$element_input['value'] = $this->get_filter_value('value', !empty($element_input['value']) ? $element_input['value'] : '');
$element_input['min'] = $this->get_filter_value('min', !empty($element_input['min']) ? $element_input['min'] : '');
$element_input['max'] = $this->get_filter_value('max', !empty($element_input['max']) ? $element_input['max'] : '');
unset($element_input['default_date']);
unset($element_input['default_to_date']);
$input[$this->options['expose']['identifier']] = $element_input;
}
return parent::accept_exposed_input($input);
}
function op_between($field) {
// Add the delta field to the view so we can later find the value that matched our query.
list($table_name, $field_name) = explode('.', $field);
if (!empty($this->options['add_delta']) && (substr($field_name, -6) == '_value' || substr($field_name, -7) == '_value2')) {
$this->query->add_field($table_name, 'delta');
$real_field_name = str_replace(array('_value', '_value2'), '', $this->real_field);
$this->query->add_field($table_name, 'entity_id', 'date_id_' . $real_field_name);
$this->query->add_field($table_name, 'delta', 'date_delta_' . $real_field_name);
}
$min_value = $this->get_filter_value('min', $this->value['min']);
$min_comp_date = new DateObject($min_value, date_default_timezone(), $this->format);
$max_value = $this->get_filter_value('max', $this->value['max']);
$max_comp_date = new DateObject($max_value, date_default_timezone(), $this->format);
$field_min = $this->date_handler->sql_field($field, NULL, $min_comp_date);
$field_min = $this->date_handler->sql_format($this->format, $field_min);
$field_max = $this->date_handler->sql_field($field, NULL, $max_comp_date);
$field_max = $this->date_handler->sql_format($this->format, $field_max);
$placeholder_min = $this->placeholder();
$placeholder_max = $this->placeholder();
$group = !empty($this->options['date_group']) ? $this->options['date_group'] : $this->options['group'];
if ($this->operator == 'between') {
$this->query->add_where_expression($group, "$field_min >= $placeholder_min AND $field_max <= $placeholder_max", array($placeholder_min => $min_value, $placeholder_max => $max_value));
}
else {
$this->query->add_where_expression($group, "$field_min < $placeholder_min OR $field_max > $placeholder_max", array($placeholder_min => $min_value, $placeholder_max => $max_value));
}
}
function op_simple($field) {
// Add the delta field to the view so we can later find the value that matched our query.
list($table_name, $field_name) = explode('.', $field);
if (!empty($this->options['add_delta']) && (substr($field_name, -6) == '_value' || substr($field_name, -7) == '_value2')) {
$this->query->add_field($table_name, 'delta');
$real_field_name = str_replace(array('_value', '_value2'), '', $this->real_field);
$this->query->add_field($table_name, 'entity_id', 'date_id_' . $real_field_name);
$this->query->add_field($table_name, 'delta', 'date_delta_' . $real_field_name);
}
$value = $this->get_filter_value('value', $this->value['value']);
$comp_date = new DateObject($value, date_default_timezone(), $this->format);
$field = $this->date_handler->sql_field($field, NULL, $comp_date);
$field = $this->date_handler->sql_format($this->format, $field);
$placeholder = $this->placeholder();
$group = !empty($this->options['date_group']) ? $this->options['date_group'] : $this->options['group'];
$this->query->add_where_expression($group, "$field $this->operator $placeholder", array($placeholder => $value));
}
/**
* Set the granularity of the date parts to use in the filter.
*/
function has_extra_options() { return TRUE; }
/**
* Date selection options.
*/
function widget_options() {
$options = array(
'date_select' => t('Select'),
'date_text' => t('Text'),
'date_popup' => t('Popup'),
);
if (!module_exists('date_popup')) {
unset($options['date_popup']);
}
return $options;
}
function year_range() {
$year_range = explode(':', $this->options['year_range']);
if (substr($this->options['year_range'], 0, 1) == '-' || $year_range[0] < 0) {
$this_year = date_format(date_now(), 'Y');
$year_range[0] = $this_year + $year_range[0];
$year_range[1] = $this_year + $year_range[1];
}
return $year_range;
}
function extra_options_form(&$form, &$form_state) {
parent::extra_options_form($form, $form_state);
$form['form_type'] = array(
'#type' => 'radios',
'#title' => t('Date selection form element'),
'#default_value' => $this->options['form_type'],
'#options' => $this->widget_options(),
);
$form['granularity'] = $this->date_handler->granularity_form($this->options['granularity']);
$form['granularity']['#title'] = t('Filter granularity');
$form['year_range'] = array(
'#type' => 'date_year_range',
'#default_value' => $this->options['year_range'],
);
if (!empty($this->definition['field_name'])) {
$field = field_info_field($this->definition['field_name']);
}
$form['add_delta'] = array(
'#type' => 'radios',
'#title' => t('Add multiple value identifier'),
'#default_value' => $this->options['add_delta'],
'#options' => array('' => t('No'), 'yes' => t('Yes')),
'#description' => t('Add an identifier to the view to show which multiple value date fields meet the filter criteria. Note: This option may introduce duplicate values into the view. Required when using multiple value fields in a Calendar or any time you want the node view of multiple value dates to display only the values that match the view filters.'),
// Only let mere mortals tweak this setting for multi-value fields
'#access' => !empty($field) ? $field['cardinality'] != 1 : 0,
);
}
function extra_options_validate($form, &$form_state) {
if (!preg_match('/^(?:\-[0-9]{1,4}|[0-9]{4}):(?:[\+|\-][0-9]{1,4}|[0-9]{4})$/', $form_state['values']['options']['year_range'])) {
form_error($form['year_range'], t('Date year range must be in the format -9:+9, 2005:2010, -9:2010, or 2005:+9'));
}
}
/**
* Add the selectors to the value form using the date handler.
*/
function value_form(&$form, &$form_state) {
// We use different values than the parent form, so we must
// construct our own form element.
$form['value'] = array();
$form['value']['#tree'] = TRUE;
// Below section copied from views_handler_filter_numeric.inc.
$which = 'all';
$source = '';
if (!empty($form['operator'])) {
$source = ($form['operator']['#type'] == 'radios') ? 'radio:options[operator]' : 'edit-options-operator';
}
$identifier = $this->options['expose']['identifier'];
if (!empty($form_state['exposed'])) {
if (empty($this->options['expose']['use_operator']) || empty($this->options['expose']['operator_id'])) {
// exposed and locked.
$which = in_array($this->operator, $this->operator_values(2)) ? 'minmax' : 'value';
}
else {
$source = 'edit-' . drupal_html_id($this->options['expose']['operator_id']);
}
}
if ($which == 'all' || $which == 'value') {
$form['value'] += $this->date_parts_form($form_state, 'value', $source, $which, $this->operator_values(1), $identifier, 'default_date');
}
if ($which == 'all' || $which == 'minmax') {
$form['value'] += $this->date_parts_form($form_state, 'min', $source, $which, $this->operator_values(2), $identifier, 'default_date');
$form['value'] += $this->date_parts_form($form_state, 'max', $source, $which, $this->operator_values(2), $identifier, 'default_to_date');
}
// Add some extra validation for the select widget to be sure that
// the user inputs all parts of the date.
if ($this->options['form_type'] == 'date_select') {
$form['value']['#element_validate'] = array('date_views_select_validate');
}
}
/**
* A form element to select date part values.
*
* @param string $prefix
* A prefix for the date values, 'value', 'min', or 'max' .
* @param string $source
* The operator for this element.
* @param string $which
* Which element to provide, 'all', 'value', or 'minmax' .
* @param array $operator_values
* An array of the allowed operators for this element.
* @param array $identifier
* Identifier of the exposed element.
* @param array $relative_id
* Form element id to use for the relative date field.
*
* @return
* The form date part element for this instance.
*/
function date_parts_form($form_state, $prefix, $source, $which, $operator_values, $identifier, $relative_id) {
module_load_include('inc', 'date_api', 'date_api_elements');
switch ($prefix) {
case 'min':
$label = t('Start date');
$relative_label = t('Relative start date');
break;
case 'max':
$label = t('End date');
$relative_label = t('Relative end date');
break;
default:
$label = '';
$relative_label = t('Relative date');
break;
}
$type = $this->options['form_type'];
if ($type == 'date_popup' && !module_exists('date_popup')) {
$type = 'date_text';
}
$format = $this->date_handler->views_formats($this->options['granularity'], 'sql');
$granularity = array_keys($this->date_handler->date_parts($this->options['granularity']));
$relative_value = ($prefix == 'max' ? $this->options['default_to_date'] : $this->options['default_date']);
if (!empty($form_state['exposed'])) {
// UI when the date selector is exposed.
$default_date = $this->date_default_value($prefix);
$id = 'edit-' . str_replace('_', '-', $this->field) . '-' . $prefix;
$form[$prefix] = array(
'#title' => check_plain($label),
'#type' => $type,
'#size' => 20,
'#default_value' => !empty($this->value[$prefix]) ? $this->value[$prefix] : $default_date,
'#date_format' => date_limit_format($format, $granularity),
'#date_label_position' => 'within',
'#date_year_range' => $this->options['year_range'],
'#process' => array($type . '_element_process'),
'#prefix' => '<div id="' . $id . '-wrapper"><div id="' . $id . '">',
'#suffix' => '</div></div>',
);
if ($which == 'all') {
$form[$prefix]['#pre_render'][] = 'ctools_dependent_pre_render';
$form[$prefix]['#dependency'] = array($source => $operator_values);
}
if (!isset($form_state['input'][$identifier][$prefix])) {
$form_state['input'][$identifier][$prefix] = $this->value[$prefix];
}
}
else {
// UI when the date selector is on the views configuration screen.
$default_date = '';
$id = 'edit-options-value-' . $prefix;
$form[$prefix . '_group'] = array(
'#type' => 'fieldset',
'#attributes' => array('class' => array('date-views-filter-fieldset')),
);
$form[$prefix . '_group'][$prefix . '_choose_input_type'] = array(
'#title' => check_plain($label),
'#type' => 'select',
'#options' => array('date' => t('Select a date'), 'relative' => ('Enter a relative date')),
'#attributes' => array('class' => array($prefix . '-choose-input-type')),
'#default_value' => !empty($relative_value) ? 'relative' : 'date',
);
$form[$prefix . '_group'][$prefix] = array(
'#title' => t('Select a date'),
'#type' => $type,
'#size' => 20,
'#default_value' => !empty($this->value[$prefix]) ? $this->value[$prefix] : $default_date,
'#date_format' => date_limit_format($format, $granularity),
'#date_label_position' => 'within',
'#date_year_range' => $this->options['year_range'],
'#process' => array($type . '_element_process'),
'#prefix' => '<div id="' . $id . '-wrapper"><div id="' . $id . '">',
'#suffix' => '</div></div>',
'#states' => array(
'visible' => array(
":input.{$prefix}-choose-input-type" => array('value' => 'date'),
),
),
);
$form[$prefix . '_group'][$relative_id] = array(
'#type' => 'textfield',
'#title' => check_plain($relative_label),
'#default_value' => $relative_value,
'#description' => t("Relative dates are computed when the view is displayed. Examples: now, now +1 day, 12AM today, Monday next week. <a href=\"@relative_format\">More examples of relative date formats in the PHP documentation</a>.", array('@relative_format' => 'http://www.php.net/manual/en/datetime.formats.relative.php')),
'#states' => array(
'visible' => array(
":input.{$prefix}-choose-input-type" => array('value' => 'relative'),
),
),
);
if ($which == 'all') {
$form[$prefix . '_group']['#pre_render'][] = 'ctools_dependent_pre_render';
$form[$prefix . '_group']['#dependency'] = array($source => $operator_values);
}
}
return $form;
}
/**
* Value validation.
*
* TODO add in more validation.
*
* We are setting an extra option using a value form
* because it makes more sense to set it there.
* That's not the normal method, so we have to manually
* transfer the selected value back to the option.
*/
function value_validate($form, &$form_state) {
$options = &$form_state['values']['options'];
if ($options['operator'] == 'between' || $options['operator'] == 'not between') {
if ($options['value']['min_group']['min_choose_input_type'] == 'relative') {
if (empty($options['value']['min_group']['default_date'])) {
form_set_error('options][value][min_group][default_date', t('Relative start date not specified.'));
}
else {
$this->options['default_date'] = $options['value']['min_group']['default_date'];
// NULL out the value field, user wanted the relative value to take hold.
$options['value']['min_group']['min'] = NULL;
}
}
// If an absolute date was used, be sure to wipe the relative date.
else {
$this->options['default_date'] = '';
}
if ($options['value']['max_group']['max_choose_input_type'] == 'relative') {
if (empty($options['value']['max_group']['default_to_date'])) {
form_set_error('options][value][max_group][default_to_date', t('Relative end date not specified.'));
}
else {
$this->options['default_to_date'] = $options['value']['max_group']['default_to_date'];
// NULL out the value field, user wanted the relative value to take hold.
$options['value']['max_group']['max'] = NULL;
}
}
// If an absolute date was used, be sure to wipe the relative date.
else {
$this->options['default_to_date'] = '';
}
}
elseif (in_array($options['operator'], array('<', '<=', '=', '!=', '>=', '>'))) {
if ($options['value']['value_group']['value_choose_input_type'] == 'relative') {
if (empty($options['value']['value_group']['default_date'])) {
form_set_error('options][value][value_group][default_date', t('Relative date not specified.'));
}
else {
$this->options['default_date'] = $options['value']['value_group']['default_date'];
// NULL out the value field, user wanted the relative value to take hold.
$options['value']['value_group']['value'] = NULL;
}
}
// If an absolute date was used, be sure to wipe the relative date.
else {
$this->options['default_date'] = '';
}
}
// Flatten the form structure for views, so the values can be saved.
foreach (array('value', 'min', 'max') as $key) {
$options['value'][$key] = $options['value'][$key . '_group'][$key];
}
}
/**
* Validate that the time values convert to something usable.
*/
function validate_valid_time(&$form, $operator, $value) {
// Override the core date filter validation.
// Our date widgets do their own validation.
}
// Update the summary values to provide
// meaningful information for each option.
function admin_summary() {
$parts = $this->date_handler->date_parts();
$widget_options = $this->widget_options();
// If the filter is exposed, display the granularity.
if ($this->options['exposed']) {
return t('<strong>Exposed</strong> @widget @format', array('@format' => $parts[$this->date_handler->granularity], '@widget' => $widget_options[$this->options['form_type']]));
}
// If not exposed, display the value.
$output = '';
if (in_array($this->operator, $this->operator_values(2))) {
$min = check_plain(!empty($this->options['default_date']) ? $this->options['default_date'] : $this->options['value']['min']);
$max = check_plain(!empty($this->options['default_to_date']) ? $this->options['default_to_date'] : $this->options['value']['max']);
$output .= t('@min and @max', array('@min' => $min, '@max' => $max));
}
else {
$output .= check_plain(!empty($this->options['default_date']) ? $this->options['default_date'] : $this->options['value']['value']);
}
return $output;
}
}

View File

@@ -0,0 +1,233 @@
<?php
/**
* @file
* Date pager.
* Works with a Date argument, the argument filters the view and the pager provides back/next navigation.
*
* USER NOTES:
*
* To use this, add a pager to a view, and choose the option to 'Page by date'.
* There are several settings:
* - The pager id: Set an id to be used as the identifier in the url for pager values, defaults to 'date'.
* - Pager position: Choose whether to display the date pager above, below, or both above and below the content.
* - Link format: Choose whether the pager links will be in the simple 'calendar/2011-12' format or the
* more complex 'calendar/?date=2011-12' pager format. The second one is more likely to work correctly
* if the pager is used in blocks and panels.
*
* The pager works in combination with a Date argument and it will use the date fields and granularity
* set in that argument to create its back/next links. If the view has no Date argument, the pager can
* do nothing. The argument can either be a 'Date' argument that lets you select one or more date fields
* in the argument, or the simple 'Content' argument for an individual date field. It must be an
* argument that uses the date argument handler.
*
* DEVELOPER NOTES
*
* The pager could technically create a query of its own rather than depending on the date argument to
* set the query, but it has only a limited set of tools to work with because it is a plugin, not a handler:
* it has no knowledge about relationships, it cannot use the ensure_my_table() function,
* plugins are not even invoked in pre_query(), so can't do anything there.
*
* My conclusion was that the date pager simply is not powerful enough to create its own queries for
* date fields, which require very complex queries. Instead, we can combine this with a date argument and
* let the argument create the query and let the pager just provide the back/next links. If there is no
* date argument, the pager will do nothing.
*
* There are still other problems. The pager is not even initialized until after all the handlers
* have created their queries, so it has no chance to alter values ahead of that. And the argument
* has no knowledge of the pager, so it can't check for pager values before the query is created.
*
* The solution used here is to let the argument create the original query. The pager query
* runs after that, so the pager checks to see if there is a pager value that needs to be used in the query.
* The date argument has identified the placeholders it used in the query. So if a change is needed,
* we can swap the pager value into the query created by the date argument and adjust the
* $view->date_info values set by the argument accordingly so the theme will pick up the new information.
*/
/**
* Example plugin to handle paging by month.
*/
class date_views_plugin_pager extends views_plugin_pager {
/**
* This kind of pager does not need to count the number of records.
*/
function use_count_query() {
return FALSE;
}
/**
* Because we don't know how many pages there are, we never believe there are more records.
*/
function has_more_records() {
return FALSE;
}
/*
* Tell Views what this pager's setting is.
*/
function summary_title() {
return t("Position: @position, format: @format.", array('@position' => $this->options['pager_position'], '@format' => $this->options['link_format']));
}
/**
* Tell Views what options this plugin can store.
*/
function option_definition() {
$options = parent::option_definition();
$options['date_id'] = array('default' => 'date');
$options['pager_position'] = array('default' => 'top');
$options['link_format'] = array('default' => 'pager');
$options['date_argument'] = array('default' => 'Unknown');
$options['granularity'] = array('default' => 'Unknown');
return $options;
}
/*
* Provide the form for setting options.
*/
function options_form(&$form, &$form_state) {
$form['markup']['#markup'] = t('This pager works together with a Date or Content date field contextual filter. If a Date filter has been added to the view, this pager will provide back/next paging to match the granularity of that filter (i.e. paging by year, month, week, or day). The filter must also be configured to use a DATE default value. If there is no Date contextual filter on this view, or if it has not been set to use a default date, the pager will not appear.');
$form['date_id'] = array(
'#title' => t('Date identifier'),
'#type' => 'textfield',
'#description' => t('The query identifier to use when fetching date data from in the URL. Note that if you have more than one display in the same view that uses the date pager (like a page and a block), the pager id must be different for each one or both will change when the pager value changes.'),
'#default_value' => $this->options['date_id'],
'#required' => TRUE,
);
$form['pager_position'] = array(
'#title' => t('Pager position'),
'#type' => 'select',
'#options' => array('bottom' => t('Bottom'), 'top' => t('Top'), 'both' => t('Both')),
'#description' => t('Where to place the date pager, on the top, bottom, or both top and bottom of the content.'),
'#default_value' => $this->options['pager_position'],
'#required' => TRUE,
);
$form['link_format'] = array(
'#title' => t('Link format'),
'#type' => 'select',
'#options' => array('pager' => t('Pager'), 'clean' => t('Clean URL')),
'#description' => t("The format for pager link urls. With the Pager format, the links look like 'calendar/?date=2020-05'. The Clean URL format links look like 'calendar/2020-05'. The Clean format links look nicer but the Pager format links are likely to work better if the calendar is used in blocks or panels."),
'#default_value' => $this->options['link_format'],
'#required' => TRUE,
);
$form['date_argument']['#type'] = 'hidden';
$form['date_argument']['#value'] = $this->options['date_argument'];
$form['granularity']['#type'] = 'hidden';
$form['granularity']['#value'] = $this->options['granularity'];
}
/**
* Transfer date information from the argument to the view so the pager theme can use it
* and update the date argument value to whatever is set by the pager.
*/
function query() {
// By fetching our data from the exposed input, it is possible to
// feed pager data through some method other than $_GET.
$input = $this->view->get_exposed_input();
$value = NULL;
if (!empty($input) && !empty($input[$this->options['date_id']])) {
$value = $input[$this->options['date_id']];
}
// Bring the argument information into the view so our theme can access it.
$i = 0;
foreach ($this->view->argument as $id => &$argument) {
if (date_views_handler_is_date($argument, 'argument')) {
// If the argument is empty, nothing to do. This could be from
// an argument that does not set a default value.
if (empty($argument->argument) || empty($argument->date_handler)) {
continue;
}
// Storing this information in the pager so it's available for summary info.
// The view argument information is not otherwise accessible to the pager.
// Not working right yet, tho.
$date_handler = $argument->date_handler;
$this->options['date_argument'] = $id;
$this->options['granularity'] = $argument->date_handler->granularity;
// Reset values set by argument if pager requires it.
if (!empty($value)) {
$argument->argument = $value;
$argument->date_range = $argument->date_handler->arg_range($value);
$argument->min_date = $argument->date_range[0];
$argument->max_date = $argument->date_range[1];
// $argument->is_default works correctly for normal arguments, but does not
// work correctly if we are swapping in a new value from the pager.
$argument->is_default = FALSE;
}
// The pager value might move us into a forbidden range, so test it.
if ($this->date_forbid($argument)) {
$this->view->build_info['fail'] = TRUE;
return;
}
if (empty($this->view->date_info)) $this->view->date_info = new stdClass();
$this->view->date_info->granularity = $argument->date_handler->granularity;
$format = $this->view->date_info->granularity == 'week' ? DATE_FORMAT_DATETIME : $argument->sql_format;
$this->view->date_info->placeholders = isset($argument->placeholders) ? $argument->placeholders : $argument->date_handler->placeholders;
$this->view->date_info->date_arg = $argument->argument;
$this->view->date_info->date_arg_pos = $i;
$this->view->date_info->year = date_format($argument->min_date, 'Y');
$this->view->date_info->month = date_format($argument->min_date, 'n');;
$this->view->date_info->day = date_format($argument->min_date, 'j');
$this->view->date_info->week = date_week(date_format($argument->min_date, DATE_FORMAT_DATE));
$this->view->date_info->date_range = $argument->date_range;
$this->view->date_info->min_date = $argument->min_date;
$this->view->date_info->max_date = $argument->max_date;
$this->view->date_info->limit = $argument->limit;
$this->view->date_info->url = $this->view->get_url();
$this->view->date_info->pager_id = $this->options['date_id'];
$this->view->date_info->date_pager_position = $this->options['pager_position'];
$this->view->date_info->date_pager_format = $this->options['link_format'];
}
$i++;
}
// Is this a view that needs to be altered based on a pager value?
// If there is pager input and the argument has set the placeholders,
// swap the pager value in for the placeholder set by the argument.
if (!empty($value) && !empty($this->view->date_info->placeholders)) {
$placeholders = $this->view->date_info->placeholders;
$count = count($placeholders);
foreach ($this->view->query->where as $group => $data) {
foreach ($data['conditions'] as $delta => $condition) {
if (array_key_exists('value', $condition) && is_array($condition['value'])) {
foreach ($condition['value'] as $placeholder => $placeholder_value) {
if (array_key_exists($placeholder, $placeholders)) {
// If we didn't get a match, this is a > $min < $max query that uses the view
// min and max dates as placeholders.
$date = ($count == 2) ? $this->view->date_info->min_date : $this->view->date_info->max_date;
$next_placeholder = array_shift($placeholders);
$this->view->query->where[$group]['conditions'][$delta]['value'][$placeholder] = $date->format($format);
$count--;
}
}
}
}
}
}
}
/**
* Add a callback to determine if we have moved outside the valid date range for this argument.
*/
function date_forbid($argument) {
// See if we're outside the allowed date range for our argument.
$limit = date_range_years($argument->options['year_range']);
if (date_format($argument->min_date, 'Y') < $limit[0] || date_format($argument->max_date, 'Y') > $limit[1]) {
return TRUE;
}
return FALSE;
}
function render($input) {
// This adds all of our template suggestions based upon the view name and display id.
$pager_theme = views_theme_functions('date_views_pager', $this->view, $this->display);
return theme($pager_theme, array('plugin' => $this, 'input' => $input));
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* @file
* Template to display the Views date filter form.
*
* Values available vary depending on the operator. The availability
* of date vs adjustment depending on the filter settings. It can
* be date-only, date and adjustment, or adjustment only.
*
* If the operator is anything but 'Is between' or 'Is not between',
* a single date and adjustment field is available.
*
* $date
* $adjustment
*
* If the operator is 'Is between' or 'Is not between',
* two date and adjustment fields are available.
*
* $mindate
* $minadjustment
* $maxdate
* $maxadjustment
*
* A description field is also available.
*
* $description
*/
?>
<div class="date-views-filter-wrapper">
<div class="container-inline-date date-clear">
<?php if (!empty($date) || !empty($adjustment)) : ?>
<div class="date-clear">
<div class="date-views-filter"><?php print $date; ?></div>
<div class="date-views-filter"><?php print $adjustment ?></div>
</div>
<?php endif; ?>
<?php if (!empty($mindate) || !empty($minadjustment)) : ?>
<div class="date-clear">
<div class="date-views-filter"><?php print $mindate; ?></div>
<div class="date-views-filter"><?php print $minadjustment; ?></div>
</div>
<?php endif; ?>
<?php if (!empty($maxdate) || !empty($maxadjustment)) : ?>
<div class="date-clear">
<div class="date-views-filter"><?php print $maxdate; ?></div>
<div class="date-views-filter"><?php print $maxadjustment; ?></div>
</div>
<?php endif; ?>
</div>
<div class="date-clear form-item"><div class="description">
<?php print $description; ?>
</div>
</div>
</div>

View File

@@ -0,0 +1,49 @@
<?php
/**
* @file
* Template file for the example display.
*
* Variables available:
*
* $plugin: The pager plugin object. This contains the view.
*
* $plugin->view
* The view object for this navigation.
*
* $nav_title
* The formatted title for this view. In the case of block
* views, it will be a link to the full view, otherwise it will
* be the formatted name of the year, month, day, or week.
*
* $prev_url
* $next_url
* Urls for the previous and next calendar pages. The links are
* composed in the template to make it easier to change the text,
* add images, etc.
*
* $prev_options
* $next_options
* Query strings and other options for the links that need to
* be used in the l() function, including rel=nofollow.
*/
?>
<?php if (!empty($pager_prefix)) print $pager_prefix; ?>
<div class="date-nav-wrapper clearfix<?php if (!empty($extra_classes)) print $extra_classes; ?>">
<div class="date-nav item-list">
<div class="date-heading">
<h3><?php print $nav_title ?></h3>
</div>
<ul class="pager">
<?php if (!empty($prev_url)) : ?>
<li class="date-prev">
<?php print l('&laquo;' . ($mini ? '' : ' ' . t('Prev', array(), array('context' => 'date_nav'))), $prev_url, $prev_options); ?>
&nbsp;</li>
<?php endif; ?>
<?php if (!empty($next_url)) : ?>
<li class="date-next">&nbsp;
<?php print l(($mini ? '' : t('Next', array(), array('context' => 'date_nav')) . ' ') . '&raquo;', $next_url, $next_options); ?>
</li>
<?php endif; ?>
</ul>
</div>
</div>

View File

@@ -0,0 +1,212 @@
<?php
/**
* @file
* Theme files for Date Pager.
*/
/**
* Jump in and move the pager.
*/
function date_views_preprocess_views_view(&$vars) {
$view = $vars['view'];
if (!empty($view->date_info) && !empty($view->date_info->date_pager_position)) {
switch ($view->date_info->date_pager_position) {
case 'top':
$vars['header'] .= $vars['pager'];
$vars['pager'] = '';
break;
case 'both':
$vars['header'] .= $vars['pager'];
break;
default:
// Already on the bottom.
}
}
}
/**
* Preprocess function for Date pager template.
*/
function template_preprocess_date_views_pager(&$vars) {
ctools_add_css('date_views', 'date_views');
$plugin = $vars['plugin'];
$input = $vars['input'];
$view = $plugin->view;
$vars['nav_title'] = '';
$vars['next_url'] = '';
$vars['prev_url'] = '';
if (empty($view->date_info) || empty($view->date_info->min_date)) {
return;
}
$date_info = $view->date_info;
// Make sure we have some sort of granularity.
$granularity = !empty($date_info->granularity) ? $date_info->granularity : 'month';
$pos = $date_info->date_arg_pos;
if (!empty($input)) {
$id = $plugin->options['date_id'];
if (array_key_exists($id, $input) && !empty($input[$id])) {
$view->args[$pos] = $input[$id];
}
}
$next_args = $view->args;
$prev_args = $view->args;
$min_date = $date_info->min_date;
$max_date = $date_info->max_date;
// Set up the pager link format. Setting the block identifier
// will force pager style links.
if ((isset($date_info->date_pager_format) && $date_info->date_pager_format != 'clean') || !empty($date_info->mini)) {
if (empty($date_info->block_identifier)) {
$date_info->block_identifier = $date_info->pager_id;
}
}
if (empty($date_info->hide_nav)) {
$prev_date = clone($min_date);
date_modify($prev_date, '-1 ' . $granularity);
$next_date = clone($min_date);
date_modify($next_date, '+1 ' . $granularity);
$format = array('year' => 'Y', 'month' => 'Y-m', 'day' => 'Y-m-d');
switch ($granularity) {
case 'week':
$next_week = date_week(date_format($next_date, 'Y-m-d'));
$prev_week = date_week(date_format($prev_date, 'Y-m-d'));
$next_arg = date_format($next_date, 'Y-\W') . date_pad($next_week);
$prev_arg = date_format($prev_date, 'Y-\W') . date_pad($prev_week);
break;
default:
$next_arg = date_format($next_date, $format[$granularity]);
$prev_arg = date_format($prev_date, $format[$granularity]);
}
$next_path = str_replace($date_info->date_arg, $next_arg, $date_info->url);
$prev_path = str_replace($date_info->date_arg, $prev_arg, $date_info->url);
$next_args[$pos] = $next_arg;
$prev_args[$pos] = $prev_arg;
$vars['next_url'] = date_pager_url($view, NULL, $next_arg);
$vars['prev_url'] = date_pager_url($view, NULL, $prev_arg);
$vars['next_options'] = $vars['prev_options'] = array();
}
else {
$next_path = '';
$prev_path = '';
$vars['next_url'] = '';
$vars['prev_url'] = '';
$vars['next_options'] = $vars['prev_options'] = array();
}
// Check whether navigation links would point to
// a date outside the allowed range.
if (!empty($next_date) && !empty($vars['next_url']) && date_format($next_date, 'Y') > $date_info->limit[1]) {
$vars['next_url'] = '';
}
if (!empty($prev_date) && !empty($vars['prev_url']) && date_format($prev_date, 'Y') < $date_info->limit[0]) {
$vars['prev_url'] = '';
}
$vars['prev_options'] += array('attributes' => array());
$vars['next_options'] += array('attributes' => array());
$prev_title = '';
$next_title = '';
// Build next/prev link titles.
switch ($granularity) {
case 'year':
$prev_title = t('Navigate to previous year');
$next_title = t('Navigate to next year');
break;
case 'month':
$prev_title = t('Navigate to previous month');
$next_title = t('Navigate to next month');
break;
case 'week':
$prev_title = t('Navigate to previous week');
$next_title = t('Navigate to next week');
break;
case 'day':
$prev_title = t('Navigate to previous day');
$next_title = t('Navigate to next day');
break;
}
$vars['prev_options']['attributes'] += array('title' => $prev_title);
$vars['next_options']['attributes'] += array('title' => $next_title);
// Add nofollow for next/prev links.
$vars['prev_options']['attributes'] += array('rel' => 'nofollow');
$vars['next_options']['attributes'] += array('rel' => 'nofollow');
// Need this so we can use '&laquo;' or images in the links.
$vars['prev_options'] += array('html' => TRUE);
$vars['next_options'] += array('html' => TRUE);
$link = FALSE;
// Month navigation titles are used as links in the block view.
if (!empty($date_info->mini) && $granularity == 'month') {
$link = TRUE;
}
$params = array(
'granularity' => $granularity,
'view' => $view,
'link' => $link,
);
$nav_title = theme('date_nav_title', $params);
$vars['nav_title'] = $nav_title;
$vars['mini'] = !empty($date_info->mini);
}
/**
* Theme the calendar title
*/
function theme_date_nav_title($params) {
$granularity = $params['granularity'];
$view = $params['view'];
$date_info = $view->date_info;
$link = !empty($params['link']) ? $params['link'] : FALSE;
$format = !empty($params['format']) ? $params['format'] : NULL;
switch ($granularity) {
case 'year':
$title = $date_info->year;
$date_arg = $date_info->year;
break;
case 'month':
$format = !empty($format) ? $format : (empty($date_info->mini) ? 'F Y' : 'F');
$title = date_format_date($date_info->min_date, 'custom', $format);
$date_arg = $date_info->year . '-' . date_pad($date_info->month);
break;
case 'day':
$format = !empty($format) ? $format : (empty($date_info->mini) ? 'l, F j, Y' : 'l, F j');
$title = date_format_date($date_info->min_date, 'custom', $format);
$date_arg = $date_info->year . '-' . date_pad($date_info->month) . '-' . date_pad($date_info->day);
break;
case 'week':
$format = !empty($format) ? $format : (empty($date_info->mini) ? 'F j, Y' : 'F j');
$title = t('Week of @date', array('@date' => date_format_date($date_info->min_date, 'custom', $format)));
$date_arg = $date_info->year . '-W' . date_pad($date_info->week);
break;
}
if (!empty($date_info->mini) || $link) {
// Month navigation titles are used as links in the mini view.
$attributes = array('title' => t('View full page month'));
$url = date_pager_url($view, $granularity, $date_arg, TRUE);
return l($title, $url, array('attributes' => $attributes));
}
else {
return $title;
}
}
/**
* Preprocessor for Date Views filter form.
*/
function template_preprocess_date_views_filter_form(&$vars) {
$form = $vars['form'];
$vars['date'] = drupal_render($form['valuedate']);
$vars['mindate'] = drupal_render($form['mindate']);
$vars['maxdate'] = drupal_render($form['maxdate']);
$vars['adjustment'] = drupal_render($form['valueadjustment']);
$vars['minadjustment'] = drupal_render($form['minadjustment']);
$vars['maxadjustment'] = drupal_render($form['maxadjustment']);
$vars['description'] = drupal_render($form['description']) . drupal_render($form);
}

View File

@@ -0,0 +1,16 @@
This folder includes files that can be used to test imports of date information.
To test them, set up FeedAPI and the Feed Element Mapper with Parser iCal
or Parser CVS and import these files into a date field.
- rrule.ics:
Creates repeating dates using a wide variety of RRULEs.
- Yahoo.csv:
This file uses the csv export format from Yahoo Calendar, similar to the
format created by Outlook's csv export. The sample contains both timed
and untimed 'All day' events.
- USHolidays.ics:
An ical export of US Holidays in the 'All day' format used by
Microsoft and Apple (where the Start date is the date of the event
and the End date is the following day).

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
"Subject","Start Date","Start Time","End Date","End Time","All day event","Description"
"New Year's Day","1/1/2009","","","","true",""
"Valentine's Day","2/14/2009","","","","true",""
"St. Patrick's Day","3/17/2009","","","","true",""
"Memorial Day","5/31/2009","","","","true",""
"Independence Day","7/4/2009","","","","true",""
"Labor Day","09/07/2009","","","","true",""
"Halloween","10/31/2009","","","","true",""
"Veteran's Day","11/11/2009","","","","true",""
"Thanksgiving Day","11/26/2009","","","","true",""
"Christmas Day","12/25/2009","","","","true",""
"Do It With Drupal","12/10/2008","08:00 AM","12/10/2008","05:00 PM","false",""
"DrupalCon","03/04/2009","08:00 AM","03/04/2009","05:00 PM","false",""
"DrupalCon","03/05/2009","08:00 AM","03/05/2009","05:00 PM","false",""
"DrupalCon","03/06/2009","08:00 AM","03/06/2009","05:00 PM","false",""
"DrupalCon","03/07/2009","08:00 AM","03/07/2009","05:00 PM","false",""
1 Subject Start Date Start Time End Date End Time All day event Description
2 New Year's Day 1/1/2009 true
3 Valentine's Day 2/14/2009 true
4 St. Patrick's Day 3/17/2009 true
5 Memorial Day 5/31/2009 true
6 Independence Day 7/4/2009 true
7 Labor Day 09/07/2009 true
8 Halloween 10/31/2009 true
9 Veteran's Day 11/11/2009 true
10 Thanksgiving Day 11/26/2009 true
11 Christmas Day 12/25/2009 true
12 Do It With Drupal 12/10/2008 08:00 AM 12/10/2008 05:00 PM false
13 DrupalCon 03/04/2009 08:00 AM 03/04/2009 05:00 PM false
14 DrupalCon 03/05/2009 08:00 AM 03/05/2009 05:00 PM false
15 DrupalCon 03/06/2009 08:00 AM 03/06/2009 05:00 PM false
16 DrupalCon 03/07/2009 08:00 AM 03/07/2009 05:00 PM false

View File

@@ -0,0 +1,168 @@
<?php
/**
* @file
* Test date UI.
*/
class DateUITestCase extends DrupalWebTestCase {
protected $privileged_user;
/**
* @todo.
*/
public static function getInfo() {
return array(
'name' => 'Field UI',
'description' => 'Test creation of various date fields and widgets using Field UI.',
'group' => 'Date',
);
}
/**
* @todo.
*/
public function setUp() {
// Load the date_api module.
parent::setUp('field', 'field_ui', 'date_api', 'date', 'date_popup', 'date_tools');
// Create and log in our privileged user.
$this->privileged_user = $this->drupalCreateUser(
array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools')
);
$this->drupalLogin($this->privileged_user);
variable_set('date_format_long', 'D, m/d/Y - H:i');
}
/**
* @todo.
*/
public function testFieldUI() {
$edit = array();
$edit['name'] = 'Story';
$edit['type'] = 'story';
$this->drupalPost('admin/structure/types/add', $edit, t('Save content type'));
$this->assertText('The content type Story has been added.', 'Content type added.');
// Creates select list field stored as a date with default settings.
$this->createDateField($type = 'date', $widget = 'date_select');
$edit = array();
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->dateForm($options = 'select');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a date field using the date_select widget.');
$this->deleteDateField();
// Creates text field stored as a date with default settings.
$this->createDateField($type = 'date', $widget = 'date_text');
$edit = array();
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->dateForm($options = 'text');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a date field using the date_text widget.');
$this->deleteDateField();
// Creates popup field stored as a date with default settings.
$this->createDateField($type = 'date', $widget = 'date_popup');
$edit = array();
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->dateForm($options = 'popup');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a date field using the date_popup widget.');
$this->deleteDateField();
// Creates select list field stored as a datestamp with default settings.
$this->createDateField($type = 'datestamp', $widget = 'date_select');
$edit = array();
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->dateForm($options = 'select');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datestamp field using the date_select widget.');
$this->deleteDateField();
// Creates text field stored as a datestamp with default settings.
$this->createDateField($type = 'datestamp', $widget = 'date_text');
$edit = array();
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->dateForm($options = 'text');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datestamp field using the date_text widget.');
$this->deleteDateField();
// Creates popup field stored as a datestamp with default settings.
$this->createDateField($type = 'datestamp', $widget = 'date_popup');
$edit = array();
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->dateForm($options = 'popup');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datestamp field using the date_popup widget.');
$this->deleteDateField();
// Creates select list field stored as a datetime with default settings.
$this->createDateField($type = 'datetime', $widget = 'date_select');
$edit = array();
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->dateForm($options = 'select');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datetime field using the date_select widget.');
$this->deleteDateField();
// Creates text field stored as a datetime with default settings.
$this->createDateField($type = 'datetime', $widget = 'date_text');
$edit = array();
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->dateForm($options = 'text');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datetime field using the date_text widget.');
$this->deleteDateField();
// Creates popup field stored as a datetime with default settings.
$this->createDateField($type = 'datetime', $widget = 'date_popup');
$edit = array();
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->dateForm($options = 'popup');
$this->assertText('Thu, 10/07/2010 - 10:30', 'Found the correct date for a datetime field using the date_popup widget.');
$this->deleteDateField();
// Test timezone handling validation on the field settings form.
$this->createDateField($type = 'date', $widget = 'date_select');
$edit = array('field[settings][granularity][hour]' => FALSE);
$this->drupalPost(NULL, $edit, t('Save field settings'));
$this->assertText("Dates without hours granularity must not use any timezone handling.", "Dates without hours granularity required to use timezone handling of 'none.'");
$this->deleteDateField();
}
/**
* @todo.
*/
function dateForm($options) {
// Tests that date field functions properly.
$edit = array();
$edit['title'] = $this->randomName(8);
$edit['body[und][0][value]'] = $this->randomName(16);
if ($options == 'select') {
$edit['field_test[und][0][value][year]'] = '2010';
$edit['field_test[und][0][value][month]'] = '10';
$edit['field_test[und][0][value][day]'] = '7';
$edit['field_test[und][0][value][hour]'] = '10';
$edit['field_test[und][0][value][minute]'] = '30';
}
elseif ($options == 'text') {
$edit['field_test[und][0][value][date]'] = '10/07/2010 - 10:30';
}
elseif ($options == 'popup') {
$edit['field_test[und][0][value][date]'] = '10/07/2010';
$edit['field_test[und][0][value][time]'] = '10:30';
}
$this->drupalPost('node/add/story', $edit, t('Save'));
$this->assertText($edit['body[und][0][value]'], 'Test node has been created');
}
/**
* @todo.
*/
function createDateField($type, $widget) {
$edit = array();
$edit['fields[_add_new_field][label]'] = 'Test';
$edit['fields[_add_new_field][field_name]'] = 'test';
$edit['fields[_add_new_field][weight]'] = '-4';
$edit['fields[_add_new_field][type]'] = $type;
$edit['fields[_add_new_field][widget_type]'] = $widget;
$this->drupalPost('admin/structure/types/manage/story/fields', $edit, t('Save'));
}
/**
* @todo.
*/
function deleteDateField() {
$this->drupalGet('admin/structure/types/manage/story/fields');
$this->clickLink('delete');
$this->drupalPost(NULL, NULL, t('Delete'));
$this->assertText('The field Test has been deleted from the Story content type.', 'Removed date field.');
}
}

View File

@@ -0,0 +1,409 @@
<?php
/**
* @file
* Test Date API functions
*/
class DateAPITestCase extends DrupalWebTestCase {
/**
* @todo.
*/
public static function getInfo() {
return array(
'name' => t('Date API'),
'description' => t('Test Date API functions.') ,
'group' => t('Date'),
);
}
/**
* @todo.
*/
public function setUp() {
// Load the date_api module.
parent::setUp('date_api');
variable_set('date_api_use_iso8601', FALSE);
variable_set('date_first_day', 1);
}
/**
* @todo.
*/
public function testDateAPI() {
// Test date_format_date().
$formatters = array(
'a',
'A',
'B',
'c',
'd',
'D',
'e',
'F',
'g',
'G',
'h',
'H',
'i',
'I',
'j',
'l',
'L',
'm',
'M',
'n',
'N',
'o',
'O',
'P',
'r',
'R',
's',
'S',
't',
'T',
'u',
'U',
'w',
'W',
'y',
'Y',
'z',
'Z',
);
foreach ($formatters as $formatter) {
$date_api_format = date_format_date(date_now(), 'custom', $formatter);
$php_format = date_format(date_now(), $formatter);
$this->assertEqual($date_api_format, $php_format, 'Test that the "' . $formatter . '" formatter is formatted correctly by date_format_date()');
}
// Test the order of the weeks days for a calendar that starts on Monday and
// one that starts on Sunday.
variable_set('date_first_day', 1);
$expected = array(0 => t('Mon'), 1 => t('Tue'), 2 => t('Wed'), 3 => t('Thu'), 4 => t('Fri'), 5 => t('Sat'), 6 => t('Sun'));
$days = date_week_days_ordered(date_week_days_abbr(1));
$this->assertEqual($expected, $days, 'Test that date_week_days_ordered() array starts on Monday when the site first day is on Monday.');
variable_set('date_first_day', 0);
$expected = array(0 => t('Sun'), 1 => t('Mon'), 2 => t('Tue'), 3 => t('Wed'), 4 => t('Thu'), 5 => t('Fri'), 6 => t('Sat'));
$days = date_week_days_ordered(date_week_days_abbr(1));
$this->assertEqual($expected, $days, 'Test that date_week_days_ordered() array starts on Sunday when the site first day is on Sunday.');
// Test days in February for a leap year and a non-leap year.
$expected = 28;
$value = date_days_in_month(2005, 2);
$this->assertEqual($expected, $value, "Test date_days_in_month(2, 2005): should be $expected, found $value.");
$expected = 29;
$value = date_days_in_month(2004, 2);
$this->assertEqual($expected, $value, "Test date_days_in_month(2, 2004): should be $expected, found $value.");
// Test days in year for a leap year and a non-leap year.
$expected = 365;
$value = date_days_in_year('2005-06-01 00:00:00');
$this->assertEqual($expected, $value, "Test date_days_in_year(2005-06-01): should be $expected, found $value.");
$expected = 366;
$value = date_days_in_year('2004-06-01 00:00:00');
$this->assertEqual($expected, $value, "Test date_days_in_year(2004-06-01): should be $expected, found $value.");
// Test ISO weeks for a leap year and a non-leap year.
$expected = 52;
$value = date_iso_weeks_in_year('2008-06-01 00:00:00');
$this->assertEqual($expected, $value, "Test date_iso_weeks_in_year(2008-06-01): should be $expected, found $value.");
$expected = 53;
$value = date_iso_weeks_in_year('2009-06-01 00:00:00');
$this->assertEqual($expected, $value, "Test date_iso_weeks_in_year(2009-06-01): should be $expected, found $value.");
// Test day of week for March 1, the day after leap day.
$expected = 6;
$value = date_day_of_week('2008-03-01 00:00:00');
$this->assertEqual($expected, $value, "Test date_day_of_week(2008-03-01): should be $expected, found $value.");
$expected = 0;
$value = date_day_of_week('2009-03-01 00:00:00');
$this->assertEqual($expected, $value, "Test date_day_of_week(2009-03-01): should be $expected, found $value.");
// Test day of week name for March 1, the day after leap day.
$expected = 'Sat';
$value = date_day_of_week_name('2008-03-01 00:00:00');
$this->assertEqual($expected, $value, "Test date_day_of_week_name(2008-03-01): should be $expected, found $value.");
$expected = 'Sun';
$value = date_day_of_week_name('2009-03-01 00:00:00');
$this->assertEqual($expected, $value, "Test date_day_of_week_name(2009-03-01): should be $expected, found $value.");
// Test week range with calendar weeks.
variable_set('date_first_day', 0);
variable_set('date_api_use_iso8601', FALSE);
$expected = '2008-01-27 to 2008-02-03';
$result = date_week_range(5, 2008);
$value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE);
$this->assertEqual($expected, $value, "Test calendar date_week_range(5, 2008): should be $expected, found $value.");
$expected = '2009-01-25 to 2009-02-01';
$result = date_week_range(5, 2009);
$value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE);
$this->assertEqual($expected, $value, "Test calendar date_week_range(5, 2009): should be $expected, found $value.");
// And now with ISO weeks.
variable_set('date_first_day', 1);
variable_set('date_api_use_iso8601', TRUE);
$expected = '2008-01-28 to 2008-02-04';
$result = date_week_range(5, 2008);
$value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE);
$this->assertEqual($expected, $value, "Test ISO date_week_range(5, 2008): should be $expected, found $value.");
$expected = '2009-01-26 to 2009-02-02';
$result = date_week_range(5, 2009);
$value = $result[0]->format(DATE_FORMAT_DATE) . ' to ' . $result[1]->format(DATE_FORMAT_DATE);
$this->assertEqual($expected, $value, "Test ISO date_week_range(5, 2009): should be $expected, found $value.");
variable_set('date_api_use_iso8601', FALSE);
// Find calendar week for a date.
variable_set('date_first_day', 0);
$expected = '09';
$value = date_week('2008-03-01');
$this->assertEqual($expected, $value, "Test date_week(2008-03-01): should be $expected, found $value.");
$expected = '10';
$value = date_week('2009-03-01');
$this->assertEqual($expected, $value, "Test date_week(2009-03-01): should be $expected, found $value.");
// Create date object from datetime string.
$input = '2009-03-07 10:30';
$timezone = 'America/Chicago';
$date = new dateObject($input, $timezone);
$value = $date->format('c');
$expected = '2009-03-07T10:30:00-06:00';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone): should be $expected, found $value.");
// Same during daylight savings time.
$input = '2009-06-07 10:30';
$timezone = 'America/Chicago';
$date = new dateObject($input, $timezone);
$value = $date->format('c');
$expected = '2009-06-07T10:30:00-05:00';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone): should be $expected, found $value.");
// Create date object from date string.
$input = '2009-03-07';
$timezone = 'America/Chicago';
$date = new dateObject($input, $timezone);
$value = $date->format('c');
$expected = '2009-03-07T00:00:00-06:00';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone): should be $expected, found $value.");
// Same during daylight savings time.
$input = '2009-06-07';
$timezone = 'America/Chicago';
$date = new dateObject($input, $timezone);
$value = $date->format('c');
$expected = '2009-06-07T00:00:00-05:00';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone): should be $expected, found $value.");
// Create date object from date array, date only.
$input = array('year' => 2010, 'month' => 2, 'day' => 28);
$timezone = 'America/Chicago';
$date = new dateObject($input, $timezone);
$value = $date->format('c');
$expected = '2010-02-28T00:00:00-06:00';
$this->assertEqual($expected, $value, "Test new dateObject(array('year' => 2010, 'month' => 2, 'day' => 28), $timezone): should be $expected, found $value.");
// Create date object from date array with hour.
$input = array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10);
$timezone = 'America/Chicago';
$date = new dateObject($input, $timezone);
$value = $date->format('c');
$expected = '2010-02-28T10:00:00-06:00';
$this->assertEqual($expected, $value, "Test new dateObject(array('year' => 2010, 'month' => 2, 'day' => 28, 'hour' => 10), $timezone): should be $expected, found $value.");
// 0 = January 1, 1970 00:00:00 (UTC);
// 1000000000 = September 9, 2001 01:46:40 (UTC);
// Create date object from unix timestamp and convert it to a local date.
$input = 0;
$timezone = 'UTC';
$date = new dateObject($input, $timezone);
$value = $date->format('c');
$expected = '1970-01-01T00:00:00+00:00';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone): should be $expected, found $value.");
$expected = 'UTC';
$value = $date->getTimeZone()->getName();
$this->assertEqual($expected, $value, "The current timezone is $value: should be $expected.");
$expected = 0;
$value = $date->getOffset();
$this->assertEqual($expected, $value, "The current offset is $value: should be $expected.");
$timezone = 'America/Los_Angeles';
$date->setTimezone(new DateTimeZone($timezone));
$value = $date->format('c');
$expected = '1969-12-31T16:00:00-08:00';
$this->assertEqual($expected, $value, "Test \$date->setTimezone(new DateTimeZone($timezone)): should be $expected, found $value.");
$expected = 'America/Los_Angeles';
$value = $date->getTimeZone()->getName();
$this->assertEqual($expected, $value, "The current timezone should be $expected, found $value.");
$expected = '-28800';
$value = $date->getOffset();
$this->assertEqual($expected, $value, "The current offset should be $expected, found $value.");
// Convert the local version of a timestamp to UTC.
$input = 0;
$timezone = 'America/Los_Angeles';
$date = new dateObject($input, $timezone);
$offset = $date->getOffset();
$value = $date->format('c');
$expected = '1969-12-31T16:00:00-08:00';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone): should be $expected, found $value.");
$expected = 'America/Los_Angeles';
$value = $date->getTimeZone()->getName();
$this->assertEqual($expected, $value, "The current timezone should be $expected, found $value.");
$expected = '-28800';
$value = $date->getOffset();
$this->assertEqual($expected, $value, "The current offset should be $expected, found $value.");
$timezone = 'UTC';
$date->setTimezone(new DateTimeZone($timezone));
$value = $date->format('c');
$expected = '1970-01-01T00:00:00+00:00';
$this->assertEqual($expected, $value, "Test \$date->setTimezone(new DateTimeZone($timezone)): should be $expected, found $value.");
$expected = 'UTC';
$value = $date->getTimeZone()->getName();
$this->assertEqual($expected, $value, "The current timezone should be $expected, found $value.");
$expected = '0';
$value = $date->getOffset();
$this->assertEqual($expected, $value, "The current offset should be $expected, found $value.");
// Create date object from datetime string and convert it to a local date.
$input = '1970-01-01 00:00:00';
$timezone = 'UTC';
$date = new dateObject($input, $timezone);
$value = $date->format('c');
$expected = '1970-01-01T00:00:00+00:00';
$this->assertEqual($expected, $value, "Test new dateObject('$input', '$timezone'): should be $expected, found $value.");
$expected = 'UTC';
$value = $date->getTimeZone()->getName();
$this->assertEqual($expected, $value, "The current timezone is $value: should be $expected.");
$expected = 0;
$value = $date->getOffset();
$this->assertEqual($expected, $value, "The current offset is $value: should be $expected.");
$timezone = 'America/Los_Angeles';
$date->setTimezone(new DateTimeZone($timezone));
$value = $date->format('c');
$expected = '1969-12-31T16:00:00-08:00';
$this->assertEqual($expected, $value, "Test \$date->setTimezone(new DateTimeZone($timezone)): should be $expected, found $value.");
$expected = 'America/Los_Angeles';
$value = $date->getTimeZone()->getName();
$this->assertEqual($expected, $value, "The current timezone should be $expected, found $value.");
$expected = '-28800';
$value = $date->getOffset();
$this->assertEqual($expected, $value, "The current offset should be $expected, found $value.");
// Convert the local version of a datetime string to UTC.
$input = '1969-12-31 16:00:00';
$timezone = 'America/Los_Angeles';
$date = new dateObject($input, $timezone);
$offset = $date->getOffset();
$value = $date->format('c');
$expected = '1969-12-31T16:00:00-08:00';
$this->assertEqual($expected, $value, "Test new dateObject('$input', '$timezone'): should be $expected, found $value.");
$expected = 'America/Los_Angeles';
$value = $date->getTimeZone()->getName();
$this->assertEqual($expected, $value, "The current timezone should be $expected, found $value.");
$expected = '-28800';
$value = $date->getOffset();
$this->assertEqual($expected, $value, "The current offset should be $expected, found $value.");
$timezone = 'UTC';
$date->setTimezone(new DateTimeZone($timezone));
$value = $date->format('c');
$expected = '1970-01-01T00:00:00+00:00';
$this->assertEqual($expected, $value, "Test \$date->setTimezone(new DateTimeZone($timezone)): should be $expected, found $value.");
$expected = 'UTC';
$value = $date->getTimeZone()->getName();
$this->assertEqual($expected, $value, "The current timezone should be $expected, found $value.");
$expected = '0';
$value = $date->getOffset();
$this->assertEqual($expected, $value, "The current offset should be $expected, found $value.");
// Create year-only date.
$input = '2009';
$timezone = NULL;
$format = 'Y';
$date = new dateObject($input, $timezone, $format);
$value = $date->format('Y');
$expected = '2009';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone, $format): should be $expected, found $value.");
// Create month and year-only date.
$input = '2009-10';
$timezone = NULL;
$format = 'Y-m';
$date = new dateObject($input, $timezone, $format);
$value = $date->format('Y-m');
$expected = '2009-10';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone, $format): should be $expected, found $value.");
// Create time-only date.
$input = '0000-00-00T10:30:00';
$timezone = NULL;
$format = 'Y-m-d\TH:i:s';
$date = new dateObject($input, $timezone, $format);
$value = $date->format('H:i:s');
$expected = '10:30:00';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone, $format): should be $expected, found $value.");
// Create time-only date.
$input = '10:30:00';
$timezone = NULL;
$format = 'H:i:s';
$date = new dateObject($input, $timezone, $format);
$value = $date->format('H:i:s');
$expected = '10:30:00';
$this->assertEqual($expected, $value, "Test new dateObject($input, $timezone, $format): should be $expected, found $value.");
// Test date ranges.
$valid = array(
'-20:+20',
'-1:+0',
'-10:-5',
'2000:2020',
'-10:2010',
'1980:-10',
'1920:+20',
);
$invalid = array(
'abc',
'abc:+20',
'1920:+20a',
'+-20:+-30',
'12:12',
'0:+20',
'-20:0',
);
foreach ($valid as $range) {
$this->assertTrue(date_range_valid($range), "$range recognized as a valid date range.");
}
foreach ($invalid as $range) {
$this->assertFalse(date_range_valid($range), "$range recognized as an invalid date range.");
}
// Test for invalid month names when we are using a short version of the month
$input = '23 abc 2012';
$timezone = NULL;
$format = 'd M Y';
$date = new dateObject($input, $timezone, $format);
$this->assertNotEqual(count($date->errors), 0, '23 abc 2012 should be an invalid date');
}
/**
* @todo.
*/
public function tearDown() {
variable_del('date_first_day');
variable_del('date_api_use_iso8601');
parent::tearDown();
}
}

View File

@@ -0,0 +1,264 @@
<?php
/**
* @file
* Basic functions for Date tests.
*/
abstract class DateFieldBasic extends DrupalWebTestCase {
protected $privileged_user;
/**
* @todo.
*/
protected function setUp() {
// Load the date_api module.
parent::setUp('field', 'field_ui', 'date_api', 'date', 'date_popup', 'date_tools');
// Create and log in our privileged user.
$this->privileged_user = $this->drupalCreateUser(
array('administer content types', 'administer nodes', 'bypass node access', 'administer date tools')
);
$this->drupalLogin($this->privileged_user);
variable_set('date_popup_timepicker', 'none');
module_load_include('inc', 'node', 'content_types');
module_load_include('inc', 'node', 'node.pages');
module_load_include('inc', 'field', 'field.crud');
module_load_include('inc', 'date', 'date_admin');
$edit = array();
$edit['name'] = 'Story';
$edit['type'] = 'story';
$this->drupalPost('admin/structure/types/add', $edit, t('Save content type'));
$this->assertText('The content type Story has been added.', 'Content type added.');
}
/**
* Creates a date field from an array of settings values.
*
* All values have defaults, only need to specify values that need to be
* different.
*/
protected function createDateField($values = array()) {
extract($values);
$field_name = !empty($field_name) ? $field_name : 'field_test';
$entity_type = !empty($entity_type) ? $entity_type : 'node';
$bundle = !empty($bundle) ? $bundle : 'story';
$label = !empty($label) ? $label : 'Test';
$field_type = !empty($field_type) ? $field_type : 'datetime';
$repeat = !empty($repeat) ? $repeat : 0;
$todate = !empty($todate) ? $todate : 'optional';
$widget_type = !empty($widget_type) ? $widget_type : 'date_select';
$tz_handling = !empty($tz_handing) ? $tz_handling : 'site';
$granularity = !empty($granularity) ? $granularity : array('year', 'month', 'day', 'hour', 'minute');
$year_range = !empty($year_range) ? $year_range : '2010:+1';
$input_format = !empty($input_format) ? $input_format : date_default_format($widget_type);
$input_format_custom = !empty($input_format_custom) ? $input_format_custom : '';
$text_parts = !empty($text_parts) ? $text_parts : array();
$increment = !empty($increment) ? $increment : 15;
$default_value = !empty($default_value) ? $default_value : 'now';
$default_value2 = !empty($default_value2) ? $default_value2 : 'blank';
$default_format = !empty($default_format) ? $default_format : 'long';
$cache_enabled = !empty($cache_enabled);
$cache_count = !empty($cache_count) ? $cache_count : 4;
$field = array(
'field_name' => $field_name,
'type' => $field_type,
'cardinality' => !empty($repeat) ? FIELD_CARDINALITY_UNLIMITED : 1,
'settings' => array(
'granularity' => $granularity,
'tz_handling' => $tz_handling,
'timezone_db' => date_get_timezone_db($tz_handling),
'repeat' => $repeat,
'todate' => $todate,
'cache_enabled' => $cache_enabled,
'cache_count' => $cache_count,
),
);
$instance = array(
'entity_type' => $entity_type,
'field_name' => $field_name,
'label' => $label,
'bundle' => $bundle,
// Move the date right below the title.
'weight' => -4,
'widget' => array(
'type' => $widget_type,
// Increment for minutes and seconds, can be 1, 5, 10, 15, or 30.
'settings' => array(
'increment' => $increment,
// The number of years to go back and forward in drop-down year
// selectors.
'year_range' => $year_range,
'input_format' => $input_format,
'input_format_custom' => $input_format_custom,
'text_parts' => $text_parts,
'label_position' => 'above',
'repeat_collapsed' => 0,
),
'weight' => -4,
),
'settings' => array(
'default_value' => $default_value,
'default_value2' => $default_value2,
),
);
$instance['display'] = array(
'default' => array(
'label' => 'above',
'type' => 'date_default',
'settings' => array(
'format_type' => $default_format,
'show_repeat_rule' => 'show',
'multiple_number' => '',
'multiple_from' => '',
'multiple_to' => '',
'fromto' => 'both',
),
'module' => 'date',
'weight' => 0 ,
),
'teaser' => array(
'label' => 'above',
'type' => 'date_default',
'weight' => 0,
'settings' => array(
'format_type' => $default_format,
'show_repeat_rule' => 'show',
'multiple_number' => '',
'multiple_from' => '',
'multiple_to' => '',
'fromto' => 'both',
),
'module' => 'date',
),
);
$field = field_create_field($field);
$instance = field_create_instance($instance);
field_info_cache_clear(TRUE);
field_cache_clear(TRUE);
// Look at how the field got configured.
$this->drupalGet("admin/structure/types/manage/$bundle/fields/$field_name");
$this->drupalGet("admin/structure/types/manage/$bundle/display");
}
/**
* @todo.
*/
protected function deleteDateField($label, $bundle = 'story', $bundle_name = 'Story') {
$this->drupalGet("admin/structure/types/manage/$bundle/fields");
$this->clickLink('delete');
$this->drupalPost(NULL, NULL, t('Delete'));
$this->assertText("The field $label has been deleted from the $bundle_name content type.", 'Removed date field.');
}
}
class DateFieldTestCase extends DateFieldBasic {
/**
* @todo.
*/
public static function getInfo() {
return array(
'name' => 'Date Field',
'description' => 'Test date field settings and Start/End date interaction.',
'group' => 'Date',
);
}
/**
* @todo.
*/
public function testField() {
// Create a date fields with simple values.
foreach (array('date', 'datestamp', 'datetime') as $field_type) {
foreach (array('date_select', 'date_popup', 'date_text') as $widget_type) {
$field_name = "field_test_$widget_type";
$label = 'Test';
$options = array(
'label' => $label,
'widget_type' => $widget_type,
'field_name' => $field_name,
'field_type' => $field_type,
'input_format' => 'm/d/Y - H:i',
);
$this->createDateField($options);
$this->dateForm($field_name, $field_type, $widget_type);
$this->deleteDateField($label);
}
}
}
/**
* @todo.
*/
public function dateForm($field_name, $field_type, $widget_type, $todate = TRUE) {
// Tests that date field functions properly.
$edit = array();
$edit['title'] = $this->randomName(8);
if ($widget_type == 'date_select') {
$edit[$field_name . '[und][0][value][year]'] = '2010';
$edit[$field_name . '[und][0][value][month]'] = '10';
$edit[$field_name . '[und][0][value][day]'] = '7';
$edit[$field_name . '[und][0][value][hour]'] = '10';
$edit[$field_name . '[und][0][value][minute]'] = '30';
if ($todate) {
$edit[$field_name . '[und][0][show_todate]'] = '1';
$edit[$field_name . '[und][0][value2][year]'] = '2010';
$edit[$field_name . '[und][0][value2][month]'] = '10';
$edit[$field_name . '[und][0][value2][day]'] = '7';
$edit[$field_name . '[und][0][value2][hour]'] = '11';
$edit[$field_name . '[und][0][value2][minute]'] = '30';
}
}
elseif ($widget_type == 'date_text') {
$edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30';
if ($todate) {
$edit[$field_name . '[und][0][show_todate]'] = '1';
$edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11:30';
}
}
elseif ($widget_type == 'date_popup') {
$edit[$field_name . '[und][0][value][date]'] = '10/07/2010';
$edit[$field_name . '[und][0][value][time]'] = '10:30';
if ($todate) {
$edit[$field_name . '[und][0][show_todate]'] = '1';
$edit[$field_name . '[und][0][value2][date]'] = '10/07/2010';
$edit[$field_name . '[und][0][value2][time]'] = '11:30';
}
}
// Test that the date is displayed correctly using both the 'short' and
// 'long' date types.
//
// For the short type, save an explicit format and assert that is the one
// which is displayed.
variable_set('date_format_short', 'l, m/d/Y - H:i:s');
$instance = field_info_instance('node', $field_name, 'story');
$instance['display']['default']['settings']['format_type'] = 'short';
field_update_instance($instance);
$this->drupalPost('node/add/story', $edit, t('Save'));
$this->assertText($edit['title'], "Node has been created");
$should_be = $todate ? 'Thursday, 10/07/2010 - 10:30 to 11:30' : 'Thursday, 10/07/2010 - 10:30';
$this->assertText($should_be, "Found the correct date for a $field_type field using the $widget_type widget displayed using the short date format.");
// For the long format, do not save anything, and assert that the displayed
// date uses the expected default value of this format provided by Drupal
// core ('l, F j, Y - H:i').
$instance = field_info_instance('node', $field_name, 'story');
$instance['display']['default']['settings']['format_type'] = 'long';
field_update_instance($instance);
$this->drupalPost('node/add/story', $edit, t('Save'));
$this->assertText($edit['title'], "Node has been created");
$should_be = $todate ? 'Thursday, October 7, 2010 - 10:30 to 11:30' : 'Thursday, October 7, 2010 - 10:30';
$this->assertText($should_be, "Found the correct date for a $field_type field using the $widget_type widget displayed using the long date format.");
}
}

View File

@@ -0,0 +1,97 @@
<?php
/**
* @file
* Timezone tests.
*/
class DateTimezoneTestCase extends DateFieldBasic {
/**
* @todo.
*/
public static function getInfo() {
return array(
'name' => 'Timezone & Granularity',
'description' => 'Test combinations of date field timezone handling and granularity.',
'group' => 'Date',
);
}
/**
* @todo.
*/
public function testTimezone() {
// Create a date fields with combinations of various timezone handling and
// granularity.
foreach (array('date', 'datestamp', 'datetime') as $field_type) {
foreach (array('site', 'none', 'date', 'user', 'utc') as $tz_handling) {
foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as $max_granularity) {
// Skip invalid combinations.
if (in_array($max_granularity, array('year', 'month', 'day')) && $tz_handling != 'none') {
continue;
}
$field_name = "field_test";
$label = 'Test';
$granularity = date_granularity_array_from_precision($max_granularity);
$options = array(
'label' => $label,
'widget_type' => 'date_text',
'field_name' => $field_name,
'field_type' => $field_type,
'input_format' => 'custom',
'input_format_custom' => 'm/d/Y - H:i:s',
'tz_handling' => $tz_handling,
'granularity' => $granularity,
);
$this->createDateField($options);
$this->dateForm($field_name, $field_type, $max_granularity, $tz_handling);
$this->deleteDateField($label);
}
}
}
}
/**
* @todo.
*/
public function dateForm($field_name, $field_type, $max_granularity, $tz_handling) {
variable_set('date_format_long', 'D, m/d/Y - H:i:s');
$edit = array();
$edit['title'] = $this->randomName(8);
$edit[$field_name . '[und][0][show_todate]'] = '1';
switch ($max_granularity) {
case 'year':
$edit[$field_name . '[und][0][value][date]'] = '2010';
$edit[$field_name . '[und][0][value2][date]'] = '2011';
$should_be = '2010 to 2011';
break;
case 'month':
$edit[$field_name . '[und][0][value][date]'] = '07/2010';
$edit[$field_name . '[und][0][value2][date]'] = '08/2010';
$should_be = '07/2010 to 08/2010';
break;
case 'day':
$edit[$field_name . '[und][0][value][date]'] = '10/07/2010';
$edit[$field_name . '[und][0][value2][date]'] = '10/08/2010';
$should_be = 'Thu, 10/07/2010 to Fri, 10/08/2010';
break;
case 'hour':
$edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10';
$edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11';
$should_be = 'Thu, 10/07/2010 - 10 to Thu, 10/07/2010 - 11';
break;
case 'minute':
$edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30';
$edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11:30';
$should_be = 'Thu, 10/07/2010 - 10:30 to 11:30';
break;
case 'second':
$edit[$field_name . '[und][0][value][date]'] = '10/07/2010 - 10:30:30';
$edit[$field_name . '[und][0][value2][date]'] = '10/07/2010 - 11:30:30';
$should_be = 'Thu, 10/07/2010 - 10:30:30 to 11:30:30';
break;
}
$this->drupalPost('node/add/story', $edit, t('Save'));
$this->assertText($edit['title'], "Node has been created");
$this->assertText($should_be, "Found the correct date for a $field_type field using $max_granularity granularity with $tz_handling timezone handling.");
}
}

View File

@@ -0,0 +1,151 @@
<?php
/**
* @file
* Date validation tests.
*/
class DateValidationTestCase extends DateFieldBasic {
/**
* @todo.
*/
public static function getInfo() {
return array(
'name' => 'Date Validation',
'description' => 'Test date validation.',
'group' => 'Date',
);
}
/**
* @todo.
*/
public function testValidation() {
// Attempts to create text date field stored as a date with default settings
// (from input which is not valid).
foreach (array('date', 'datestamp', 'datetime') as $field_type) {
foreach (array('date_select', 'date_popup', 'date_text') as $widget_type) {
$field_name = 'field_test';
$label = 'Test';
$options = array(
'label' => $label,
'field_name' => $field_name,
'field_type' => $field_type,
'widget_type' => $widget_type,
'input_format' => 'm/d/Y - H:i',
);
$this->createDateField($options);
// Malformed date test won't work on date_select, which won't allow
// invalid input.
if ($widget_type != 'date_select') {
$this->malFormedDate($field_name, $field_type, $widget_type);
}
$this->wrongGranularity($field_name, $field_type, $widget_type);
$this->deleteDateField($label);
}
}
}
/**
* @todo.
*/
function malFormedDate($field_name, $field_type, $widget_type) {
// Tests that date field filters improper dates.
$edit = array();
$edit['title'] = $this->randomName(8);
$edit['body[und][0][value]'] = $this->randomName(16);
if ($widget_type == 'date_select') {
$edit[$field_name . '[und][0][value][year]'] = '2011';
$edit[$field_name . '[und][0][value][month]'] = '15';
$edit[$field_name . '[und][0][value][day]'] = '49';
$edit[$field_name . '[und][0][value][hour]'] = '10';
$edit[$field_name . '[und][0][value][minute]'] = '30';
}
elseif ($widget_type == 'date_text') {
$edit[$field_name . '[und][0][value][date]'] = '15/49/2011 - 10:30';
}
elseif ($widget_type == 'date_popup') {
$edit[$field_name . '[und][0][value][date]'] = '15/49/2011';
$edit[$field_name . '[und][0][value][time]'] = '10:30';
}
$this->drupalPost('node/add/story', $edit, t('Save'));
$should_not_be = $edit['title'] . "has been created";
$this->assertNoText($should_not_be, "Correctly blocked creation of node with invalid month and day for a $field_type field using the $widget_type widget.");
$this->assertText('The month is invalid.', "Correctly blocked invalid month for a $field_type field using the $widget_type widget.");
$this->assertText('The day is invalid.', "Correctly blocked invalid day for a $field_type field using the $widget_type widget.");
// Test two-digit entry for year where 4-digit is expected.
if ($widget_type == 'date_select') {
$edit[$field_name . '[und][0][value][year]'] = '11';
$edit[$field_name . '[und][0][value][month]'] = '12';
$edit[$field_name . '[und][0][value][day]'] = '10';
$edit[$field_name . '[und][0][value][hour]'] = '10';
$edit[$field_name . '[und][0][value][minute]'] = '30';
}
elseif ($widget_type == 'date_text') {
$edit[$field_name . '[und][0][value][date]'] = '12/10/11 - 10:30';
}
elseif ($widget_type == 'date_popup') {
$edit[$field_name . '[und][0][value][date]'] = '12/10/11';
$edit[$field_name . '[und][0][value][time]'] = '10:30';
}
$this->drupalPost('node/add/story', $edit, t('Save'));
$should_not_be = $edit['title'] . " has been created";
$this->assertNoText($should_not_be, "Correctly blocked creation of node with invalid year for a $field_type field using the $widget_type widget.");
$should_be = 'The year is invalid. Please check that entry includes four digits.';
$this->assertText($should_be, "Correctly blocked two digit year for a $field_type field using the $widget_type widget.");
// Test invalid hour/minute entry for time.
if ($widget_type == 'date_select') {
$edit[$field_name . '[und][0][value][year]'] = '2011';
$edit[$field_name . '[und][0][value][month]'] = '12';
$edit[$field_name . '[und][0][value][day]'] = '10';
$edit[$field_name . '[und][0][value][hour]'] = '29';
$edit[$field_name . '[und][0][value][minute]'] = '95';
}
elseif ($widget_type == 'date_text') {
$edit[$field_name . '[und][0][value][date]'] = '12/10/2011 - 29:95';
}
elseif ($widget_type == 'date_popup') {
$edit[$field_name . '[und][0][value][date]'] = '12/10/2011';
$edit[$field_name . '[und][0][value][time]'] = '29:95';
}
$this->drupalPost('node/add/story', $edit, t('Save'));
$should_not_be = $edit['title'] . " has been created";
$this->assertNoText($should_not_be, "Correctly blocked creation of node with invalid time for a $field_type field using the $widget_type widget.");
$should_be = 'The hour is invalid.';
$this->assertText($should_be, "Correctly blocked invalid hour for a $field_type field using the $widget_type widget.");
$should_be = 'The minute is invalid.';
$this->assertText($should_be, "Correctly blocked invalid minute for a $field_type field using the $widget_type widget.");
}
/**
* @todo.
*/
public function wrongGranularity($field_name, $field_type, $widget_type) {
// Create a node with incorrect granularity -- missing time.
$edit = array();
$edit['title'] = $this->randomName(8);
$edit['body[und][0][value]'] = $this->randomName(16);
if ($widget_type == 'date_select') {
$edit[$field_name . '[und][0][value][year]'] = '2011';
$edit[$field_name . '[und][0][value][month]'] = '12';
$edit[$field_name . '[und][0][value][day]'] = '10';
$edit[$field_name . '[und][0][value][hour]'] = '';
$edit[$field_name . '[und][0][value][minute]'] = '';
}
elseif ($widget_type == 'date_text') {
$edit[$field_name . '[und][0][value][date]'] = '12/10/2011';
}
elseif ($widget_type == 'date_popup') {
$edit[$field_name . '[und][0][value][date]'] = '12/10/2011';
$edit[$field_name . '[und][0][value][time]'] = '';
}
$this->drupalPost('node/add/story', $edit, t('Save'));
$should_not_be = $edit['title'] . " has been created";
$this->assertNoText($should_not_be, "Correctly blocked creation of node with missing time for a $field_type field using the $widget_type widget.");
$this->assertText('invalid', "Marked form with missing time as invalid for a $field_type field using the $widget_type widget.");
}
}

View File

@@ -0,0 +1,125 @@
BEGIN:VCALENDAR
PRODID:-//Test
VERSION:2.0
X-WR-CALDESC:Test various iCal RRULEs.
BEGIN:VEVENT
UID:iCalRRuleTest1
SUMMARY:Daily for 10 occurrences
DTSTART;TZID=US-Eastern:20090702T090000
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest2
SUMMARY:Daily until December 24, 2009
DTSTART;TZID=US-Eastern:20091202T090000
RRULE:FREQ=DAILY;UNTIL=20091224T000000Z
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest3
SUMMARY:Every other day
DTSTART;TZID=US-Eastern:20090202T090000
RRULE:FREQ=DAILY;INTERVAL=2
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest4
SUMMARY:Every 10 days, 5 occurrences
DTSTART;TZID=US-Eastern:20090302T090000
RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest5
SUMMARY:Everyday in January, for 2 years
DTSTART;TZID=US-Eastern:20090101T090000
RRULE:FREQ=YEARLY;UNTIL=20110131T090000Z;BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest6
SUMMARY:Weekly for 10 occurrences
DTSTART;TZID=US-Eastern:20090102T090000
RRULE:FREQ=WEEKLY;COUNT=10
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest7
SUMMARY:Weekly on Tuesday and Thursday for 5 weeks
DTSTART;TZID=US-Eastern:20090902T090000
RRULE:FREQ=WEEKLY;UNTIL=20091007T000000Z;WKST=SU;BYDAY=TU,TH
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest8
SUMMARY:Every other week on Monday, Wednesday and Friday until December 24, 2009:
DTSTART;TZID=US-Eastern:20090502T090000
RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=20091224T000000Z;WKST=SU;BYDAY=MO,WE,FR
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest9
SUMMARY:Every other week on Tuesday and Thursday, for 8 occurrences
DTSTART;TZID=US-Eastern:20090702T090000
RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest10
SUMMARY:Monthly on the 1st Friday for ten occurrences
DTSTART;TZID=US-Eastern:20090905T090000
RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest11
SUMMARY:Monthly on the 1st Friday until December 24, 2009
DTSTART;TZID=US-Eastern:20090905T090000
RRULE:FREQ=MONTHLY;UNTIL=20091224T000000Z;BYDAY=1FR
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest12
SUMMARY:Every other month on the 1st and last Sunday of the month for 10 occurrences
DTSTART;TZID=US-Eastern:20090907T090000
RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest13
SUMMARY:Monthly on the second to last Monday of the month for 6 months
DTSTART;TZID=US-Eastern:20090119T090000
RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest14
SUMMARY:Monthly on the third to the last day of the month
DTSTART;TZID=US-Eastern:20090928T090000
RRULE:FREQ=MONTHLY;BYMONTHDAY=-3
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest15
SUMMARY:Monthly on the 2nd and 15th of the month for 10 occurrences
DTSTART;TZID=US-Eastern:20090202T090000
RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest16
SUMMARY:Monthly on the first and last day of the month for 10 occurrences
DTSTART;TZID=US-Eastern:20090130T090000
RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest17
SUMMARY:Every 3 months on the 10th thru 15th of the month for 10 occurrences
DTSTART;TZID=US-Eastern:20090410T090000
RRULE:FREQ=MONTHLY;INTERVAL=3;COUNT=10;BYMONTHDAY=10,11,12,13,14,15
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest18
SUMMARY:Every Tuesday, every other month
DTSTART;TZID=US-Eastern:20090602T090000
RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest19
SUMMARY:Yearly in June and July for 10 occurrences
DTSTART;TZID=US-Eastern:20090610T090000
RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7
END:VEVENT
BEGIN:VEVENT
UID:iCalRRuleTest20
SUMMARY:Every Thursday in March
DTSTART;TZID=US-Eastern:20090305T090000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH
END:VEVENT
END:VCALENDAR