Parent Directory
|
Revision Log
|
Revision Graph
Part of the D6 update.
| 1 | <?php |
| 2 | // $Id: eventrepeat.module,v 1.64 2009/05/10 00:03:05 rmiddle Exp $ |
| 3 | |
| 4 | // TODO: Update this section |
| 5 | |
| 6 | //KNOWN BUGS |
| 7 | //this module is currently in beta release. known bugs include: |
| 8 | // 1. exception editor not working |
| 9 | // 2. editing the repeat sequence itself after it's already been created is inconsistent. nodes don't map to the exact |
| 10 | // date they're being moved to |
| 11 | // 3. bug: items getting rendered on day previous to creation day |
| 12 | |
| 13 | //TODO |
| 14 | // 1. put form_set_error in place of drupal_set_message where appropriate. change drupal_set_message to warning |
| 15 | // where appropriate |
| 16 | // 2. add watchdog entries |
| 17 | |
| 18 | |
| 19 | // WISH LIST |
| 20 | // 1. invoke hook 'delete pre' for multi deletions (single node only?) |
| 21 | // 2. add ical support |
| 22 | |
| 23 | //include_once(drupal_get_path('module', 'event') .'/event_timezones.inc'); |
| 24 | include(drupal_get_path('module', 'eventrepeat') .'/eventrepeat.theme'); |
| 25 | |
| 26 | /** |
| 27 | * @file |
| 28 | * |
| 29 | * Event Repeats and repeating events to the event module. |
| 30 | */ |
| 31 | |
| 32 | /** |
| 33 | * @defgroup eventrepeat_core Core drupal hooks |
| 34 | */ |
| 35 | |
| 36 | /** |
| 37 | * Implementation of hook_cron(). |
| 38 | * |
| 39 | * @ingroup eventrepeat_core |
| 40 | */ |
| 41 | function eventrepeat_cron() { |
| 42 | |
| 43 | //this value determines the maximum number of rows that can be added to the calmap table in one cron run, and should be |
| 44 | //set to a reasonable value to avoid server timeouts that could occur. this will most likely only happen when initially |
| 45 | //seeding the table. default value is 2000 rows |
| 46 | $_BAILOUT_VALUE = variable_get('eventrepeat_maximum_rows', 2000); |
| 47 | |
| 48 | //the table values for eventrepeat_calmap |
| 49 | //day_stamp: human readable date in the format Y-m-d. also used in SQL queries |
| 50 | //date_stamp: date for which the RRULE values are being generated, timestamp is last second of the day, in UTC |
| 51 | //day_of_week: day of the week in the following format 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' |
| 52 | //day_in_month: iteration of the day of the week in the month in the following format '1SU', '3MO' |
| 53 | //day_in_month_R: iteration of the day of the week in the month, counting from the end, in the following format '-1SU', '-3MO' |
| 54 | //month_day: numeric day of the month in the following format '1', '15' |
| 55 | //month_day_R: numeric day of the month, counting from the end, in the following format '-1', '-15' |
| 56 | //month: numeric month number, in the following format '1', '12' |
| 57 | //year_day: numeric day of the year in the following format '1', '226' |
| 58 | //year_day_R: numeric day of the year, counting from the end, in the following format '-1', '-226' |
| 59 | //week_number: numeric ISO-8601 week in the following format '1', '24' |
| 60 | //week_number_R: numeric ISO-8601 week, counting from the end, in the following format '-1', '-24' |
| 61 | |
| 62 | //first thing is to determine the exact range that's going to be rendered, and calculate start and end timestamps. |
| 63 | //current time is calculated in GMT, then we check the table for the last created date. if no records are in the |
| 64 | //table, set start date to the end of the day on the day before the current date, and enable the seed. |
| 65 | $curtime = time(); |
| 66 | $result = db_query("SELECT MAX(date_stamp) FROM {eventrepeat_calendar_map}"); |
| 67 | $start = db_fetch_array($result); |
| 68 | if ($start['MAX(date_stamp)']) { |
| 69 | $start = $start['MAX(date_stamp)']; |
| 70 | } |
| 71 | else { |
| 72 | $start = gmmktime(23, 59, 59, (int) gmdate('n', $curtime), (int) gmdate('j', $curtime), (int) gmdate('Y', $curtime)) - 86400; |
| 73 | $seed = TRUE; |
| 74 | } |
| 75 | |
| 76 | //end date is the current day, plus one day, plus the render support period |
| 77 | $curtime_end = gmmktime(23, 59, 59, (int) gmdate('n', $curtime), (int) gmdate('j', $curtime), (int) gmdate('Y', $curtime)); |
| 78 | $end = $curtime_end + 86400 + (variable_get('eventrepeat_render_support', 2000) * 86400); |
| 79 | |
| 80 | //next, we want to delete any table entries that are no longer needed. leave yesterday's date in as a precaution. |
| 81 | //since cron will probably be run more than once a day, and it's also possible that the admin may have shortened the |
| 82 | //render period since the last cron run, we put in a few checks for that, too, and exit the function if it doesn't |
| 83 | //need to be run now |
| 84 | if ($end < $start) { |
| 85 | db_query('DELETE FROM {eventrepeat_calendar_map} WHERE date_stamp > %d', $end); |
| 86 | return; |
| 87 | } |
| 88 | |
| 89 | $result = db_query('SELECT date_stamp FROM {eventrepeat_calendar_map} WHERE date_stamp < %d', $curtime - 86400); |
| 90 | if (db_result($result) > 0 || $seed) { |
| 91 | $result = db_query('DELETE FROM {eventrepeat_calendar_map} WHERE date_stamp < %d', $curtime - 86400); |
| 92 | } |
| 93 | else { |
| 94 | return; |
| 95 | } |
| 96 | |
| 97 | //set the insert query line, initialize the array where we'll dump the values initially, and start a bailout counter: |
| 98 | //this counter will count up and bail out of the calmap cycle when it hits _BAIL_OUT_VALUE, which can be set at the top |
| 99 | //of the function |
| 100 | $insert_line = "INSERT INTO {eventrepeat_calendar_map} (day_stamp, date_stamp, day_of_week, day_in_month, |
| 101 | day_in_month_R, month_day ,month_day_R, month, year_day, year_day_R, week_number, week_number_R) VALUES"; |
| 102 | $values = array(); |
| 103 | $bailout = 1; |
| 104 | |
| 105 | //create timestamp for first day of the month which $start is in. this is the easiest way to count to generate info |
| 106 | //for the date range that needs to be rendered. also create the other initial calendar values we'll need here, based |
| 107 | //on the beginning of the month timestamp |
| 108 | $BOM = gmmktime(23, 59, 59, (int) gmdate('n', $start), 1, (int) gmdate('Y', $start)); |
| 109 | $master_year = (int) gmdate('Y', $BOM); //current year |
| 110 | $leap_year = (int) gmdate('L', $BOM); //Whether it's a leap year |
| 111 | $days_in_month = (int) gmdate('t', $BOM); //Number of days in the given month |
| 112 | $days_of_week = array('SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'); |
| 113 | $master_month = (int) gmdate('n', $BOM); //current month |
| 114 | $master_month_day = 1; //current numeric day of the month |
| 115 | $master_month_day_R = -$days_in_month; //current numeric day of the month, counting backwards |
| 116 | $master_day = (int) gmdate('w', $BOM); //day of the week of the first day of the month, for incrementing day counts |
| 117 | $master_last_day = (int) gmdate('w', gmmktime(23, 59, 59, (int) gmdate('n', $start), $days_in_month, |
| 118 | (int) gmdate('Y', $start))); |
| 119 | $dow = $master_day; //current day of the week |
| 120 | |
| 121 | //1st day of the month will always be the first of it's kind with respect to the day of the week, so we set that |
| 122 | //to one here. months will have certain days of the week that occur 4 times, and others that occur 5 times, so |
| 123 | //here we also build an incrementer that will help with correct reverse day in month calculations |
| 124 | $master_day_in_month = 1; |
| 125 | $master_day_in_month_R = -4; |
| 126 | $day_in_month_incrementer = _eventrepeat_day_in_month_incrementer($master_day, $days_in_month); |
| 127 | |
| 128 | $master_year_day = (int) gmdate('z', $BOM) + 1; //current numeric day of the year |
| 129 | $master_year_day_R = $master_year_day - 365 - $leap_year - 1; //current numeric day of the year, counting backwards |
| 130 | $master_week_no = (int) gmdate('W', $BOM); //generate the ISO-8601 week no. |
| 131 | |
| 132 | //figure out how many ISO-8601 weeks are in the current year. this seems like a hack but i wasn't sure how |
| 133 | //else to do it w/ php's date function |
| 134 | for ($n = 28; $n <= 31; $n++) { |
| 135 | $WN_stamp = gmmktime(23, 59, 59, 12, $n, $master_year); |
| 136 | $master_weeks_in_year = (int) gmdate('W', $WN_stamp) > $master_weeks_in_year ? (int) gmdate('W', $WN_stamp) : |
| 137 | $master_weeks_in_year; |
| 138 | } |
| 139 | $master_week_no_R = $master_week_no - $master_weeks_in_year - 1; //ISO-8601 week no. in year counting backwards |
| 140 | |
| 141 | //now we move from the beginning of the month to the day before the start of the rendering period, adjusting values |
| 142 | //for everything we set above as we go. set the counter that increments by one day, and if the start of the rendering |
| 143 | //cycle is the beginning of the month, skip this step |
| 144 | $counter = $BOM + 86400; |
| 145 | if ($BOM != $start) { |
| 146 | while ($counter <= $start) { |
| 147 | |
| 148 | //cycle through the numeric days of the week |
| 149 | if ($dow == 6) { |
| 150 | $dow = 0; |
| 151 | } |
| 152 | else { |
| 153 | $dow++; |
| 154 | } |
| 155 | |
| 156 | //each time we get back around to the day of the week of the first day of the month, we increment the |
| 157 | //master_day_in_month counters |
| 158 | if ($master_day == $dow) { |
| 159 | $master_day_in_month++; |
| 160 | $master_day_in_month_R++; |
| 161 | } |
| 162 | |
| 163 | //increment the master_month_day and master_year_day counters |
| 164 | $master_month_day++; |
| 165 | $master_month_day_R++; |
| 166 | $master_year_day++; |
| 167 | $master_year_day_R++; |
| 168 | |
| 169 | //increment the weekno. variables if the day of the week is monday |
| 170 | if ($dow == 1) { |
| 171 | $master_week_no++; |
| 172 | $master_week_no_R++; |
| 173 | } |
| 174 | $counter += 86400; |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | //now we start the cycle for days where values need to be created |
| 179 | while ($counter <= $end) { |
| 180 | |
| 181 | //cycle through the numeric days of the week |
| 182 | if ($dow == 6) { |
| 183 | $dow = 0; |
| 184 | } |
| 185 | else { |
| 186 | $dow++; |
| 187 | } |
| 188 | |
| 189 | //each time we get back around to the day of the week of the first day of the month, we increment the |
| 190 | //master_day_in_month counters |
| 191 | if ($master_day == $dow) { |
| 192 | $master_day_in_month++; |
| 193 | $master_day_in_month_R++; |
| 194 | } |
| 195 | |
| 196 | //increment the master_month_day and master_year_day counters |
| 197 | $master_month_day++; |
| 198 | $master_month_day_R++; |
| 199 | $master_year_day++; |
| 200 | $master_year_day_R++; |
| 201 | |
| 202 | //january 4th is the first day of the year that's guaranteed to be in the first ISO-8601 week of the year |
| 203 | //so here we check to see if it's the 4th and reset the week no. variables if so |
| 204 | if ($master_month == 1 && $master_month_day == 4) { |
| 205 | $master_week_no = 1; |
| 206 | |
| 207 | //figure out how many ISO-8601 weeks are in the current year. this seems like a hack but i wasn't sure how |
| 208 | //else to do it w/ php's date function. here also we need to set $master_weeks_in_year back to 52 so the |
| 209 | //hack will work |
| 210 | for ($n = 28; $n <= 31; $n++) { |
| 211 | $master_weeks_in_year = 52; |
| 212 | $WN_stamp = gmmktime(23, 59, 59, 12, $n, $master_year); |
| 213 | $master_weeks_in_year = (int) gmdate('W', $WN_stamp) > $master_weeks_in_year ? (int) gmdate('W', $WN_stamp) : |
| 214 | $master_weeks_in_year; |
| 215 | } |
| 216 | |
| 217 | $master_week_no_R = $master_week_no - $master_weeks_in_year - 1; //ISO-8601 week no. in year counting backwards |
| 218 | |
| 219 | //it's not the 4th of january, so increment the weekno. variables if the day of the week is monday |
| 220 | } |
| 221 | else { |
| 222 | if ($dow == 1) { |
| 223 | $master_week_no++; |
| 224 | $master_week_no_R++; |
| 225 | } |
| 226 | } |
| 227 | |
| 228 | //if the date is between december 28th and january 4th, then we need to manually calculate the weekno. |
| 229 | //variables, b/c days in this range can fall into either the last week of the year, or the first week |
| 230 | //of the next year, depending upon which year it is. |
| 231 | if (($master_month == 12 && $master_month_day > 28) || ($master_month == 1 && $master_month_day < 4)) { |
| 232 | $master_week_no = (int) gmdate('W', $counter); //generate the ISO-8601 week no. |
| 233 | |
| 234 | //if the weekno value calculates to 1, then the day is in week of the year we're changing to. if it's |
| 235 | //still december then push the temp year forward by one to ensure accurate calculation of the total |
| 236 | //number of weeks in this new year. don't forget to set $master_weeks_in_year to 52 again! |
| 237 | if ($master_week_no == 1) { |
| 238 | if ($master_month == 12) { |
| 239 | $master_year_temp = $master_year +1; |
| 240 | } |
| 241 | else { |
| 242 | $master_year_temp = $master_year; |
| 243 | } |
| 244 | for ($n = 28; $n <= 31; $n++) { |
| 245 | $master_weeks_in_year = 52; |
| 246 | $WN_stamp = gmmktime(23, 59, 59, 12, $n, $master_year_temp); |
| 247 | $master_weeks_in_year = (int) gmdate('W', $WN_stamp) > $master_weeks_in_year ? (int) gmdate('W', $WN_stamp) : |
| 248 | $master_weeks_in_year; |
| 249 | } |
| 250 | } |
| 251 | $master_week_no_R = $master_week_no - $master_weeks_in_year - 1; //ISO-8601 week no. in year counting backwards |
| 252 | } |
| 253 | |
| 254 | //make day of the week the custom string value, and adjust the reverse day in month value according to the current |
| 255 | //month's incrementer array |
| 256 | $day_of_the_week = $days_of_week[$dow]; |
| 257 | $day_of_the_month_R = $master_day_in_month_R - $day_in_month_incrementer[$dow]; |
| 258 | $day_stamp = gmdate('Y-m-d', $counter); |
| 259 | |
| 260 | //populate a new element of the values array with the RRULE data for the day in question |
| 261 | $values[] = "('$day_stamp', $counter,'$day_of_the_week','". $master_day_in_month.$day_of_the_week ."','". |
| 262 | $day_of_the_month_R.$day_of_the_week."','".$master_month_day."','".$master_month_day_R."','". |
| 263 | $master_month ."','". $master_year_day."','". $master_year_day_R."','".$master_week_no."','". $master_week_no_R."')"; |
| 264 | |
| 265 | //increment the bailout counter, and check to see if it's at 500. if so |
| 266 | //then send the query and bail out |
| 267 | $bailout++; |
| 268 | if ($bailout > $_BAILOUT_VALUE) { |
| 269 | //$result = db_query($insert_line.implode(',', $values)); |
| 270 | foreach ($values as $v) { |
| 271 | db_query($insert_line . $v); |
| 272 | } |
| 273 | return; |
| 274 | } |
| 275 | $counter += 86400; |
| 276 | |
| 277 | //here we start checks to see if we're at the end of the current month--if it's december then increment the |
| 278 | //year counter and reset leap year, and set the month values for january (we need to set the month day values |
| 279 | //one less than they actually are, b/c they'll be incremented when the code swings around) |
| 280 | if ($days_in_month == $master_month_day) { |
| 281 | if ($master_month == 12) { |
| 282 | $master_year++; |
| 283 | $leap_year = (int) gmdate('L', $counter); |
| 284 | $days_in_month = 31; |
| 285 | $master_month = 1; |
| 286 | $master_month_day = 0; |
| 287 | $master_month_day_R = -32; |
| 288 | |
| 289 | //set master day as the next day of the week in the cycle--this is the day of the week of the first |
| 290 | //day in january |
| 291 | if ($dow == 6) { |
| 292 | $master_day = 0; |
| 293 | } |
| 294 | else { |
| 295 | $master_day = $dow + 1; |
| 296 | } |
| 297 | |
| 298 | //set these 1 less than they need to be here, b/c they'll be incremented to their correct value on next |
| 299 | //cycle through |
| 300 | $master_day_in_month = 0; |
| 301 | $master_day_in_month_R = -5; |
| 302 | $day_in_month_incrementer = _eventrepeat_day_in_month_incrementer($master_day, $days_in_month); |
| 303 | $master_year_day = 0; |
| 304 | $master_year_day_R = -366 - $leap_year; |
| 305 | |
| 306 | //it's not december, so increment the month value, and set the new days in month and month day values |
| 307 | //once again the month day values need to be one less than their actual value |
| 308 | } |
| 309 | else { |
| 310 | $days_in_month = (int) gmdate('t', $counter); |
| 311 | $master_month++; |
| 312 | $master_month_day = 0; |
| 313 | $master_month_day_R = -$days_in_month -1; |
| 314 | |
| 315 | //set master day as the next day of the week in the cycle--this is the day of the week of the first |
| 316 | //day of the next month |
| 317 | if ($dow == 6) { |
| 318 | $master_day = 0; |
| 319 | } |
| 320 | else { |
| 321 | $master_day = $dow + 1; |
| 322 | } |
| 323 | |
| 324 | //set these 1 less than they need to be here, b/c they'll be incremented to their correct value on next |
| 325 | //cycle through, and reset the incrementer for the new month |
| 326 | $master_day_in_month = 0; |
| 327 | $master_day_in_month_R = -5; |
| 328 | $day_in_month_incrementer = _eventrepeat_day_in_month_incrementer($master_day, $days_in_month); |
| 329 | } |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | //we didn't bail out, so insert the new rows into the table, if there any rows to insert |
| 334 | if (count($values)) { |
| 335 | //$result = db_query($insert_line.implode(',', $values)); |
| 336 | foreach ($values as $v) { |
| 337 | db_query($insert_line . $v); |
| 338 | } |
| 339 | } |
| 340 | |
| 341 | //we've added new data to the calmap table, so render nodes for all repeat sequences |
| 342 | $endtime = $curtime + (variable_get('eventrepeat_initial_render', 90) * 86400); |
| 343 | _eventrepeat_render_nodes('all', $endtime, FALSE, FALSE, FALSE); |
| 344 | } |
| 345 | |
| 346 | /** |
| 347 | * Implementation of form API hook_elements() |
| 348 | */ |
| 349 | function eventrepeat_elements() { |
| 350 | $type['eventrepeat_date'] = array('#input' => TRUE, ); |
| 351 | return $type; |
| 352 | } |
| 353 | |
| 354 | /** |
| 355 | * Implementation of hook_form_alter(). |
| 356 | * |
| 357 | * @ingroup eventrepeat_core |
| 358 | * @param $form_id The form being altered. |
| 359 | * @param $form The form array. |
| 360 | */ |
| 361 | function eventrepeat_form_alter(&$form, $form_state, $form_id) { |
| 362 | |
| 363 | //check here to see if the node is part of a repeat sequence |
| 364 | if ($form_id == 'node_delete_confirm') { |
| 365 | $node = node_load($form['nid']['#value']); |
| 366 | if ($node->eventrepeat_rid) { |
| 367 | |
| 368 | //create the option array |
| 369 | $options = array( |
| 370 | 'this' => t('This occurrence only'), |
| 371 | 'future' => t('This occurrence and all future occurrences'), |
| 372 | 'all' => t('All occurrences') |
| 373 | ); |
| 374 | |
| 375 | //construct the radio buttons. NOTE: in order to prevent name collisions in the admin |
| 376 | //delete process for multiple nodes, these form elements are named in array fashion, with nid to distinguish |
| 377 | //the elements of the array |
| 378 | $form['eventrepeat_delete_type']['#tree'] = TRUE; |
| 379 | $form['eventrepeat_delete_type']['#weight'] = -12; |
| 380 | $form['eventrepeat_delete_type'][$node->nid] = array( |
| 381 | '#type' => 'radios', |
| 382 | '#title' => t('Repeat event--delete the following'), |
| 383 | '#default_value' => variable_get('eventrepeat_default_edit_type', 'future'), |
| 384 | '#options' => $options, |
| 385 | '#description' => t('\'This occurrence and all future occurrences\' will delete repeat events from the date of the selected node forward, \'All occurrences\' will delete repeat events after today\'s date.'), |
| 386 | |
| 387 | ); |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | //inject node type settings checkbox for repeat events |
| 392 | elseif ($form_id == 'node_type_form') { |
| 393 | $node_type = $form['old_type']['#value']; |
| 394 | $form['workflow']["eventrepeat_nodeapi"] = array( |
| 395 | '#type' => 'checkbox', |
| 396 | '#title' => t('Allow repeat events'), |
| 397 | '#default_value' => variable_get("eventrepeat_nodeapi_$node_type", FALSE), |
| 398 | '#description' => t('If selected, users will be allowed to add repeating events for this node type'), |
| 399 | ); |
| 400 | } |
| 401 | |
| 402 | //inject the appropriate repeat data into the node form if the node type is repeat enabled |
| 403 | elseif ($form['type']['#value'] .'_node_form' == $form_id) { |
| 404 | if (variable_get('event_nodeapi_'. $form['type']['#value'], 'never') != 'never' && |
| 405 | variable_get('eventrepeat_nodeapi_'. $form['type']['#value'], 0) == 1) { |
| 406 | |
| 407 | //get the node info from the db if not in edit (remember the edit means we are previewing, usually) |
| 408 | if (isset($_POST['edit']) && (!empty($_POST['edit']))) { |
| 409 | $edit = $_POST['edit']; |
| 410 | $node = (object) $edit; |
| 411 | } |
| 412 | elseif (isset($form['nid']['#value'])) { |
| 413 | $node = node_load($form['nid']['#value']); |
| 414 | } |
| 415 | |
| 416 | // If this is a new node and this user |
| 417 | // doesn't have access to create repeating events |
| 418 | // just return. |
| 419 | if (!$node->nid && !user_access('create repeat events')) { |
| 420 | return; |
| 421 | } |
| 422 | // If, however, this is an existing event that has |
| 423 | // a repeating pattern attached to it, we need to |
| 424 | // show the form because otherwise the user would |
| 425 | // end up breaking this node out of the repeat. |
| 426 | else if ($node->nid && (!$node->eventrepeat_rid && !user_access('create repeat events')) ) { |
| 427 | return; |
| 428 | } |
| 429 | |
| 430 | // TODO: I'm not sure if this is in the right spot, but this seems to be the only reliable place to put it right now |
| 431 | // adds a new exception to the list if needed |
| 432 | _eventrepeat_form_add_exception($node); |
| 433 | |
| 434 | // add the repeat pattern form elements |
| 435 | $form += theme('eventrepeat_form', $node); |
| 436 | |
| 437 | } |
| 438 | } |
| 439 | |
| 440 | /* this is a check for module dependencies. the only way we |
| 441 | can ensure this check happening when the module is initially |
| 442 | enabled is to insert the check for when the form is initially |
| 443 | built, which will also be caught when the admin/module page is |
| 444 | reloaded upon submission. this means we never want to call this |
| 445 | function when the form has been submitted, so make sure there's |
| 446 | no $_POST. */ |
| 447 | elseif ($form_id == 'system_modules' && !$_POST) { |
| 448 | _eventrepeat_system_module_validate($form); |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | /** |
| 453 | * Implementation of hook_help(). |
| 454 | * |
| 455 | * @ingroup eventrepeat_core |
| 456 | * @param $section The page which is requesting help. |
| 457 | * @return The help text. |
| 458 | */ |
| 459 | function eventrepeat_help($path, $arg) { |
| 460 | switch ($path) { |
| 461 | // General help page |
| 462 | case 'admin/help#eventrepeat': |
| 463 | $output = ''; |
| 464 | $output .= t('<p>Eventrepeat enables the creation of repeating event patterns for node types that are event-enabled. In order for repeating events to be created for a node type, you must first configure that node type to be enabled for repeating events. This is done from the <a href="!content_types">content types configuration page</a>. While you\'re in the configuration screen, check that the node type is also able to be viewed in the event calendar.</p>', array('!content_types' => url('admin/content/types'))); |
| 465 | $output .= _eventrepeat_user_help_content(TRUE); |
| 466 | $output .= t('<p>Eventrepeat\'s pattern creation was largely modeled on the iCal RRULE specification. At this time, it should support all RRULE parameters, with the following exceptions:</p>'); |
| 467 | $output .= t('<ol><li>Recurrance periods less than DAILY</li><li>BYDAY declarations greater than 5 and less than -5 (ex. 20th Monday of the year is not supported).Other similar patterns can be built that should approximate this functionality.</li> <li>BYSETPOS parameter</li><li>EXRULE parameter</li></ol>'); |
| 468 | return $output; |
| 469 | |
| 470 | // Settings page help |
| 471 | case 'admin/settings/eventrepeat': |
| 472 | return t('<p>Adjust how events repeat using the form below. To add the event repeat form to node forms, visit the <a href="!settings">content types page</a>.</p>', array('!settings' => url('admin/content/types'))); |
| 473 | } |
| 474 | } |
| 475 | |
| 476 | /** |
| 477 | * Implementation of hook_menu(). |
| 478 | * |
| 479 | * @ingroup eventrepeat_core |
| 480 | * @param $may_cache A boolean indicating whether cacheable menu items should be returned. |
| 481 | * @return An array of menu items. Each menu item is an associative array. |
| 482 | */ |
| 483 | function eventrepeat_menu() { |
| 484 | |
| 485 | $items['admin/settings/eventrepeat'] = array( |
| 486 | 'title' => 'Repeating Events', |
| 487 | 'description' => 'Change how repeating events are handled.', |
| 488 | 'page callback' => 'drupal_get_form', |
| 489 | 'page arguments' => array('eventrepeat_admin_settings'), |
| 490 | 'access arguments' => array('administer site configuration'), |
| 491 | 'type' => MENU_NORMAL_ITEM, // optional |
| 492 | 'weight' => 0 |
| 493 | ); |
| 494 | $items['admin/settings/eventrepeat/debug'] = array( |
| 495 | 'title' => 'Debug', |
| 496 | 'description' => 'Debug repeating events by seeing all patterns at a glance.', |
| 497 | 'page callback' => 'eventrepeat_debug', |
| 498 | 'access arguments' => array('administer site configuration'), |
| 499 | 'weight' => 0 |
| 500 | ); |
| 501 | $items['admin/settings/eventrepeat/reset'] = array( |
| 502 | 'title' => 'Reset', |
| 503 | 'description' => 'Reset the event repeat map.', |
| 504 | 'page callback' => 'drupal_get_form', |
| 505 | 'page arguments' => array('eventrepeat_refresh_calendar_map'), |
| 506 | 'access arguments' => array('administer site configuration'), |
| 507 | 'weight' => 1 |
| 508 | ); |
| 509 | $items['eventrepeat/help'] = array( |
| 510 | 'title' => 'How to create repeating events', |
| 511 | 'description' => 'How to create repeating events.', |
| 512 | 'page callback' => 'eventrepeat_user_help_page', |
| 513 | 'access arguments' => array('access content'), |
| 514 | 'type' => MENU_CALLBACK, |
| 515 | ); |
| 516 | return $items; |
| 517 | } |
| 518 | |
| 519 | /** |
| 520 | * Implementation of hook_perm(). |
| 521 | * @ingroup eventrepeat_core |
| 522 | */ |
| 523 | function eventrepeat_perm() { |
| 524 | return array('create repeat events'); |
| 525 | } |
| 526 | |
| 527 | /** |
| 528 | * @defgroup eventrepeat_nodeapi Functions for nodeapi integration |
| 529 | */ |
| 530 | |
| 531 | /** |
| 532 | * hook_nodeapi implementation |
| 533 | * |
| 534 | * @ingroup eventrepeat_nodeapi |
| 535 | * @param &$node The node the action is being performed on. |
| 536 | * @param $op What kind of action is being performed. |
| 537 | * @return This varies depending on the operation. |
| 538 | */ |
| 539 | function eventrepeat_nodeapi(&$node, $op) { |
| 540 | |
| 541 | // only continue if this node is event and event_repeat enabled |
| 542 | if (variable_get('event_nodeapi_'. $node->type, 'never') == 'never' && |
| 543 | variable_get('eventrepeat_nodeapi_'. $node->type, 0) == 0) { |
| 544 | return; |
| 545 | } |
| 546 | |
| 547 | //this holds the old start time of an event, for use in mass updates |
| 548 | static $old_times; |
| 549 | |
| 550 | //for all ops except settings, make sure that the node is event & eventrepeat enabled before executing |
| 551 | switch ($op) { |
| 552 | |
| 553 | case 'validate': |
| 554 | // no break. both need a node with a formatted date and event_start |
| 555 | // and event_end set, 'validate' for the previewing and 'submit' for |
| 556 | // update/insert. |
| 557 | |
| 558 | case 'presave': |
| 559 | |
| 560 | // TODO: what if the user wants to "turn off" the repeating event |
| 561 | // the user is trying to turn off repeating events for all future events |
| 562 | if (($node->eventrepeat_FREQ == '' || $node->eventrepeat_FREQ == 'NONE') && ($node->eventrepeat_edit_type == 'future' || $node->eventrepeat_edit_type == 'all')) { |
| 563 | form_set_error('eventrepeat_FREQ', t('Trying to remove a repeat setting from a repeating event is not currently supported. Please try deleting the events you don\'t want instead.')); |
| 564 | } |
| 565 | |
| 566 | //validate the repeat end date |
| 567 | _eventrepeat_validate_form_date('eventrepeat_end', t('Repeat end'), $node); |
| 568 | |
| 569 | // TODO: this would cause an odd error if the person wanted |
| 570 | // to remove the repeating sequence so I removed it for now. |
| 571 | //if they have some repeat options chosen, |
| 572 | // make sure a repeat type is selected |
| 573 | /* |
| 574 | if ($node->eventrepeat_FREQ == 'NONE' && |
| 575 | ($node->eventrepeat_COUNT |
| 576 | || $node->eventrepeat_INTERVAL > 1 |
| 577 | || $node->eventrepeat_BYDAY |
| 578 | || $node->eventrepeat_BYWEEKNO |
| 579 | || $node->eventrepeat_BYMONTH |
| 580 | || $node->eventrepeat_BYMONTHDAY |
| 581 | || $node->eventrepeat_BYYEARDAY |
| 582 | || $node->eventrepeat_EXDATE) |
| 583 | ) { |
| 584 | form_set_error('eventrepeat_FREQ', t('You must select a Repeat Type if you want this event to repeat.')); |
| 585 | } |
| 586 | */ |
| 587 | |
| 588 | //warn if user entered values for both repeat end and COUNT |
| 589 | if ($node->eventrepeat_end && $node->eventrepeat_COUNT) { |
| 590 | form_set_error('eventrepeat_end', t('\'Repeat end date\' and \'count\' cannot both be set--select only one to provide a valid end point for the sequence')); |
| 591 | } |
| 592 | |
| 593 | // TODO: if this is unsupported, can we just use end date after the initial render? |
| 594 | //warn if user tries to edit a sequence based on COUNT--this is currently not supported |
| 595 | if ($node->eventrepeat_rid && $node->eventrepeat_COUNT) { |
| 596 | form_set_error('eventrepeat_COUNT', t('Editing a sequence which uses the \'count\' parameter is not currently supported. You may need to delete this event.')); |
| 597 | } |
| 598 | |
| 599 | break; |
| 600 | |
| 601 | case 'delete': |
| 602 | static $mass_delete; |
| 603 | global $form_values; |
| 604 | //determine what kind of delete we're doing here. check $form_values to see if it's a mass delete |
| 605 | //and mark it as such if so. otherwise just delete the node from the detail table |
| 606 | if ($form_values['eventrepeat_delete_type'][$node->nid] && |
| 607 | $form_values['eventrepeat_delete_type'][$node->nid] != 'this') { |
| 608 | |
| 609 | $mass_delete = TRUE; |
| 610 | _eventrepeat_delete_nodes($form_values['eventrepeat_delete_type'][$node->nid], $node); |
| 611 | //this line necessary to clean up mods after eventrepeat |
| 612 | node_invoke_nodeapi($node, 'delete'); |
| 613 | } |
| 614 | else { |
| 615 | db_query("DELETE FROM {eventrepeat_nodes} WHERE nid = %d", $node->nid); |
| 616 | |
| 617 | //if not a mass delete, check to see if this is the last repeat event in this sequence. if so, then |
| 618 | //delete the sequence |
| 619 | if (!$mass_delete) { |
| 620 | db_query("DELETE FROM {eventrepeat} WHERE rid = %d", $node->eventrepeat_rid); |
| 621 | } |
| 622 | } |
| 623 | |
| 624 | |
| 625 | break; |
| 626 | case 'load': |
| 627 | |
| 628 | if (variable_get('event_nodeapi_'. $node->type, 'never') != 'never') { |
| 629 | |
| 630 | //if the old start time hasn't been saved to the update code yet, then save it. this is a total hack, |
| 631 | //but i don't know any other way to do it |
| 632 | if (!$old_times && $form_state['values']['op'] == t('Submit')) { |
| 633 | $old_times = db_query('SELECT e.event_start, e.event_end, e.timezone FROM {event} e WHERE e.nid = %d', $node->nid); |
| 634 | if (db_num_rows($old_times)) { |
| 635 | $old_times = db_fetch_object($old_times); |
| 636 | $old_times->eventrepeat_old_times = TRUE; |
| 637 | _eventrepeat_update_nodes($old_times, NULL); |
| 638 | } |
| 639 | } |
| 640 | |
| 641 | //if it's a repeat node, grab the repeat data for the node |
| 642 | if (variable_get('eventrepeat_nodeapi_'. $node->type, 0) == 1) { |
| 643 | $object = db_fetch_object(db_query('SELECT e_r.rid, repeat_RRULE, repeat_end |
| 644 | FROM {eventrepeat} e_r INNER JOIN {eventrepeat_nodes} e_r_n ON |
| 645 | e_r.rid = e_r_n.rid WHERE e_r_n.nid = %d', $node->nid)); |
| 646 | |
| 647 | //if this node is in a repeat sequence, parse the RRULE, and return repeat data to the node |
| 648 | if ($object->rid) { |
| 649 | |
| 650 | //append the repeat tag to the title if it's an event page |
| 651 | if (arg(0) == 'event') { |
| 652 | $node->title = theme("eventrepeat_title_tag", $node->title); |
| 653 | } |
| 654 | $items = _eventrepeat_parse_ical($object->repeat_RRULE); |
| 655 | return array('eventrepeat_rid' => $object->rid, |
| 656 | 'eventrepeat_FREQ' => $items[0]['FREQ'], |
| 657 | 'eventrepeat_COUNT' => $items[0]['COUNT'], |
| 658 | 'eventrepeat_INTERVAL' => $items[0]['INTERVAL'], |
| 659 | 'eventrepeat_BYDAY' => $items[0]['BYDAY'], |
| 660 | 'eventrepeat_BYWEEKNO' => $items[0]['BYWEEKNO'], |
| 661 | 'eventrepeat_BYMONTH' => $items[0]['BYMONTH'], |
| 662 | 'eventrepeat_BYMONTHDAY' => $items[0]['BYMONTHDAY'], |
| 663 | 'eventrepeat_BYYEARDAY' => $items[0]['BYYEARDAY'], |
| 664 | 'eventrepeat_EXDATE' => $items[0]['EXDATE'], |
| 665 | 'eventrepeat_end' => $object->repeat_end, // placeholder in case the end date processing changes |
| 666 | 'eventrepeat_endyear' => $object->repeat_end ? _event_date('Y', $object->repeat_end) : 0, |
| 667 | 'eventrepeat_endmonth' => $object->repeat_end ? _event_date('n', $object->repeat_end) : 0, |
| 668 | 'eventrepeat_endday' => $object->repeat_end ? _event_date('d', $object->repeat_end) : 0 |
| 669 | ); |
| 670 | } |
| 671 | } |
| 672 | } |
| 673 | break; |
| 674 | |
| 675 | case 'insert': |
| 676 | |
| 677 | // add a new exception to the list if needed |
| 678 | _eventrepeat_form_add_exception($node); |
| 679 | |
| 680 | // if this node has eventrepeat_FREQ info |
| 681 | // remember that we strip this for new instances |
| 682 | // in _eventrepeat_render_nodes |
| 683 | if ($node->eventrepeat_FREQ != '' && $node->eventrepeat_FREQ != 'NONE') { |
| 684 | _eventrepeat_save_repeat($node); |
| 685 | } |
| 686 | |
| 687 | break; |
| 688 | |
| 689 | |
| 690 | case 'update': |
| 691 | //setting a static indicator here--this case will more than likely get called |
| 692 | //multiple times as nodes are updated, but we only want the repeat update |
| 693 | //itself to run once per page load. |
| 694 | static $update_run = NULL; |
| 695 | if (!isset($update_run)) { |
| 696 | $update_run = TRUE; |
| 697 | // add a new exception to the list if needed |
| 698 | _eventrepeat_form_add_exception($node); |
| 699 | |
| 700 | //determine what kind of update we're doing here. it's either all (from current date |
| 701 | //forward), future (current node and all future nodes) or just an individual node update |
| 702 | //pass all and future to the mass edit code |
| 703 | if ($node->eventrepeat_edit_type == 'future') { |
| 704 | //we have to update the existing nodes first before we update the repeat pattern |
| 705 | //only update the repeat pattern if the node update was successful. |
| 706 | if (_eventrepeat_update_nodes($node, $node->event_start) === TRUE) { |
| 707 | _eventrepeat_save_repeat($node); |
| 708 | } |
| 709 | } |
| 710 | elseif ($node->eventrepeat_edit_type == 'all') { |
| 711 | //we have to update the existing nodes first before we update the repeat pattern |
| 712 | //only update the repeat pattern if the node update was successful. |
| 713 | if (_eventrepeat_update_nodes($node, time()) === TRUE) { |
| 714 | _eventrepeat_save_repeat($node); |
| 715 | } |
| 716 | } |
| 717 | elseif ($node->eventrepeat_edit_type == 'this') { |
| 718 | //if it's an individual edit, and admin has selected that they are to be removed from sequences, |
| 719 | //then delete from detail table |
| 720 | if (!variable_get('eventrepeat_single_edit_in_sequence', FALSE)) { |
| 721 | db_query("DELETE FROM {eventrepeat_nodes} WHERE nid = %d", $node->nid); |
| 722 | } |
| 723 | } |
| 724 | // the user is turning a non-repeat event into a repeat event |
| 725 | elseif (($node->eventrepeat_FREQ != '' && $node->eventrepeat_FREQ != 'NONE') && !$node->eventrepeat_rid) { |
| 726 | _eventrepeat_save_repeat($node); |
| 727 | } |
| 728 | } |
| 729 | break; |
| 730 | |
| 731 | |
| 732 | case 'view': |
| 733 | break; |
| 734 | } |
| 735 | } |
| 736 | |
| 737 | /** |
| 738 | * @defgroup eventrepeat_event Functions which use hooks in event.module |
| 739 | */ |
| 740 | |
| 741 | /** |
| 742 | * hook_event_edit_upcoming implementation |
| 743 | * |
| 744 | * @ingroup eventrepeat_event |
| 745 | * @param $node The node the action is being performed on (not a full node). |
| 746 | * @return None--edits are made directly to the $node object prior to rendering |
| 747 | */ |
| 748 | function eventrepeat_event_edit_upcoming(&$node) { |
| 749 | |
| 750 | //if this node appears in the detail table, then add the repeat tag to it |
| 751 | if (db_result(db_query('SELECT nid FROM {eventrepeat_nodes} WHERE nid = %d', $node->nid))) { |
| 752 | $node->title = theme("eventrepeat_title_tag", $node->title); |
| 753 | } |
| 754 | } |
| 755 | |
| 756 | /** |
| 757 | * hook_event_load implementation |
| 758 | * |
| 759 | * @ingroup eventrepeat_event |
| 760 | * @param $year The year of the rendered calendar view. |
| 761 | * @param $month The month of the rendered calendar view. |
| 762 | * @param $day The day of the rendered calendar view. No leading zeroes. |
| 763 | * @param $view The calendar view being rendered |
| 764 | * @param $types unused |
| 765 | * @param $terms unused |
| 766 | * @return None--allows for node creation which only. |
| 767 | */ |
| 768 | function eventrepeat_event_load($year, $month, $day, $view, $types, $terms) { |
| 769 | |
| 770 | //only should be run once per page call |
| 771 | static $execute; |
| 772 | if (!$execute) { |
| 773 | $execute = 1; |
| 774 | |
| 775 | //default $view to either the default view setting, or 'month' if it's not set |
| 776 | $view = $view ? $view : variable_get('event_overview', 'month'); |
| 777 | |
| 778 | //first thing is to determine the exact range that's going to be rendered, and calculate start and end timestamps |
| 779 | $calendar_period_start = _event_mktime(23, 59, 59, $month, $day, $year); |
| 780 | switch ($view) { |
| 781 | case 'day': |
| 782 | $endtime = $calendar_period_start; |
| 783 | break; |
| 784 | |
| 785 | case 'week': |
| 786 | //calculate the end of the week (not perfect, but close enough!) |
| 787 | $endtime = $calendar_period_start + (86400*7); |
| 788 | break; |
| 789 | |
| 790 | case 'month': |
| 791 | //generate the end date based on number of days in the month |
| 792 | $endtime = gmmktime(23, 59, 59, $month, gmdate('t', $calendar_period_start), $year); |
| 793 | break; |
| 794 | |
| 795 | case 'table': |
| 796 | case 'list': |
| 797 | //table/list end is simply the start day of the viewing period plus the event_table_duration. if duration |
| 798 | //is specified as an argument, then make sure it's not longer than a year. if not, then just use the |
| 799 | //table duration default or 30 days |
| 800 | $duration = arg(7); |
| 801 | $numberofdays = $duration && $duration < 366 ? $duration : variable_get('event_table_duration', '30'); |
| 802 | $endtime = gmmktime(23, 59, 59, $month, $day + $numberofdays, $year); |
| 803 | break; |
| 804 | |
| 805 | default: |
| 806 | return; |
| 807 | } |
| 808 | |
| 809 | //check here to see if the user is viewing a date outside of the rendering range. if so, warn and exit |
| 810 | $curtime = time(); |
| 811 | $curtime_end = gmmktime(23, 59, 59, (int) gmdate('n', $curtime), (int) gmdate('j', $curtime), |
| 812 | (int) gmdate('Y', $curtime)); |
| 813 | $render_support = $curtime_end + (variable_get('eventrepeat_render_support', 2000) * 86400); |
| 814 | if ($endtime > $render_support) { |
| 815 | drupal_set_message(t('This calendar view is outside of the range of repeat event support')); |
| 816 | return; |
| 817 | } |
| 818 | |
| 819 | //check here to see if the calendar is outside of the initial render range. if so, then render all repeat |
| 820 | //sequences through the end time of the calendar period |
| 821 | $past_initial_render = $curtime_end - 86400 + (variable_get('eventrepeat_initial_render', 90) * 86400); |
| 822 | if ($endtime > $past_initial_render) { |
| 823 | _eventrepeat_render_nodes('all', $endtime, FALSE, FALSE, FALSE); |
| 824 | } |
| 825 | } |
| 826 | } |
| 827 | |
| 828 | /** |
| 829 | * @defgroup eventrepeat_support Functions that support the eventrepeat system |
| 830 | */ |
| 831 | |
| 832 | /** |
| 833 | * Helper function that corrects for date GMT date changes based on timezone |
| 834 | * |
| 835 | * @ingroup eventrepeat_support |
| 836 | * @param $starthour The start hour of the event. |
| 837 | * @param $startminute The start minute of the event. |
| 838 | * @param $offset The timezone offset of the event. |
| 839 | * @return Day offset in seconds. |
| 840 | */ |
| 841 | function _eventrepeat_day_correction($starthour, $startminute, $offset) { |
| 842 | |
| 843 | //if the GMT day for the event falls on a different day than the local day, we have to account for that |
| 844 | //since the event_repeat_calendar map table has all of it's day info in GMT |
| 845 | //here we check that by adding the timezone offset to the starthour--if the result is less than zero, then |
| 846 | //we need to add a day to the rendering date, and if it's greater than 24, we need to subtract a day from the |
| 847 | //rendering date |
| 848 | if ($offset > 0) { |
| 849 | $day_offset = (((($starthour * 3600) + ($startminute * 60)) + $offset) > 86400) ? -86400 : 0; |
| 850 | } |
| 851 | elseif ($offset < 0) { |
| 852 | $day_offset = (((($starthour * 3600) + ($startminute * 60)) + $offset) < 0) ? 86400 : 0; |
| 853 | } |
| 854 | else { |
| 855 | $day_offset = 0; |
| 856 | } |
| 857 | |
| 858 | return $day_offset; |
| 859 | } |
| 860 | |
| 861 | /** |
| 862 | * Helper function that gives information on the number of each of the days in the month for each of the seven days |
| 863 | * |
| 864 | * @ingroup eventrepeat_support |
| 865 | * @param $master_day The day of the week of the first day of the month. |
| 866 | * @param $days_in_month The number of days in the month. |
| 867 | * @return An array in the same order as the days of the week (Sun-Sat), with values of 0 for days of the week with 4 occurrences, and 1 for days of the week with 5 occurrences. |
| 868 | */ |
| 869 | function _eventrepeat_day_in_month_incrementer($master_day, $days_in_month) { |
| 870 | |
| 871 | //some days of the week in a month occur 4 times, and some occur 5 times. here we're building an array that will |
| 872 | //store data of this kind for the current month. any days of the week that occur five times in the month are given |
| 873 | //a value of 1 in this array, so that the $master_day_in_month_R counter can be adjusted correctly for these days |
| 874 | $day_in_month_incrementer = array(0, 0, 0, 0, 0, 0, 0); |
| 875 | for ($i = 29; $i <= $days_in_month; $i++) { |
| 876 | $day_in_month_incrementer[$master_day] = 1; |
| 877 | $master_day == 6 ? $master_day = 0 : $master_day++; |
| 878 | } |
| 879 | return $day_in_month_incrementer; |
| 880 | } |
| 881 | |
| 882 | /** |
| 883 | * Helper function that handles mass deletion of nodes in a repeat sequence |
| 884 | * |
| 885 | * @ingroup eventrepeat_support |
| 886 | * @param $op The operation to be performed (either 'this', 'all', or 'future'). |
| 887 | * @param $node The root node which the mass delete operation is based on. |
| 888 | * @return None. |
| 889 | */ |
| 890 | function _eventrepeat_delete_nodes($op, $node) { |
| 891 | |
| 892 | global $form_values; |
| 893 | |
| 894 | //unset eventrepeat_delete_type for this nid to prevent this function from being called mutliple times |
| 895 | //also set the current GMT timestamp |
| 896 | unset($form_values['eventrepeat_delete_type'][$node->nid]); |
| 897 | $curtime = time(); |
| 898 | |
| 899 | //delete the entry in the repeat_nodes table for the current node being deleted |
| 900 | db_query("DELETE FROM {eventrepeat_nodes} WHERE nid = %d", $node->nid); |
| 901 | |
| 902 | //set the proper timestamp for the nodes to delete query. for 'all' this will be from the current |
| 903 | //date forward, and for 'future', it will be from the node's event->start time--if this is an update |
| 904 | //delete, it will be just after the passed in update end time |
| 905 | if ($op == 'all') { |
| 906 | $timestamp = $curtime; |
| 907 | } |
| 908 | elseif ($op == 'future') { |
| 909 | $timestamp = $node->event_start; |
| 910 | } |
| 911 | |
| 912 | //pull all of the nids for nodes that are to be deleted from this sequence |
| 913 | $result = db_query("SELECT e_r_n.nid FROM {eventrepeat_nodes} e_r_n INNER JOIN {event} e ON |
| 914 | e_r_n.nid = e.nid WHERE e_r_n.rid = %d AND e.event_start >= %d", $node->eventrepeat_rid, $timestamp); |
| 915 | |
| 916 | //loop through the nids, calling node_delete for all nodes in the result set |
| 917 | while ($node_to_delete = db_fetch_object($result)) { |
| 918 | node_delete($node_to_delete->nid); |
| 919 | } |
| 920 | |
| 921 | //pull the start times for the remaining nodes in this repeat sequence |
| 922 | $result = db_query("SELECT e.event_start FROM {eventrepeat_nodes} e_r_n INNER JOIN {event} e ON |
| 923 | e_r_n.nid = e.nid WHERE e_r_n.rid = %d ORDER BY e.event_start DESC", $node->eventrepeat_rid); |
| 924 | $num_rows = FALSE; |
| 925 | if ($last_rendered = db_fetch_object($result)) { |
| 926 | //there are still nodes in the repeat sequence--in this case we want to keep the repeat sequence intact |
| 927 | //since it may still need to be mass edited/deleted, but we also want to end creation of any new nodes, |
| 928 | //so here we grab the start date of the latest node in the sequence and set the end date of the sequence equal |
| 929 | //to it. |
| 930 | $last_rendered = gmmktime(23, 59, 59, |
| 931 | (int) gmdate('n', $last_rendered->event_start), |
| 932 | (int) gmdate('j', $last_rendered->event_start), |
| 933 | (int) gmdate('Y', $last_rendered->event_start) |
| 934 | ); |
| 935 | db_query("UPDATE {eventrepeat} SET repeat_end = %d, repeat_last_rendered = %d WHERE rid = %d", |
| 936 | $last_rendered, $last_rendered, $node->eventrepeat_rid); |
| 937 | } |
| 938 | else { |
| 939 | //no nodes left in the sequence, so delete the repeat data |
| 940 | db_query("DELETE FROM {eventrepeat} WHERE rid = %d", $node->eventrepeat_rid); |
| 941 | } |
| 942 | } |
| 943 | |
| 944 | /** |
| 945 | * Generates the repeat setting form under the repeat tab, and saves repeat data to the repeat tables. |
| 946 | * |
| 947 | * @ingroup eventrepeat_support |
| 948 | * @param $node Either the $node object from the db or the node object from $_POST['edit'] |
| 949 | * @return Fully themed page containing the repeat setting form. |
| 950 | */ |
| 951 | function theme_eventrepeat_form($node) { |
| 952 | |
| 953 | //if the node is part of a repeat sequence, then construct the radio buttons for mass edit |
| 954 | // TODO: review default change: moved to default of future because |
| 955 | // otherwise I found that users would keep trying to change exceptions and |
| 956 | // make other repeat pattern updates on occurrences by accident. |
| 957 | // The "this" options is also the most destructive of the three. |
| 958 | // The "future" maps a bit better to how iCal works. |
| 959 | if ($node->eventrepeat_rid) { |
| 960 | $form['eventrepeat_rid'] = array('#type' => 'hidden', '#value' => $node->eventrepeat_rid); |
| 961 | $options = array( |
| 962 | 'this' => t('This occurrence only'), |
| 963 | 'future' => t('This occurrence and all future occurrences'), |
| 964 | 'all' => t('All occurrences') |
| 965 | ); |
| 966 | $form['eventrepeat_edit_type'] = array( |
| 967 | '#type' => 'radios', |
| 968 | '#title' => t('Apply edit(s) to'), |
| 969 | '#default_value' => variable_get('eventrepeat_default_edit_type', 'future'), |
| 970 | '#options' => $options, |
| 971 | '#description' => t('\'This occurrence and all future occurrences\' will edit repeat events from the date of the selected node forward, \'All occurrences\' will edit repeat events after today\'s date. <br />Note: editing a single occurrence will remove it from the repeat sequence.'), |
| 972 | '#weight' => -12 |
| 973 | ); |
| 974 | } |
| 975 | |
| 976 | // figure out what we are expanding and create the main fieldset |
| 977 | $exception_collapsed = ($node->eventrepeat_EXDATE) ? FALSE : TRUE; |
| 978 | $advanced_collapsed = ($node->eventrepeat_INTERVAL > 1 |
| 979 | || $node->eventrepeat_BYDAY |
| 980 | || $node->eventrepeat_BYMONTH |
| 981 | || $node->eventrepeat_BYMONTHDAY |
| 982 | || $node->eventrepeat_BYYEARDAY |
| 983 | || $node->eventrepeat_BYWEEKNO |
| 984 | ) ? FALSE: TRUE; |
| 985 | $collapsed = TRUE; |
| 986 | if (($node->eventrepeat_FREQ != '' && $node->eventrepeat_FREQ != 'NONE') |
| 987 | || $exception_collapsed == FALSE |
| 988 | || $advanced_collapsed == FALSE |
| 989 | ) { |
| 990 | $collapsed = FALSE; |
| 991 | } |
| 992 | $help = '<p>'. l(t('Need help creating a repeat pattern?'), 'eventrepeat/help', array('target' => '_blank')) .'<br />'. t('NOTE: Editing an existing repeat pattern maps previously created events to the new pattern, in sequential order, on all dates from the date where the edit is performed.') .'</p>'; |
| 993 | $form['eventrepeat'] = array( |
| 994 | '#type' => 'fieldset', |
| 995 | '#title' => t('Repeat'), |
| 996 | '#tree' => FALSE, |
| 997 | '#collapsible' => TRUE, |
| 998 | '#collapsed' => $collapsed, |
| 999 | '#weight' => -12, |
| 1000 | '#description' => $help, |
| 1001 | ); |
| 1002 | |
| 1003 | //link to user help for repeat patterns |
| 1004 | //$form['eventrepeat_advanced']['help'] = array('#type' => 'markup', '#value' => '<p>'. l(t('Need help creating a repeat pattern?'), 'eventrepeat/help') .'<br />'. t('NOTE: Editing an existing repeat pattern maps previously created events to the new pattern, in sequential order, on all dates from the date where the edit is performed.') .'</p>'); |
| 1005 | |
| 1006 | |
| 1007 | //FREQ param select box |
| 1008 | $options = array( |
| 1009 | 'NONE' => t('none'), |
| 1010 | 'DAILY' => t('Daily'), |
| 1011 | 'WEEKLY' => t('Weekly'), |
| 1012 | 'MONTHLY' => t('Monthly'), |
| 1013 | 'YEARLY' => t('Yearly') |
| 1014 | ); |
| 1015 | $form['eventrepeat']['eventrepeat_FREQ'] = array( |
| 1016 | '#type' => 'select', |
| 1017 | '#title' => t('Repeat type'), |
| 1018 | '#default_value' => $node->eventrepeat_FREQ ? $node->eventrepeat_FREQ : 'NONE', |
| 1019 | '#options' => $options, |
| 1020 | '#description' => t('select \'none\' to disable repeats for this event') |
| 1021 | ); |
| 1022 | |
| 1023 | //put end controls in a group to make them less confusing |
| 1024 | $form['eventrepeat']['end_controls'] = array( |
| 1025 | '#type' => 'fieldset', |
| 1026 | '#title' => t('End Settings'), |
| 1027 | '#collapsible' => TRUE, |
| 1028 | '#description' => t('Select either the end date or the number of times you want this event to repeat.') |
| 1029 | ); |
| 1030 | |
| 1031 | //date box for end date |
| 1032 | $form['eventrepeat']['end_controls']['end_date'] = array( |
| 1033 | '#type' => 'eventrepeat_date', |
| 1034 | '#title' => t('Repeat end date'), |
| 1035 | '#process' => array('_eventrepeat_form_date' => array($node, 'eventrepeat_end')) |
| 1036 | ); |
| 1037 | |
| 1038 | // the - or - markup |
| 1039 | $form['eventrepeat']['end_controls']['or'] = array('#type' => 'markup', '#value' => '<b>---'. t('OR') .'---</b>'); |
| 1040 | |
| 1041 | // TODO: if editing based on count is unsupported, can we just use end date after the initial render? |
| 1042 | //COUNT param select box |
| 1043 | $options = array(0 => '--'. t('Select') .'--'); |
| 1044 | for ($i = 2; $i <= 100; $i++) { |
| 1045 | $options[$i] = $i; |
| 1046 | } |
| 1047 | $form['eventrepeat']['end_controls']['eventrepeat_COUNT'] = array( |
| 1048 | '#type' => 'select', |
| 1049 | '#title' => t('count'), |
| 1050 | '#default_value' => $node->eventrepeat_COUNT ? $node->eventrepeat_COUNT : 0, |
| 1051 | '#options' => $options, |
| 1052 | '#description' => t('Determines the number of repeat nodes that will be created for the repeat sequence') |
| 1053 | ); |
| 1054 | |
| 1055 | // Add the advanced options |
| 1056 | $form['eventrepeat'][] = theme('eventrepeat_form_advanced', $node, $advanced_collapsed); |
| 1057 | |
| 1058 | // Removed the exception form because it wasn't working right. You can just delete the instances you want anyway. |
| 1059 | //$form['eventrepeat'][] = theme('eventrepeat_form_exception', $node, $exception_collapsed); |
| 1060 | |
| 1061 | return $form; |
| 1062 | |
| 1063 | } |
| 1064 | |
| 1065 | /** |
| 1066 | * theme the advanced fieldset of form elements |
| 1067 | */ |
| 1068 | function theme_eventrepeat_form_advanced($node, $advanced_collapsed) { |
| 1069 | $options = array(); |
| 1070 | |
| 1071 | $show_advanced = variable_get('eventrepeat_showadvanced', array()); |
| 1072 | if ($show_advanced['eventrepeat_INTERVAL'] == FALSE |
| 1073 | && $show_advanced['eventrepeat_BYDAY'] == FALSE |
| 1074 | && $show_advanced['eventrepeat_BYWEEKNO'] == FALSE |
| 1075 | && $show_advanced['eventrepeat_BYMONTH'] == FALSE |
| 1076 | && $show_advanced['eventrepeat_BYMONTHDAY'] == FALSE |
| 1077 | && $show_advanced['eventrepeat_BYYEARDAY'] == FALSE |
| 1078 | ) { |
| 1079 | return; |
| 1080 | } |
| 1081 | $form = array(); |
| 1082 | |
| 1083 | // start the advanced fieldset |
| 1084 | $form['eventrepeat_advanced'] = array( |
| 1085 | '#type' => 'fieldset', |
| 1086 | '#title' => t('Advanced'), |
| 1087 | '#collapsible' => TRUE, |
| 1088 | '#collapsed' => $advanced_collapsed, |
| 1089 | '#tree' => FALSE, |
| 1090 | ); |
| 1091 | |
| 1092 | //INTERVAL param select box |
| 1093 | if ($show_advanced['eventrepeat_INTERVAL']) { |
| 1094 | for ($i = 1; $i <= 60; $i++) { |
| 1095 | $options[$i] = $i; |
| 1096 | } |
| 1097 | $form['eventrepeat_advanced']['eventrepeat_INTERVAL'] = array( |
| 1098 | '#type' => 'select', |
| 1099 | '#title' => t('Interval'), |
| 1100 | '#default_value' => ($node->eventrepeat_INTERVAL) ? $node->eventrepeat_INTERVAL : 1, '#options' => $options, |
| 1101 | '#description' => t('Frequency of repeat: 1 = every, 2 = every other, 3 = every 3rd, etc.') |
| 1102 | ); |
| 1103 | } |
| 1104 | |
| 1105 | //set days of the week array, and copy it into $dow |
| 1106 | $options = array( |
| 1107 | 'SU' => t('Sunday'), |
| 1108 | 'MO' => t('Monday'), |
| 1109 | 'TU' => t('Tuesday'), |
| 1110 | 'WE' => t('Wednesday'), |
| 1111 | 'TH' => t('Thursday'), |
| 1112 | 'FR' => t('Friday'), |
| 1113 | 'SA' => t('Saturday') |
| 1114 | ); |
| 1115 | $dow = $options; |
| 1116 | |
| 1117 | //set the count options array |
| 1118 | $dowcount = array( |
| 1119 | '1' => t('1st'), |
| 1120 | '2' => t('2nd'), |
| 1121 | '3' => t('3rd'), |
| 1122 | '4' => t('4th'), |
| 1123 | '5' => t('5th'), |
| 1124 | '-1' => t('Last'), |
| 1125 | '-2' => t('Next to Last'), |
| 1126 | '-3' => t('2nd from Last'), |
| 1127 | '-4' => t('3rd from Last'), |
| 1128 | '-5' => t('4th from Last') |
| 1129 | ); |
| 1130 | |
| 1131 | //loop through each day of the week, looping through each count option, and build an array of all combinations |
| 1132 | foreach ($dow as $dowkey => $dowvalue) { |
| 1133 | foreach ($dowcount as $countkey => $countvalue) { |
| 1134 | $options[$countkey . $dowkey] = $countvalue .' '. $dowvalue; |
| 1135 | } |
| 1136 | } |
| 1137 | |
| 1138 | //BYDAY param select box |
| 1139 | if ($show_advanced['eventrepeat_BYDAY']) { |
| 1140 | $form['eventrepeat_advanced']['eventrepeat_BYDAY'] = array( |
| 1141 | '#type' => 'select', |
| 1142 | '#title' => t('Day(s)'), |
| 1143 | '#default_value' => $node->eventrepeat_BYDAY ? $node->eventrepeat_BYDAY : '', |
| 1144 | '#options' => $options, '#description' => t('Determines what day(s) of the week/month this event repeats on (by day of the week). Lots of options available, scroll down!'), |
| 1145 | '#attributes' => array('size' => '7'), |
| 1146 | '#multiple' => TRUE |
| 1147 | ); |
| 1148 | } |
| 1149 | |
| 1150 | |
| 1151 | //BYMONTH param select box |
| 1152 | if ($show_advanced['eventrepeat_BYMONTH']) { |
| 1153 | $options = array( |
| 1154 | 1 => t('January'), |
| 1155 | 2 => t('February'), |
| 1156 | 3 => t('March'), |
| 1157 | 4 => t('April'), |
| 1158 | 5 => t('May'), |
| 1159 | 6 => t('June'), |
| 1160 | 7 => t('July'), |
| 1161 | 8 => t('August'), |
| 1162 | 9 => t('September'), |
| 1163 | 10 => t('October'), |
| 1164 | 11 => t('November'), |
| 1165 | 12 => t('December'), |
| 1166 | ); |
| 1167 | $form['eventrepeat_advanced']['eventrepeat_BYMONTH'] = array( |
| 1168 | '#type' => 'select', |
| 1169 | '#title' => t('Month(s)'), |
| 1170 | '#default_value' => $node->eventrepeat_BYMONTH ? $node->eventrepeat_BYMONTH : '', |
| 1171 | '#options' => $options, |
| 1172 | '#description' => t('Selects what month(s) of the year this event repeats on'), |
| 1173 | '#multiple' => TRUE, |
| 1174 | '#size' => 5, |
| 1175 | ); |
| 1176 | } |