├── .gitignore ├── LICENSE ├── README.md ├── RecurringDatePlugin.php ├── composer.json ├── composer.lock ├── fieldtypes └── RecurringDate_AdvancedDateFieldType.php ├── models └── RecurringDate_RuleModel.php ├── records ├── RecurringDate_DateRecord.php └── RecurringDate_RuleRecord.php ├── resources └── js │ └── advanceddate.js ├── services ├── RecurringDateService.php └── RecurringDate_SortService.php ├── templates └── fields.html └── variables └── RecurringDateVariable.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | vendor/ 3 | .DS_Store 4 | 5 | # Commit your application's lock file http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file 6 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 7 | # composer.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 North By Northwest 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | craft-recurring-dates 2 | ===================== 3 | 4 | ### I could still use help testing and bug fixing. If you find any issues put them in the Github issues. Thanks 5 | 6 | This is a plugin for the Craft CMS to add recurring dates functionality. It adds a new field type called Advanced Date which has the recurring date functions. It stores the recurring dates and an rule in the data base and returns an array of dates to the twig templates. I'm using the [Recurr library](https://github.com/simshaun/recurr) by [simshaun](https://github.com/simshaun) to build and parse the rrule. 7 | 8 | ###Installation 9 | 10 | 1. Clone this project into `your_craft_dir/craft/plugins/recurringdate` 11 | 2. Install Composer from [getcomposer.org](https://getcomposer.org/doc/00-intro.md#installation-nix) 12 | 3. Run `composer install` or `php composer.phar install` in the `recurringdate` directory to install dependencies 13 | 4. Install the plugin through the Craft admin panel 14 | 15 | ###Usage 16 | 17 | * Add a new Advanced Date Field type to one of your sections 18 | * Output is either an array of dates and the entries associated with them or an array of the values used to set the date rule. Output depends on how you query the dates 19 | * Multiple Dates Query can be used to automatically sort and filter corresponding dates. I built this with an event calendar in mind, that would need recurring dates. 20 | 21 | ####Example Output Usage - Multiple Dates Query 22 | 23 | ``` 24 | {% set query = craft.entries.section('events').relatedTo(craft.categories.slug('your-slug')) %} 25 | {% set events = craft.recurringdate.dates('yourFieldHandle', 26 | { 27 | 'limit': 3, 28 | 'before': '12/22/2014', 29 | 'after': 'now +1 months', 30 | 'criteria': query 31 | }) 32 | %} 33 | 34 | {% for event in events %} 35 | {{ event.date.start|date("n/j/Y") }}{{ event.date.end ? ' -- ' ~ event.date.end|date("n/j/Y") }}
36 | {% endfor %} 37 | ``` 38 | 39 | ####Arguments for `craft.recurringdate.dates` 40 | * Field Handle - Handle of your field in the Craft CP 41 | 42 | Values in the options array - see above example for usage 43 | * `limit` - limit the number of entries returned 44 | * `order` - 'ASC' or 'DESC' - defaults to 'ASC' 45 | * `group` - null, 'day', 'month', or 'year' - See Grouping Info Below 46 | * `before` - null, or Date string accepted by PHP's [strtotime function](http://www.php.net/manual/en/datetime.formats.php) 47 | * `after` - null, or Date string accepted by PHP's strtotime function 48 | * `criteria` - ElementCriteriaModel returned by a craft entry query 49 | * `excludes` - if excluded dates should be respected - defaults to true 50 | 51 | ####Properties available for output for Multiple Dates Query 52 | * `date` - Array containing all the date info for the recurring date 53 | * `id` - Id of the date, used to query the correct date in a sequence 54 | * `elementId` - Id of the Craft entry associated with this date 55 | * `start` - A string containing the start date 56 | * `end` - A string containing the end date, if set 57 | * `start_time` - A string containing the start time, if set 58 | * `end_time` - A string containing the end time, if set 59 | * `allday` - If the date lasts all day 60 | * `repeats` - If the date repeats 61 | * `rrule` - The RRULE string used to build the date 62 | * `entry` - The entry associated with this date 63 | 64 | ####Info about using Group for Multiple Dates Query 65 | The Group query will return a different structure than a regular query. It's structure looks like this 66 | ``` 67 | array( 68 | 'grouped_date_string' = array( 69 | array('date', 'entry'), 70 | array('date', 'entry'), 71 | ... 72 | ), 73 | 'grouped_date_string2' = array( 74 | array('date', 'entry'), 75 | array('date', 'entry') 76 | ), 77 | ... 78 | ) 79 | ``` 80 | 81 | ####Example Output Usage - Single Date Query using Date ID 82 | ``` 83 | {% set row = craft.recurringdate.date(id) %} 84 | {% set date = row.date %} 85 | {% set entry = row.entry %} 86 | {{ entry.title }}
87 | {{ date.start_date|date("n/j/Y H:i:s") }}{{ date.end_date is defined ? ' -- ' ~ date.end_date|date("n/j/Y H:i:s") }} 88 | ``` 89 | 90 | ####Properties available for output for Single Date Query 91 | The Single Date Query will only return one array that contains the `date` and the `entry` of the row with the specified ID. The idea is that you would use the multiple date query to list the date's ID and then you can select which specific date you want to display based on that ID. 92 | 93 | 94 | ####Example Output Usage - Single Entry 95 | ``` 96 | {% set date = entry.advanced_date_field_name %} 97 | {{ date.start_date|date("n/j/Y H:i:s") }}{{ date.end_date is defined ? ' -- ' ~ date.end_date|date("n/j/Y H:i:s") }} 98 | ``` 99 | 100 | ####Properties available for output for each Advanced Date Field - Single Entry 101 | * `start_date` - Craft DateTime Object - Start date set for the event 102 | * `start_time` - Craft DateTime Object - Same as startdate 103 | * `end_date` - Craft DateTime Object - End date set for the event, can be null if not set 104 | * `end_time` - Craft DateTime Object - Same as enddate 105 | * `allday` - Boolean - If the event is an allday event 106 | * `repeats` - Boolean - If the event repeats 107 | * `frequency` - String - Recurrence frequency, 'daily', 'weekly', 'monthly', 'yearly' 108 | * `interval` - Int - Recurrence Interval, integer from 1-31 109 | * `weekdays` - Array - days of the week that the event occurs, undefined if interval not weekly, 'SU', 'MO', 'TU', etc... 110 | * `repeat_by` - String - by day of week or day of month, undefined if interval not monthly, 'week', 'month' 111 | * `ends` - String - how the event ends, 'never', 'after', 'until' 112 | * `count` - Int - How many times this occurs, undefined if `ends` is not 'after' 113 | * `until` - Craft DateTime Object - Date event goes until, undefined if `ends` is not 'until' 114 | * `rrule` - RRULE string used to build the rule 115 | -------------------------------------------------------------------------------- /RecurringDatePlugin.php: -------------------------------------------------------------------------------- 1 | on('content.onSaveContent', function(Event $event) { 14 | craft()->recurringDate->contentSaved($event->params['content'], $event->params['isNewContent']); 15 | }); 16 | } 17 | 18 | function getName() 19 | { 20 | return Craft::t('Recurring Dates'); 21 | } 22 | 23 | function getVersion() 24 | { 25 | return '0.3'; 26 | } 27 | 28 | function getDeveloper() 29 | { 30 | return 'NXNW'; 31 | } 32 | 33 | function getDeveloperUrl() 34 | { 35 | return 'http://nxnw.net'; 36 | } 37 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "simshaun/recurr": "dev-master" 4 | } 5 | } -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" 5 | ], 6 | "hash": "8810619048498dd6127f84a98ea7c44c", 7 | "packages": [ 8 | { 9 | "name": "simshaun/recurr", 10 | "version": "dev-master", 11 | "source": { 12 | "type": "git", 13 | "url": "https://github.com/simshaun/recurr.git", 14 | "reference": "c90027564151953e487d9e0de3662b0c0b1e9fd1" 15 | }, 16 | "dist": { 17 | "type": "zip", 18 | "url": "https://api.github.com/repos/simshaun/recurr/zipball/c90027564151953e487d9e0de3662b0c0b1e9fd1", 19 | "reference": "c90027564151953e487d9e0de3662b0c0b1e9fd1", 20 | "shasum": "" 21 | }, 22 | "require": { 23 | "php": ">=5.3.0" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "3.7.*" 27 | }, 28 | "type": "library", 29 | "extra": { 30 | "branch-alias": { 31 | "dev-master": "1.0.x-dev" 32 | } 33 | }, 34 | "autoload": { 35 | "psr-0": { 36 | "Recurr": "src/" 37 | } 38 | }, 39 | "notification-url": "https://packagist.org/downloads/", 40 | "license": [ 41 | "MIT" 42 | ], 43 | "authors": [ 44 | { 45 | "name": "Shaun Simmons", 46 | "email": "shaun@envysphere.com", 47 | "homepage": "http://envysphere.com" 48 | } 49 | ], 50 | "description": "PHP library for working with recurrence rules", 51 | "homepage": "https://github.com/simshaun/recurr", 52 | "keywords": [ 53 | "dates", 54 | "events", 55 | "recurrence", 56 | "recurring", 57 | "rrule" 58 | ], 59 | "time": "2014-03-16 10:02:08" 60 | } 61 | ], 62 | "packages-dev": [ 63 | 64 | ], 65 | "aliases": [ 66 | 67 | ], 68 | "minimum-stability": "stable", 69 | "stability-flags": { 70 | "simshaun/recurr": 20 71 | }, 72 | "platform": [ 73 | 74 | ], 75 | "platform-dev": [ 76 | 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /fieldtypes/RecurringDate_AdvancedDateFieldType.php: -------------------------------------------------------------------------------- 1 | templates->formatInputId($name); 27 | 28 | $namespaceId = craft()->templates->namespaceInputId($id); 29 | 30 | craft()->templates->includeJsResource('recurringdate/js/advanceddate.js'); 31 | 32 | if(!empty($value)) { 33 | $ruleModel = RecurringDate_RuleModel::populateModel($value); 34 | } else { 35 | $ruleModel = new RecurringDate_RuleModel; 36 | $ruleModel->handle = $name; 37 | } 38 | 39 | $attr = $ruleModel->getAttributes(); 40 | 41 | if( strpos($attr['rrule'], 'EXDATE') !== false ){ 42 | $attr['exdates'] = array(); 43 | 44 | $exDatesArray = explode('EXDATE=', $attr['rrule']); 45 | $exDatesString = $exDatesArray[1]; 46 | $exDatesString = rtrim($exDatesString, ";"); 47 | $exDatesArray = explode(',', $exDatesString); 48 | 49 | foreach ($exDatesArray as $index => $date) { 50 | $attr['exdates'][] = DateTime::createFromFormat('Ymd', $date); 51 | } 52 | } 53 | else{ 54 | $attr['exdates'] = array(); 55 | } 56 | 57 | $attr['namespaceId'] = $namespaceId; 58 | 59 | return craft()->templates->render('recurringdate/fields', $attr); 60 | } 61 | 62 | //Leaving the db to be displayed 63 | public function prepValue($value){ 64 | return craft()->recurringDate->getRule($this); 65 | } 66 | 67 | public function validate($value){ 68 | return craft()->recurringDate->validateRule($this); 69 | } 70 | 71 | // After saving element, save field to plugin table 72 | public function onAfterElementSave() 73 | { 74 | // Returns true if entry was saved 75 | return craft()->recurringDate->saveRuleField($this); 76 | } 77 | } -------------------------------------------------------------------------------- /models/RecurringDate_RuleModel.php: -------------------------------------------------------------------------------- 1 | AttributeType::Number, 10 | 'handle' => AttributeType::String, 11 | 'start_date'=> AttributeType::String, 12 | 'start_time'=> AttributeType::String, 13 | 'end_date' => AttributeType::String, 14 | 'end_time' => AttributeType::String, 15 | 'allday' => AttributeType::Bool, 16 | 'repeats' => AttributeType::Bool, 17 | 'frequency' => array(AttributeType::Enum, 'values' => "daily, monthly, weekly, yearly"), 18 | 'interval' => AttributeType::Number, 19 | 'weekdays' => AttributeType::String, 20 | 'repeat_by' => array(AttributeType::Enum, 'values' => "week, month"), 21 | 'ends' => array(AttributeType::Enum, 'values' => "after, until"), 22 | 'count' => AttributeType::Number, 23 | 'until' => AttributeType::String, 24 | 'rrule' => AttributeType::String, 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /records/RecurringDate_DateRecord.php: -------------------------------------------------------------------------------- 1 | array(static::BELONGS_TO, 'RecurringDate_RuleRecord', 'onDelete' => static::CASCADE), 13 | ); 14 | } 15 | 16 | protected function defineAttributes(){ 17 | return array( 18 | 'start' => AttributeType::DateTime, 19 | 'end' => AttributeType::DateTime 20 | ); 21 | } 22 | } -------------------------------------------------------------------------------- /records/RecurringDate_RuleRecord.php: -------------------------------------------------------------------------------- 1 | array(static::BELONGS_TO, 'ElementRecord', 'required' => true, 'onDelete' => static::CASCADE), 13 | ); 14 | } 15 | 16 | protected function defineAttributes(){ 17 | return array( 18 | 'handle' => AttributeType::String, 19 | 'start_date'=> AttributeType::String, 20 | 'start_time'=> AttributeType::String, 21 | 'end_date' => AttributeType::String, 22 | 'end_time' => AttributeType::String, 23 | 'allday' => AttributeType::Bool, 24 | 'repeats' => AttributeType::Bool, 25 | 'frequency' => array(AttributeType::Enum, 'values' => "daily, monthly, weekly, yearly"), 26 | 'interval' => AttributeType::Number, 27 | 'weekdays' => AttributeType::String, 28 | 'repeat_by' => array(AttributeType::Enum, 'values' => "week, month"), 29 | 'ends' => array(AttributeType::Enum, 'values' => "after, until"), 30 | 'count' => AttributeType::Number, 31 | 'until' => AttributeType::String, 32 | 'rrule' => AttributeType::String, 33 | ); 34 | } 35 | } -------------------------------------------------------------------------------- /resources/js/advanceddate.js: -------------------------------------------------------------------------------- 1 | window.advancedDate = function(id) { 2 | 3 | var advancedDateView = function(namespace) { 4 | 5 | var _namespace = namespace; 6 | var _root = $('#' + namespace + '-field'); 7 | 8 | var _init = function() { 9 | _allDayToggle(); 10 | _repeatToggle(); 11 | _repeatInterval(); 12 | _repeatEnds(); 13 | _repeatNewDate(); 14 | }; 15 | 16 | var _allDayToggle = function() { 17 | 18 | var startTime = _root.find('.field.starttime .starttime-time'); 19 | var endTime = _root.find('.field.endtime .endtime-time'); 20 | var alldaySwitch = _root.find('.allday-switch .lightswitch'); 21 | var alldaySwitchData; 22 | 23 | if(!alldaySwitch.data('lightswitch')){ 24 | alldaySwitch.lightswitch(); 25 | alldaySwitchData = alldaySwitch.data('lightswitch'); 26 | } 27 | else{ 28 | alldaySwitchData = alldaySwitch.data('lightswitch'); 29 | } 30 | 31 | var changeHandler = function() { 32 | 33 | if (alldaySwitchData.on) { 34 | 35 | startTime.hide(); 36 | endTime.hide(); 37 | 38 | } else { 39 | 40 | startTime.show(); 41 | endTime.show(); 42 | } 43 | 44 | }; 45 | 46 | alldaySwitchData.settings.onChange = changeHandler; 47 | changeHandler(); 48 | 49 | 50 | }; 51 | 52 | var _repeatToggle = function() { 53 | 54 | var repeatHolder = _root.find('.repeat-holder'); 55 | var repeatsSwitch = _root.find('.repeats-switch .lightswitch'); 56 | var repeatsSwitchData; 57 | 58 | if(!repeatsSwitch.data('lightswitch')){ 59 | repeatsSwitch.lightswitch(); 60 | repeatsSwitchData = repeatsSwitch.data('lightswitch'); 61 | } 62 | else{ 63 | repeatsSwitchData = repeatsSwitch.data('lightswitch'); 64 | } 65 | 66 | var changeHandler = function() { 67 | 68 | if (repeatsSwitchData.on) { 69 | repeatHolder.show(); 70 | } else { 71 | repeatHolder.hide(); 72 | } 73 | 74 | }; 75 | 76 | repeatsSwitchData.settings.onChange = changeHandler; 77 | changeHandler(); 78 | 79 | }; 80 | 81 | var _repeatInterval = function() { 82 | 83 | var repeatSelect = _root.find('#' + _namespace + 'repeat-frequency'); 84 | 85 | var repeatOn = _root.find('.field.weekdays'); 86 | var repeatBy = _root.find('.field.repeat_by'); 87 | 88 | var repeatEveryUnit = _root.find('.repeat-every-unit'); 89 | 90 | var changeHandler = function() { 91 | 92 | repeatOn.hide(); 93 | repeatBy.hide(); 94 | 95 | switch (repeatSelect.val()) { 96 | case "daily": 97 | repeatEveryUnit.html('days'); 98 | break; 99 | case "weekly": 100 | repeatEveryUnit.html('weeks'); 101 | break; 102 | case "monthly": 103 | repeatEveryUnit.html('months'); 104 | break; 105 | case "yearly": 106 | repeatEveryUnit.html('years'); 107 | break; 108 | } 109 | 110 | if (repeatSelect.val() === 'weekly') { 111 | repeatOn.show(); 112 | } else { 113 | repeatOn.hide(); 114 | } 115 | 116 | if (repeatSelect.val() === 'monthly') { 117 | repeatBy.show(); 118 | } else { 119 | repeatBy.hide(); 120 | } 121 | 122 | }; 123 | 124 | repeatSelect.on('change', changeHandler); 125 | repeatSelect.trigger('change'); 126 | 127 | }; 128 | 129 | var _repeatEnds = function() { 130 | 131 | var repeatEndsSelect = _root.find('#' + _namespace + 'repeat-ends'); 132 | var repeatEndOccurrences = _root.find('.field.occurrences'); 133 | var repeatEndUntil = _root.find('.field.until'); 134 | 135 | var changeHandler = function() { 136 | if (repeatEndsSelect.val() == 'after') { 137 | repeatEndOccurrences.show(); 138 | } else { 139 | repeatEndOccurrences.hide(); 140 | } 141 | 142 | if (repeatEndsSelect.val() == 'until') { 143 | repeatEndUntil.show(); 144 | } else { 145 | repeatEndUntil.hide(); 146 | } 147 | }; 148 | 149 | repeatEndsSelect.on('change', changeHandler); 150 | repeatEndsSelect.trigger('change'); 151 | 152 | }; 153 | 154 | var _repeatNewDate = function() { 155 | var repeatDateButton = _root.find('#' + _namespace + 'date-add'); 156 | 157 | var clickHandler = function() { 158 | var repeatDateDiv = _root.find('.field.exdates > .padding:last'); 159 | 160 | var newRepeatDate = repeatDateDiv.clone(); 161 | var newRepeatDateInput = newRepeatDate.find('input'); 162 | var newRepeatDateDelete = newRepeatDate.find('a'); 163 | 164 | if( newRepeatDateDelete.length == 0 ){ 165 | newRepeatDate.append(''); 166 | newRepeatDateDelete = newRepeatDate.find('a'); 167 | } 168 | 169 | indexPos1 = newRepeatDateInput.attr('id').indexOf('exdates') + 7; 170 | indexPos2 = newRepeatDateInput.attr('id').indexOf('-date'); 171 | newDateIndex = newRepeatDateInput.attr('id').substring(indexPos1, indexPos2); 172 | index = parseInt(newDateIndex) + 1; 173 | 174 | handlePos1 = newRepeatDateInput.attr('id').indexOf('fields-') + 7; 175 | handlePos2 = newRepeatDateInput.attr('id').indexOf('exdates'); 176 | handle = newRepeatDateInput.attr('id').substring(handlePos1, handlePos2); 177 | 178 | newRepeatDateInput.attr('class', 'text'); 179 | newRepeatDateInput.attr('id', 'fields-'+handle+'exdates'+index+'-date'); 180 | newRepeatDateInput.attr('name', 'fields['+handle+'][exdates][][date]'); 181 | newRepeatDateInput.val(""); 182 | 183 | newRepeatDate.insertAfter(repeatDateDiv); 184 | newRepeatDate.show(); 185 | 186 | newRepeatDateInput.datepicker({ 187 | constrainInput: false, 188 | dateFormat: 'm/d/yy', 189 | defaultDate: new Date(), 190 | prevText: 'Prev', 191 | nextText: 'Next', 192 | }); 193 | 194 | var deleteClickHandler = function(){ 195 | newRepeatDate.fadeOut(300, function(){ newRepeatDate.remove(); }); 196 | }; 197 | 198 | newRepeatDateDelete.on('click', deleteClickHandler); 199 | }; 200 | 201 | repeatDateButton.on('click', clickHandler); 202 | } 203 | 204 | _init(); 205 | 206 | }; 207 | 208 | return { 209 | create: function(id) { 210 | $('#' + id + '-field').data('ad', new advancedDateView(id)); 211 | } 212 | }; 213 | 214 | }(); -------------------------------------------------------------------------------- /services/RecurringDateService.php: -------------------------------------------------------------------------------- 1 | findByAttributes(array( 17 | 'elementId' => $fieldType->element->id, 18 | 'handle' => $fieldType->model->handle, 19 | )); 20 | 21 | // Get attributes 22 | if ($ruleRecord) { 23 | $attr = $ruleRecord->getAttributes(); 24 | if( !empty($attr['start_date']) ){ 25 | $attr['start_date'] = DateTime::createFromString($attr['start_date'], craft()->getTimeZone()); 26 | } 27 | else 28 | { 29 | $attr['start_date'] = ''; 30 | } 31 | 32 | if( !empty($attr['end_date']) ){ 33 | $attr['end_date'] = DateTime::createFromString($attr['end_date'], craft()->getTimeZone()); 34 | } 35 | else 36 | { 37 | $attr['end_date'] = ''; 38 | } 39 | 40 | if( !empty($attr['until']) ){ 41 | $attr['until'] = DateTime::createFromString($attr['until'], craft()->getTimeZone()); 42 | } 43 | else 44 | { 45 | $attr['until'] = ''; 46 | } 47 | 48 | if( !empty($attr['start_time']) ){ 49 | $attr['start_time'] = DateTime::createFromFormat('H:i:s', $attr['start_time'], craft()->getTimeZone()); 50 | } 51 | else 52 | { 53 | $attr['start_time'] = ''; 54 | } 55 | 56 | if( !empty($attr['end_time']) ){ 57 | $attr['end_time'] = DateTime::createFromFormat('H:i:s', $attr['end_time'], craft()->getTimeZone()); 58 | } 59 | else 60 | { 61 | $attr['end_time'] = ''; 62 | } 63 | 64 | } else { 65 | $attr = array(); 66 | } 67 | 68 | return $attr; 69 | } 70 | 71 | public function validateRule(BaseFieldType $fieldType){ 72 | $postContent = $fieldType->element->getContentFromPost(); 73 | $value = $postContent[$fieldType->model->handle]; 74 | 75 | $startDate = $value['start_date']['date']; 76 | $startTime = $value['start_time']['time']; 77 | $endDate = $value['end_date']['date']; 78 | $endTime = $value['end_time']['time']; 79 | 80 | $allday = $value['allday']; 81 | $repeats = $value['repeats']; 82 | $ends = $value['ends']; 83 | $until = $value['until']['date']; 84 | $count = $value['count']; 85 | 86 | $errors = array(); 87 | 88 | if( empty($startDate) ){ 89 | $errors[] = Craft::t('There must be a valid Start Date'); 90 | } 91 | 92 | if( empty($startTime) && !$allday ){ 93 | $errors[] = Craft::t('If not all day event Start Time must be set'); 94 | } 95 | 96 | if( empty($endDate) && !empty($endTime) ){ 97 | $errors[] = Craft::t('If End Time is set, End Date must also be set'); 98 | } 99 | 100 | if( !empty($endDate) && empty($endTime) && !$allday ){ 101 | $errors[] = Craft::t('If End Date is set, End Time must also be set'); 102 | } 103 | 104 | //Checking Dates 105 | if( !empty($endDate) && !empty($startDate) && empty($endTime) && empty($startTime) ){ 106 | if( strtotime($endDate) <= strtotime($startDate) ){ 107 | $errors[] = Craft::t('End Date must be after the Start Date'); 108 | } 109 | } 110 | 111 | //Checking Times 112 | if( !empty($endDate) && !empty($startDate) && !empty($endTime) && !empty($startTime) ){ 113 | if( strtotime($endDate . ' ' . $endTime) <= strtotime($startDate . ' ' . $startTime) ){ 114 | $errors[] = Craft::t('End Date/Time must be after the Start Date/Time'); 115 | } 116 | } 117 | 118 | //Check until date 119 | if( empty($until) && $ends == 'until' && $repeats ){ 120 | $errors[] = Craft::t('Until date must be set if repeating until a specific date'); 121 | } 122 | elseif( !empty($until) && $ends == 'until' && $repeats ){ 123 | if( strtotime($until) <= strtotime($startDate) ){ 124 | $errors[] = Craft::t('Until date must be after the Start Date'); 125 | } 126 | } 127 | 128 | //Check after count 129 | if( empty($count) && $ends == 'after' && $repeats ){ 130 | $errors[] = Craft::t('After count must be set'); 131 | } 132 | 133 | if($errors){ 134 | return $errors; 135 | } 136 | else{ 137 | return true; 138 | } 139 | } 140 | 141 | // // Modify fieldtype query 142 | // public function modifyQuery(DbCommand $query, $params = array()) 143 | // { 144 | // // Join with plugin table 145 | // $query->join('recurringdate_rules', 'elements.id='.craft()->db->tablePrefix.'recurringdate_rules'.'.elementId'); 146 | // // Search by comparing coordinates 147 | // // Return modified query 148 | // return $query; 149 | // } 150 | 151 | // Once the content has been saved... 152 | public function contentSaved(ContentModel $content, $isNewContent) 153 | { 154 | $this->content = $content; 155 | $this->isNewContent = $isNewContent; 156 | } 157 | 158 | // Save field to plugin table 159 | public function saveRuleField(BaseFieldType $fieldType) 160 | { 161 | // Get elementId and handle 162 | $elementId = $fieldType->element->id; 163 | $handle = $fieldType->model->handle; 164 | 165 | // Check if attribute exists 166 | if (!$this->content->getAttribute($handle)) { 167 | return false; 168 | } 169 | 170 | // Set specified attributes 171 | $attr = $this->content[$handle]; 172 | 173 | // Attempt to load existing record 174 | $ruleRecord = RecurringDate_RuleRecord::model()->findByAttributes(array( 175 | 'elementId' => $elementId, 176 | 'handle' => $handle, 177 | )); 178 | 179 | // If no record exists, create new record 180 | if (!$ruleRecord) { 181 | $ruleRecord = new RecurringDate_RuleRecord; 182 | $attr['elementId'] = $elementId; 183 | $attr['handle'] = $handle; 184 | } 185 | 186 | $attr['start_date'] = ( !empty($attr['start_date']['date']) ? strtotime($attr['start_date']['date']) : null ); 187 | $attr['end_date'] = ( !empty($attr['end_date']['date']) ? strtotime($attr['end_date']['date']) : null ); 188 | $attr['start_time'] = ( !empty($attr['start_time']['time']) ? date('H:i:s', strtotime($attr['start_time']['time'])) : null ); 189 | $attr['end_time'] = ( !empty($attr['end_time']['time']) ? date('H:i:s', strtotime($attr['end_time']['time'])) : null ); 190 | 191 | 192 | if( isset($attr['exdates']) ){ 193 | $rawExDates = $attr['exdates']; 194 | $attr['exdates'] = array(); 195 | 196 | foreach ($rawExDates as $index => $exdate) { 197 | $attr['exdates'][] = !empty($exdate['date']) ? strtotime($exdate['date']) : null; 198 | } 199 | } 200 | else{ 201 | $attr['exdates'] = array(); 202 | } 203 | 204 | if (!isset($attr['allday'])) { $attr['allday'] =null; } 205 | if (!isset($attr['repeats'])) { $attr['repeats'] =null; } 206 | if (!isset($attr['frequency'])) { $attr['frequency'] =null; } 207 | if (!isset($attr['interval'])) { $attr['interval'] =null; } 208 | if (!isset($attr['weekdays'])) { $attr['weekdays'] =null; } 209 | if (!isset($attr['repeat_by'])) { $attr['repeat_by'] =null; } 210 | if (!isset($attr['ends'])) { $attr['ends'] = null;} 211 | if (!isset($attr['count'])) { $attr['count'] = null;} 212 | 213 | $attr['until'] = ( isset($attr['until']['date']) ? strtotime($attr['until']['date']) : null ); 214 | 215 | $attr['rrule'] = $this->buildRRule($attr); 216 | 217 | // Set record attributes 218 | $ruleRecord->setAttributes($attr, false); 219 | 220 | $ruleSaved = $ruleRecord->save(); 221 | 222 | $id = $ruleRecord->id; 223 | 224 | RecurringDate_DateRecord::model()->deleteAll('ruleId = '. $id); 225 | 226 | $this->generateDates($attr['rrule'], $id, $attr['repeats'], $attr['start_date'], $attr['end_date']); 227 | 228 | return $ruleSaved; 229 | 230 | } 231 | 232 | private function generateDates($rrule, $id, $repeats, $start, $end){ 233 | $finalDates = array(); 234 | $start = DateTime::createFromString($start, craft()->getTimeZone()); 235 | 236 | if(!is_null($end)){ 237 | $end = DateTime::createFromString($end, craft()->getTimeZone()); 238 | } 239 | 240 | if($repeats){ 241 | $rule = new Recurr\RecurrenceRule($rrule); 242 | $ruleTransformer = new Recurr\RecurrenceRuleTransformer($rule, 300); 243 | $dates = $ruleTransformer->getComputedArray(); 244 | 245 | if( !is_null($end) ){ 246 | $durationInterval = $start->diff($end); 247 | } 248 | else{ 249 | $durationInterval = $start->diff($start); 250 | } 251 | 252 | $fullDates = array(); 253 | foreach ($dates as $date) { 254 | $end = clone $date; 255 | $end = $end->add($durationInterval); 256 | 257 | $startDateString = $date->format('Ymd\THis'); 258 | $endDateString = $end->format('Ymd\THis'); 259 | 260 | 261 | $datesValues['start'] = DateTime::createFromFormat('Ymd\THis', $startDateString, craft()->getTimeZone()); 262 | 263 | if( !empty($end) ){ 264 | $datesValues['end'] = DateTime::createFromFormat('Ymd\THis', $endDateString, craft()->getTimeZone()); 265 | } 266 | 267 | $fullDates[] = $datesValues; 268 | } 269 | 270 | $finalDates = $fullDates; 271 | } 272 | else{ 273 | $finalDates = array(array( 'start' => $start, 'end' => $end )); 274 | } 275 | 276 | foreach ($finalDates as $index => $date) { 277 | $dateRecord = new RecurringDate_DateRecord; 278 | $dateRecord->setAttributes(array( 279 | 'ruleId' => $id, 280 | 'start' => $date['start'], 281 | 'end' => $date['end'], 282 | ), false); 283 | $dateRecord->save(); 284 | } 285 | } 286 | 287 | // SELECT d.start, d.end, r.end_time, r.start_time, r.allday, r.repeats, r.rrule 288 | // FROM craft_recurringdate_dates d 289 | // LEFT JOIN craft_recurringdate_rules r 290 | // ON d.ruleId = r.id 291 | // WHERE handle='eventDate' 292 | // ORDER BY start, start_time DESC 293 | 294 | // public function getCriteria($attributes = null){ 295 | 296 | // } 297 | 298 | public function getDate($id){ 299 | $query = craft()->db->createCommand() 300 | ->select('d.id, r.elementId, d.start start_date, d.start start, d.end end_date, d.end end, r.end_time, r.start_time, r.allday, r.repeats, r.rrule') 301 | ->from('recurringdate_dates d') 302 | ->leftJoin('recurringdate_rules r', 'd.ruleId = r.id') 303 | ->where('d.id=:id', array(':id'=>$id)); 304 | 305 | return $query->queryRow(); 306 | } 307 | 308 | public function getDates($handle, $limit, $order, $groupBy, $before, $after, $criteria, $excludes){ 309 | 310 | $query = craft()->db->createCommand() 311 | ->select('d.id, r.elementId, d.start start_date, d.start start, d.end end_date, d.end end, r.end_time, r.start_time, r.allday, r.repeats, r.rrule') 312 | ->from('recurringdate_dates d') 313 | ->leftJoin('recurringdate_rules r', 'd.ruleId = r.id') 314 | ->where('handle=:handle', array(':handle'=>$handle)); 315 | 316 | 317 | if( !is_null($before) ){ 318 | $beforeValue = date('Y-m-d H:i:s', strtotime($before)); 319 | $query->andWhere(':before >= d.start', array(':before'=>$beforeValue)); 320 | } 321 | 322 | if( !is_null($after) ){ 323 | $afterValue = date('Y-m-d H:i:s', strtotime($after)); 324 | $query->andWhere(':after <= d.start', array(':after'=>$afterValue)); 325 | } 326 | 327 | if( !is_null($criteria) ){ 328 | $critArr = array(); 329 | foreach ($criteria as $index => $entry) { 330 | $critArr[] = $entry->id; 331 | } 332 | 333 | $critIds = implode(',', $critArr); 334 | 335 | $query->andWhere('r.elementId IN (:ids)', array(':ids'=>$critIds)); 336 | } 337 | 338 | if($order == 'ASC'){ 339 | $query->order(array('start ASC', 'start_time ASC')); 340 | } 341 | else{ 342 | $query->order(array('start DESC', 'start_time DESC')); 343 | } 344 | 345 | 346 | if( !is_null($limit) ){ 347 | $query->limit($limit * 2); 348 | } 349 | 350 | 351 | $events = $query->queryAll(); 352 | 353 | $eventsFinal = array(); 354 | 355 | foreach ($events as $index => $value) { 356 | $id = $value['elementId']; 357 | if( $excludes ){ 358 | $exdates = $this->getExdates($value['rrule']); 359 | if( !in_array(date('Ymd', strtotime($value['start'])), $exdates) ){ 360 | $eventsFinal[] = array( 361 | 'date' => $value, 362 | 'entry' => craft()->entries->getEntryById($id), 363 | ); 364 | } 365 | } 366 | else{ 367 | $eventsFinal[] = array( 368 | 'date' => $value, 369 | 'entry' => craft()->entries->getEntryById($id), 370 | ); 371 | } 372 | } 373 | 374 | if( !is_null($limit) ){ 375 | $eventsFinal = array_slice($eventsFinal, 0, $limit); 376 | } 377 | 378 | if( !is_null($groupBy) ){ 379 | if( $groupBy == 'day' ){ 380 | $eventsFinal = $this->groupBy($eventsFinal, 'n/j/Y'); 381 | } 382 | elseif( $groupBy == 'month' ){ 383 | $eventsFinal = $this->groupBy($eventsFinal, 'n/1/Y'); 384 | } 385 | elseif( $groupBy == 'year' ){ 386 | $eventsFinal = $this->groupBy($eventsFinal, '1/1/Y'); 387 | } 388 | } 389 | 390 | return $eventsFinal; 391 | } 392 | 393 | private function getExdates($rrule){ 394 | if( strpos($rrule, 'EXDATE') !== false ){ 395 | $exdates = array(); 396 | 397 | $exDatesArray = explode('EXDATE=', $rrule); 398 | $exDatesString = $exDatesArray[1]; 399 | $exDatesString = rtrim($exDatesString, ";"); 400 | $exDatesArray = explode(',', $exDatesString); 401 | 402 | foreach ($exDatesArray as $index => $date) { 403 | $exdates[] = date('Ymd', strtotime($date)); 404 | } 405 | } 406 | else{ 407 | $exdates = array(); 408 | } 409 | return $exdates; 410 | } 411 | 412 | private function groupBy($events, $groupString){ 413 | $dates = array(); 414 | foreach ($events as $i => $date) { 415 | $dateStart = $date['date']['start']; 416 | $formDate = date($groupString, strtotime($dateStart)); 417 | if( isset($dates[$formDate]) ){ 418 | $dates[$formDate][] = array( 419 | 'entry' => $date['entry'], 420 | 'date' => $date['date'] 421 | ); 422 | } 423 | else{ 424 | $dates[$formDate] = array(array( 425 | 'entry' => $date['entry'], 426 | 'date' => $date['date'] 427 | )); 428 | } 429 | } 430 | 431 | return $dates; 432 | } 433 | 434 | private function buildRRule($settings){ 435 | $allday = $settings['allday']; 436 | $startDate = $settings['start_date']; 437 | $endDate = $settings['end_date']; 438 | $startTime = $settings['start_time']; 439 | $endTime = $settings['end_time']; 440 | $repeats = $settings['repeats']; //Does it repeat? 441 | $frequency = $settings['frequency']; //Weekly, Daily, Monthly, Yearly 442 | $interval = $settings['interval']; // i.e. Every 1-30 Months? 443 | $weekDays = $settings['weekdays']; //Which weekdays 444 | $repeatBy = $settings['repeat_by']; //Monthly, by day of week, or day of month 445 | $ends = $settings['ends']; //how it ends (never, after, until) 446 | $count = $settings['count']; // if ending occurs amounts 447 | $untilDate = $settings['until']; // if ending until date 448 | $exDates = $settings['exdates']; 449 | 450 | $dbString = ''; 451 | 452 | //Builds RRULE based on UI Elements Input 453 | if($repeats){ 454 | $rule = new Recurr\RecurrenceRule(); 455 | $rule->setStartDate(DateTime::createFromString($startDate, craft()->getTimeZone())); 456 | $rule->setInterval($interval); 457 | 458 | if($ends == 'until'){ 459 | $rule->setEndDate(DateTime::createFromString($untilDate, craft()->getTimeZone())); 460 | } 461 | else if($ends == 'after'){ 462 | $rule->setCount($count); 463 | } 464 | 465 | switch ($frequency) { 466 | case 'daily': 467 | $rule->setFreq(Recurr\RecurrenceRule::FREQ_DAILY); 468 | break; 469 | 470 | case 'weekly': 471 | $rule->setFreq(Recurr\RecurrenceRule::FREQ_WEEKLY); 472 | if( empty($weekDays) ){ 473 | //If weekdays empty set monday by default 474 | $rule->setByDay(array('MO')); 475 | } 476 | else{ 477 | $rule->setByDay($weekDays); 478 | } 479 | break; 480 | 481 | case 'monthly': 482 | $rule->setFreq(Recurr\RecurrenceRule::FREQ_MONTHLY); 483 | if( $repeatBy == 'month' ){ 484 | $dayOfMonth = date('j', $startDate); 485 | $rule->setByMonthDay(array($dayOfMonth)); 486 | } 487 | else if( $repeatBy == 'week' ){ 488 | $uStartDate = $startDate; 489 | $dayOfWeek = strtoupper( substr( date( 'D', $uStartDate ), 0, -1) ); 490 | $numberOfWeek = ceil( date( 'j', $uStartDate ) / 7 ); 491 | $rule->setByDay(array('+'.$numberOfWeek . $dayOfWeek)); 492 | } 493 | break; 494 | 495 | case 'yearly': 496 | $rule->setFreq(Recurr\RecurrenceRule::FREQ_YEARLY); 497 | break; 498 | } 499 | 500 | if($startTime){ 501 | $time = date('Ymd\THis', strtotime( date('Y-m-d', $startDate) . date(' H:i:s', strtotime($startTime)))); 502 | $dbString .= 'DTSTART=' . $time . ';' . $rule->getString(); 503 | } 504 | else{ 505 | $time = $startDate; 506 | var_dump($startDate); 507 | $dbString .= 'DTSTART=' . date('Ymd', $time) . ';' . $rule->getString(); 508 | } 509 | 510 | if( count($exDates) > 0 ){ 511 | $dbString .= ';EXDATE='; 512 | foreach ($exDates as $index => $date) { 513 | $dbString .= date('Ymd', $date); 514 | if( $date !== end($exDates) ){ 515 | $dbString .= ','; 516 | } 517 | } 518 | $dbString .= ';'; 519 | } 520 | } 521 | else{ 522 | if($startTime){ 523 | $time = date('Ymd\THis', strtotime( date('Ymd', $startDate) . date('\THis', strtotime($startTime)))); 524 | $dbString .= 'DTSTART=' . $time . ';'; 525 | } 526 | else{ 527 | $dbString .= 'DTSTART=' . date('Ymd', $startDate) . ';'; 528 | } 529 | } 530 | 531 | return $dbString; 532 | } 533 | } -------------------------------------------------------------------------------- /services/RecurringDate_SortService.php: -------------------------------------------------------------------------------- 1 | $entry) { 9 | foreach ($entry->eventDate['dates'] as $i => $date) { 10 | $dates[] = array( 11 | 'start' => $date['start'], 12 | 'entry' => $entry 13 | ); 14 | } 15 | } 16 | 17 | $cmp = function($a, $b){ 18 | return strcmp($a['start'], $b['start']); 19 | }; 20 | 21 | usort($dates, $cmp); 22 | 23 | return $dates; 24 | } 25 | 26 | public function group($entries, $field){ 27 | $dates = array(); 28 | 29 | //Group the entries by date 30 | foreach ($entries as $index => $entry) { 31 | foreach ($entry->eventDate['dates'] as $i => $date) { 32 | $dateStart = $date['start']; 33 | $formDate = strtotime($dateStart->format('Ymd')); 34 | if( isset($dates[$formDate]) ){ 35 | $dates[$formDate][] = array( 36 | 'entry' => $entry, 37 | 'start' => $date['start'], 38 | 'end' => $date['end'] 39 | ); 40 | } 41 | else{ 42 | $dates[$formDate] = array(array( 43 | 'entry' => $entry, 44 | 'start' => $date['start'], 45 | 'end' => $date['end'] 46 | )); 47 | } 48 | } 49 | } 50 | 51 | //Sort by dates 52 | ksort($dates); 53 | 54 | // $cmp = function($a, $b){ 55 | // return strcmp($a->eventDate['startdate'], $b->eventDate['startdate']); 56 | // }; 57 | 58 | // foreach ($dates as $index => $date) { 59 | // usort($date, $cmp); 60 | // } 61 | 62 | return $dates; 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /templates/fields.html: -------------------------------------------------------------------------------- 1 | {% import "_includes/forms" as forms %} 2 | 3 |
4 |
5 |
6 | 7 |
8 |

Does this event last all day?

9 |
10 |
11 |
12 | {{ forms.lightswitch({ 13 | id: handle~'allday', 14 | name: handle~'[allday]', 15 | on: (allday is defined ? allday : null) 16 | }) }} 17 |
18 |
19 |
20 |
21 | 22 |
23 | {{ forms.date({ 24 | name: handle~'[start_date]', 25 | id: handle~'start_date', 26 | value: (start_date is defined ? start_date : null) 27 | }) }} 28 | 29 | {{ forms.time({ 30 | name: handle~'[start_time]', 31 | id: handle~'start_time', 32 | value: (start_time is defined ? start_time : null) 33 | }) }} 34 | 35 |
36 |
37 |
38 | 39 |
40 | {{ forms.date({ 41 | name: handle~'[end_date]', 42 | id: handle~'end_date', 43 | value: (end_date is defined ? end_date : null) 44 | }) }} 45 | 46 | {{ forms.time({ 47 | name: handle~'[end_time]', 48 | id: handle~'end_time', 49 | value: (end_time is defined ? end_time : null) 50 | }) }} 51 | 52 |
53 |
54 |
55 | 56 |
57 |

Does this event occur multiple times?

58 |
59 |
60 |
61 | {{ forms.lightswitch({ 62 | name: handle~'[repeats]', 63 | on: (repeats is defined ? repeats : null) 64 | }) }} 65 |
66 |
67 | 68 |
69 | 70 |
71 |
72 | 73 |
74 | {{ forms.select({ 75 | id: handle~'repeat-frequency', 76 | name: handle~'[frequency]', 77 | value: (frequency is defined ? frequency : 'daily'), 78 | options: { 79 | 'daily': 'Daily', 80 | 'weekly': 'Weekly', 81 | 'monthly': 'Monthly', 82 | 'yearly': 'Yearly' 83 | } 84 | }) }} 85 |
86 |
87 |
88 | 89 |
90 | {{ forms.select({ 91 | name: handle~'[interval]', 92 | id: 'repeat-interval', 93 | value: ( interval is defined ? interval : 1 ), 94 | options: { 95 | 1: 1, 96 | 2: 2, 97 | 3: 3, 98 | 4: 4, 99 | 5: 5, 100 | 6: 6, 101 | 7: 7, 102 | 8: 8, 103 | 9: 9, 104 | 10: 10, 105 | 11: 11, 106 | 12: 12, 107 | 13: 13, 108 | 14: 14, 109 | 15: 15, 110 | 16: 16, 111 | 17: 17, 112 | 18: 18, 113 | 19: 19, 114 | 20: 20, 115 | 21: 21, 116 | 22: 22, 117 | 23: 23, 118 | 24: 24, 119 | 25: 25, 120 | 26: 26, 121 | 27: 27, 122 | 28: 28, 123 | 29: 29, 124 | 30: 30, 125 | 31: 31, 126 | } 127 | }) }} 128 | days 129 |
130 |
131 |
132 | 133 |
134 | {{ forms.checkboxGroup({ 135 | name: handle~'[weekdays]', 136 | values: (weekdays is defined ? weekdays : null), 137 | options: { 138 | 'SU' : 'Sunday', 139 | 'MO' : 'Monday', 140 | 'TU' : 'Tuesday', 141 | 'WE' : 'Wednesday', 142 | 'TH' : 'Thursday', 143 | 'FR' : 'Friday', 144 | 'SA' : 'Saturday', 145 | } 146 | }) }} 147 |
148 |
149 |
150 | 151 |
152 | {{ forms.radioGroup({ 153 | name: handle~'[repeat_by]', 154 | value: (repeat_by is defined and repeat_by is not null ? repeat_by : 'month'), 155 | options: { 156 | 'month' : 'Day of Month', 157 | 'week' : 'Day of Week', 158 | } 159 | }) }} 160 |
161 |
162 |
163 | 164 |
165 | {{ forms.select({ 166 | id: handle~'repeat-ends', 167 | name: handle~'[ends]', 168 | value: (ends is defined ? ends : 'after'), 169 | options: { 170 | 'after': 'After', 171 | 'until': 'Until', 172 | } 173 | }) }} 174 | 175 |
176 | 177 |
178 | 179 |
180 | {{ forms.text({ 181 | name: handle~'[count]', 182 | value: ( count is defined ? count : 5 ), 183 | size: '20px', 184 | maxlength: '5' 185 | }) }} 186 | 187 |
188 | 189 |
190 | 191 |
192 | 193 |
194 | {{ forms.date({ 195 | name: handle~'[until]', 196 | id: handle~'until', 197 | value: ( until is defined ? until : null ) 198 | }) }} 199 | 200 |
201 | 202 |
203 |
204 | 205 |
206 | 213 | {% for date in exdates %} 214 |
215 | {{ forms.date({ 216 | name: handle~'[exdates][]', 217 | id: handle~'exdates'~loop.index, 218 | value: (date is defined ? date : null) 219 | }) }} 220 |
221 | {% endfor %} 222 |
223 |
224 |
{{ "Excluded Date"|t }}
225 |
226 |
227 |
228 |
229 |
230 | 231 | {% set rangeSliderFieldJs %} 232 | $(function() { window.advancedDate.create('{{ namespaceId }}'); }); 233 | {% endset %} 234 | {% includeJs rangeSliderFieldJs %} 235 | 236 | {% includeJsResource "recurringdate/js/advanceddate.js" %} -------------------------------------------------------------------------------- /variables/RecurringDateVariable.php: -------------------------------------------------------------------------------- 1 | recurringDate->getDates($handle, $limit, $order, $groupBy, $before, $after, $criteria, $excludes); 18 | } 19 | 20 | public function date($id) { 21 | return craft()->recurringDate->getDate($id); 22 | } 23 | 24 | // Will implement a better query system similar to the ElementCriteriaModel 25 | // public function dates($criteria = null){ 26 | // return craft()->recurringDate->getCriteria($criteria); 27 | // } 28 | } --------------------------------------------------------------------------------