[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Calendar extension 19 * 20 * @package core_calendar 21 * @copyright 2004 Greek School Network (http://www.sch.gr), Jon Papaioannou, 22 * Avgoustos Tsinakos 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 if (!defined('MOODLE_INTERNAL')) { 27 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page 28 } 29 30 /** 31 * These are read by the administration component to provide default values 32 */ 33 34 /** 35 * CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD - default value of upcoming event preference 36 */ 37 define('CALENDAR_DEFAULT_UPCOMING_LOOKAHEAD', 21); 38 39 /** 40 * CALENDAR_DEFAULT_UPCOMING_MAXEVENTS - default value to display the maximum number of upcoming event 41 */ 42 define('CALENDAR_DEFAULT_UPCOMING_MAXEVENTS', 10); 43 44 /** 45 * CALENDAR_DEFAULT_STARTING_WEEKDAY - default value to display the starting weekday 46 */ 47 define('CALENDAR_DEFAULT_STARTING_WEEKDAY', 1); 48 49 // This is a packed bitfield: day X is "weekend" if $field & (1 << X) is true 50 // Default value = 65 = 64 + 1 = 2^6 + 2^0 = Saturday & Sunday 51 52 /** 53 * CALENDAR_DEFAULT_WEEKEND - default value for weekend (Saturday & Sunday) 54 */ 55 define('CALENDAR_DEFAULT_WEEKEND', 65); 56 57 /** 58 * CALENDAR_URL - path to calendar's folder 59 */ 60 define('CALENDAR_URL', $CFG->wwwroot.'/calendar/'); 61 62 /** 63 * CALENDAR_TF_24 - Calendar time in 24 hours format 64 */ 65 define('CALENDAR_TF_24', '%H:%M'); 66 67 /** 68 * CALENDAR_TF_12 - Calendar time in 12 hours format 69 */ 70 define('CALENDAR_TF_12', '%I:%M %p'); 71 72 /** 73 * CALENDAR_EVENT_GLOBAL - Global calendar event types 74 */ 75 define('CALENDAR_EVENT_GLOBAL', 1); 76 77 /** 78 * CALENDAR_EVENT_COURSE - Course calendar event types 79 */ 80 define('CALENDAR_EVENT_COURSE', 2); 81 82 /** 83 * CALENDAR_EVENT_GROUP - group calendar event types 84 */ 85 define('CALENDAR_EVENT_GROUP', 4); 86 87 /** 88 * CALENDAR_EVENT_USER - user calendar event types 89 */ 90 define('CALENDAR_EVENT_USER', 8); 91 92 93 /** 94 * CALENDAR_IMPORT_FROM_FILE - import the calendar from a file 95 */ 96 define('CALENDAR_IMPORT_FROM_FILE', 0); 97 98 /** 99 * CALENDAR_IMPORT_FROM_URL - import the calendar from a URL 100 */ 101 define('CALENDAR_IMPORT_FROM_URL', 1); 102 103 /** 104 * CALENDAR_IMPORT_EVENT_UPDATED - imported event was updated 105 */ 106 define('CALENDAR_IMPORT_EVENT_UPDATED', 1); 107 108 /** 109 * CALENDAR_IMPORT_EVENT_INSERTED - imported event was added by insert 110 */ 111 define('CALENDAR_IMPORT_EVENT_INSERTED', 2); 112 113 /** 114 * CALENDAR_SUBSCRIPTION_UPDATE - Used to represent update action for subscriptions in various forms. 115 */ 116 define('CALENDAR_SUBSCRIPTION_UPDATE', 1); 117 118 /** 119 * CALENDAR_SUBSCRIPTION_REMOVE - Used to represent remove action for subscriptions in various forms. 120 */ 121 define('CALENDAR_SUBSCRIPTION_REMOVE', 2); 122 123 /** 124 * Return the days of the week 125 * 126 * @return array array of days 127 */ 128 function calendar_get_days() { 129 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 130 return $calendartype->get_weekdays(); 131 } 132 133 /** 134 * Get the subscription from a given id 135 * 136 * @since Moodle 2.5 137 * @param int $id id of the subscription 138 * @return stdClass Subscription record from DB 139 * @throws moodle_exception for an invalid id 140 */ 141 function calendar_get_subscription($id) { 142 global $DB; 143 144 $cache = cache::make('core', 'calendar_subscriptions'); 145 $subscription = $cache->get($id); 146 if (empty($subscription)) { 147 $subscription = $DB->get_record('event_subscriptions', array('id' => $id), '*', MUST_EXIST); 148 // cache the data. 149 $cache->set($id, $subscription); 150 } 151 return $subscription; 152 } 153 154 /** 155 * Gets the first day of the week 156 * 157 * Used to be define('CALENDAR_STARTING_WEEKDAY', blah); 158 * 159 * @return int 160 */ 161 function calendar_get_starting_weekday() { 162 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 163 return $calendartype->get_starting_weekday(); 164 } 165 166 /** 167 * Generates the HTML for a miniature calendar 168 * 169 * @param array $courses list of course to list events from 170 * @param array $groups list of group 171 * @param array $users user's info 172 * @param int|bool $calmonth calendar month in numeric, default is set to false 173 * @param int|bool $calyear calendar month in numeric, default is set to false 174 * @param string|bool $placement the place/page the calendar is set to appear - passed on the the controls function 175 * @param int|bool $courseid id of the course the calendar is displayed on - passed on the the controls function 176 * @param int $time the unixtimestamp representing the date we want to view, this is used instead of $calmonth 177 * and $calyear to support multiple calendars 178 * @return string $content return html table for mini calendar 179 */ 180 function calendar_get_mini($courses, $groups, $users, $calmonth = false, $calyear = false, $placement = false, 181 $courseid = false, $time = 0) { 182 global $CFG, $OUTPUT, $PAGE; 183 184 // Get the calendar type we are using. 185 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 186 187 $display = new stdClass; 188 189 // Assume we are not displaying this month for now. 190 $display->thismonth = false; 191 192 $content = ''; 193 194 // Do this check for backwards compatibility. The core should be passing a timestamp rather than month and year. 195 // If a month and year are passed they will be in Gregorian. 196 if (!empty($calmonth) && !empty($calyear)) { 197 // Ensure it is a valid date, else we will just set it to the current timestamp. 198 if (checkdate($calmonth, 1, $calyear)) { 199 $time = make_timestamp($calyear, $calmonth, 1); 200 } else { 201 $time = time(); 202 } 203 $date = usergetdate($time); 204 if ($calmonth == $date['mon'] && $calyear == $date['year']) { 205 $display->thismonth = true; 206 } 207 // We can overwrite date now with the date used by the calendar type, if it is not Gregorian, otherwise 208 // there is no need as it is already in Gregorian. 209 if ($calendartype->get_name() != 'gregorian') { 210 $date = $calendartype->timestamp_to_date_array($time); 211 } 212 } else if (!empty($time)) { 213 // Get the specified date in the calendar type being used. 214 $date = $calendartype->timestamp_to_date_array($time); 215 $thisdate = $calendartype->timestamp_to_date_array(time()); 216 if ($date['month'] == $thisdate['month'] && $date['year'] == $thisdate['year']) { 217 $display->thismonth = true; 218 // If we are the current month we want to set the date to the current date, not the start of the month. 219 $date = $thisdate; 220 } 221 } else { 222 // Get the current date in the calendar type being used. 223 $time = time(); 224 $date = $calendartype->timestamp_to_date_array($time); 225 $display->thismonth = true; 226 } 227 228 list($d, $m, $y) = array($date['mday'], $date['mon'], $date['year']); // This is what we want to display. 229 230 // Get Gregorian date for the start of the month. 231 $gregoriandate = $calendartype->convert_to_gregorian($date['year'], $date['mon'], 1); 232 233 // Store the gregorian date values to be used later. 234 list($gy, $gm, $gd, $gh, $gmin) = array($gregoriandate['year'], $gregoriandate['month'], $gregoriandate['day'], 235 $gregoriandate['hour'], $gregoriandate['minute']); 236 237 // Get the max number of days in this month for this calendar type. 238 $display->maxdays = calendar_days_in_month($m, $y); 239 // Get the starting week day for this month. 240 $startwday = dayofweek(1, $m, $y); 241 // Get the days in a week. 242 $daynames = calendar_get_days(); 243 // Store the number of days in a week. 244 $numberofdaysinweek = $calendartype->get_num_weekdays(); 245 246 // Set the min and max weekday. 247 $display->minwday = calendar_get_starting_weekday(); 248 $display->maxwday = $display->minwday + ($numberofdaysinweek - 1); 249 250 // These are used for DB queries, so we want unixtime, so we need to use Gregorian dates. 251 $display->tstart = make_timestamp($gy, $gm, $gd, $gh, $gmin, 0); 252 $display->tend = $display->tstart + ($display->maxdays * DAYSECS) - 1; 253 254 // Align the starting weekday to fall in our display range 255 // This is simple, not foolproof. 256 if ($startwday < $display->minwday) { 257 $startwday += $numberofdaysinweek; 258 } 259 260 // Get the events matching our criteria. Don't forget to offset the timestamps for the user's TZ! 261 $events = calendar_get_events($display->tstart, $display->tend, $users, $groups, $courses); 262 263 // Set event course class for course events 264 if (!empty($events)) { 265 foreach ($events as $eventid => $event) { 266 if (!empty($event->modulename)) { 267 $cm = get_coursemodule_from_instance($event->modulename, $event->instance); 268 if (!\core_availability\info_module::is_user_visible($cm, 0, false)) { 269 unset($events[$eventid]); 270 } 271 } 272 } 273 } 274 275 // This is either a genius idea or an idiot idea: in order to not complicate things, we use this rule: if, after 276 // possibly removing SITEID from $courses, there is only one course left, then clicking on a day in the month 277 // will also set the $SESSION->cal_courses_shown variable to that one course. Otherwise, we 'd need to add extra 278 // arguments to this function. 279 $hrefparams = array(); 280 if(!empty($courses)) { 281 $courses = array_diff($courses, array(SITEID)); 282 if(count($courses) == 1) { 283 $hrefparams['course'] = reset($courses); 284 } 285 } 286 287 // We want to have easy access by day, since the display is on a per-day basis. 288 calendar_events_by_day($events, $m, $y, $eventsbyday, $durationbyday, $typesbyday, $courses); 289 290 // Accessibility: added summary and <abbr> elements. 291 $summary = get_string('calendarheading', 'calendar', userdate($display->tstart, get_string('strftimemonthyear'))); 292 // Begin table. 293 $content .= '<table class="minicalendar calendartable" summary="' . $summary . '">'; 294 if (($placement !== false) && ($courseid !== false)) { 295 $content .= '<caption>'. calendar_top_controls($placement, array('id' => $courseid, 'time' => $time)) .'</caption>'; 296 } 297 $content .= '<tr class="weekdays">'; // Header row: day names 298 299 // Print out the names of the weekdays. 300 for ($i = $display->minwday; $i <= $display->maxwday; ++$i) { 301 $pos = $i % $numberofdaysinweek; 302 $content .= '<th scope="col"><abbr title="'. $daynames[$pos]['fullname'] .'">'. 303 $daynames[$pos]['shortname'] ."</abbr></th>\n"; 304 } 305 306 $content .= '</tr><tr>'; // End of day names; prepare for day numbers 307 308 // For the table display. $week is the row; $dayweek is the column. 309 $dayweek = $startwday; 310 311 // Paddding (the first week may have blank days in the beginning) 312 for($i = $display->minwday; $i < $startwday; ++$i) { 313 $content .= '<td class="dayblank"> </td>'."\n"; 314 } 315 316 $weekend = CALENDAR_DEFAULT_WEEKEND; 317 if (isset($CFG->calendar_weekend)) { 318 $weekend = intval($CFG->calendar_weekend); 319 } 320 321 // Now display all the calendar 322 $daytime = strtotime('-1 day', $display->tstart); 323 for($day = 1; $day <= $display->maxdays; ++$day, ++$dayweek) { 324 $cellattributes = array(); 325 $daytime = strtotime('+1 day', $daytime); 326 if($dayweek > $display->maxwday) { 327 // We need to change week (table row) 328 $content .= '</tr><tr>'; 329 $dayweek = $display->minwday; 330 } 331 332 // Reset vars. 333 if ($weekend & (1 << ($dayweek % $numberofdaysinweek))) { 334 // Weekend. This is true no matter what the exact range is. 335 $class = 'weekend day'; 336 } else { 337 // Normal working day. 338 $class = 'day'; 339 } 340 341 $eventids = array(); 342 if (!empty($eventsbyday[$day])) { 343 $eventids = $eventsbyday[$day]; 344 } 345 346 if (!empty($durationbyday[$day])) { 347 $eventids = array_unique(array_merge($eventids, $durationbyday[$day])); 348 } 349 350 $finishclass = false; 351 352 if (!empty($eventids)) { 353 // There is at least one event on this day. 354 355 $class .= ' hasevent'; 356 $hrefparams['view'] = 'day'; 357 $dayhref = calendar_get_link_href(new moodle_url(CALENDAR_URL . 'view.php', $hrefparams), 0, 0, 0, $daytime); 358 359 $popupcontent = ''; 360 foreach ($eventids as $eventid) { 361 if (!isset($events[$eventid])) { 362 continue; 363 } 364 $event = new calendar_event($events[$eventid]); 365 $popupalt = ''; 366 $component = 'moodle'; 367 if (!empty($event->modulename)) { 368 $popupicon = 'icon'; 369 $popupalt = $event->modulename; 370 $component = $event->modulename; 371 } else if ($event->courseid == SITEID) { // Site event. 372 $popupicon = 'i/siteevent'; 373 } else if ($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) { // Course event. 374 $popupicon = 'i/courseevent'; 375 } else if ($event->groupid) { // Group event. 376 $popupicon = 'i/groupevent'; 377 } else { // Must be a user event. 378 $popupicon = 'i/userevent'; 379 } 380 381 if ($event->timeduration) { 382 $startdate = $calendartype->timestamp_to_date_array($event->timestart); 383 $enddate = $calendartype->timestamp_to_date_array($event->timestart + $event->timeduration - 1); 384 if ($enddate['mon'] == $m && $enddate['year'] == $y && $enddate['mday'] == $day) { 385 $finishclass = true; 386 } 387 } 388 389 $dayhref->set_anchor('event_'.$event->id); 390 391 $popupcontent .= html_writer::start_tag('div'); 392 $popupcontent .= $OUTPUT->pix_icon($popupicon, $popupalt, $component); 393 // Show ical source if needed. 394 if (!empty($event->subscription) && $CFG->calendar_showicalsource) { 395 $a = new stdClass(); 396 $a->name = format_string($event->name, true); 397 $a->source = $event->subscription->name; 398 $name = get_string('namewithsource', 'calendar', $a); 399 } else { 400 if ($finishclass) { 401 $samedate = $startdate['mon'] == $enddate['mon'] && 402 $startdate['year'] == $enddate['year'] && 403 $startdate['mday'] == $enddate['mday']; 404 405 if ($samedate) { 406 $name = format_string($event->name, true); 407 } else { 408 $name = format_string($event->name, true) . ' (' . get_string('eventendtime', 'calendar') . ')'; 409 } 410 } else { 411 $name = format_string($event->name, true); 412 } 413 } 414 $popupcontent .= html_writer::link($dayhref, $name); 415 $popupcontent .= html_writer::end_tag('div'); 416 } 417 418 if ($display->thismonth && $day == $d) { 419 $popupdata = calendar_get_popup(true, $daytime, $popupcontent); 420 } else { 421 $popupdata = calendar_get_popup(false, $daytime, $popupcontent); 422 } 423 $cellattributes = array_merge($cellattributes, $popupdata); 424 425 // Class and cell content 426 if(isset($typesbyday[$day]['startglobal'])) { 427 $class .= ' calendar_event_global'; 428 } else if(isset($typesbyday[$day]['startcourse'])) { 429 $class .= ' calendar_event_course'; 430 } else if(isset($typesbyday[$day]['startgroup'])) { 431 $class .= ' calendar_event_group'; 432 } else if(isset($typesbyday[$day]['startuser'])) { 433 $class .= ' calendar_event_user'; 434 } 435 if ($finishclass) { 436 $class .= ' duration_finish'; 437 } 438 $cell = html_writer::link($dayhref, $day); 439 } else { 440 $cell = $day; 441 } 442 443 $durationclass = false; 444 if (isset($typesbyday[$day]['durationglobal'])) { 445 $durationclass = ' duration_global'; 446 } else if(isset($typesbyday[$day]['durationcourse'])) { 447 $durationclass = ' duration_course'; 448 } else if(isset($typesbyday[$day]['durationgroup'])) { 449 $durationclass = ' duration_group'; 450 } else if(isset($typesbyday[$day]['durationuser'])) { 451 $durationclass = ' duration_user'; 452 } 453 if ($durationclass) { 454 $class .= ' duration '.$durationclass; 455 } 456 457 // If event has a class set then add it to the table day <td> tag 458 // Note: only one colour for minicalendar 459 if(isset($eventsbyday[$day])) { 460 foreach($eventsbyday[$day] as $eventid) { 461 if (!isset($events[$eventid])) { 462 continue; 463 } 464 $event = $events[$eventid]; 465 if (!empty($event->class)) { 466 $class .= ' '.$event->class; 467 } 468 break; 469 } 470 } 471 472 if ($display->thismonth && $day == $d) { 473 // The current cell is for today - add appropriate classes and additional information for styling. 474 $class .= ' today'; 475 $today = get_string('today', 'calendar').' '.userdate(time(), get_string('strftimedayshort')); 476 477 if (!isset($eventsbyday[$day]) && !isset($durationbyday[$day])) { 478 $class .= ' eventnone'; 479 $popupdata = calendar_get_popup(true, false); 480 $cellattributes = array_merge($cellattributes, $popupdata); 481 $cell = html_writer::link('#', $day); 482 } 483 $cell = get_accesshide($today . ' ') . $cell; 484 } 485 486 // Just display it 487 $cellattributes['class'] = $class; 488 $content .= html_writer::tag('td', $cell, $cellattributes); 489 } 490 491 // Paddding (the last week may have blank days at the end) 492 for($i = $dayweek; $i <= $display->maxwday; ++$i) { 493 $content .= '<td class="dayblank"> </td>'; 494 } 495 $content .= '</tr>'; // Last row ends 496 497 $content .= '</table>'; // Tabular display of days ends 498 499 static $jsincluded = false; 500 if (!$jsincluded) { 501 $PAGE->requires->yui_module('moodle-calendar-info', 'Y.M.core_calendar.info.init'); 502 $jsincluded = true; 503 } 504 return $content; 505 } 506 507 /** 508 * Gets the calendar popup 509 * 510 * It called at multiple points in from calendar_get_mini. 511 * Copied and modified from calendar_get_mini. 512 * 513 * @param bool $is_today false except when called on the current day. 514 * @param mixed $event_timestart $events[$eventid]->timestart, OR false if there are no events. 515 * @param string $popupcontent content for the popup window/layout. 516 * @return string eventid for the calendar_tooltip popup window/layout. 517 */ 518 function calendar_get_popup($today = false, $timestart, $popupcontent = '') { 519 global $PAGE; 520 521 $popupcaption = ''; 522 if ($today) { 523 $popupcaption = get_string('today', 'calendar') . ' '; 524 } 525 526 if (false === $timestart) { 527 $popupcaption .= userdate(time(), get_string('strftimedayshort')); 528 $popupcontent = get_string('eventnone', 'calendar'); 529 530 } else { 531 $popupcaption .= get_string('eventsfor', 'calendar', userdate($timestart, get_string('strftimedayshort'))); 532 } 533 534 return array( 535 'data-core_calendar-title' => $popupcaption, 536 'data-core_calendar-popupcontent' => $popupcontent, 537 ); 538 } 539 540 /** 541 * Gets the calendar upcoming event 542 * 543 * @param array $courses array of courses 544 * @param array|int|bool $groups array of groups, group id or boolean for all/no group events 545 * @param array|int|bool $users array of users, user id or boolean for all/no user events 546 * @param int $daysinfuture number of days in the future we 'll look 547 * @param int $maxevents maximum number of events 548 * @param int $fromtime start time 549 * @return array $output array of upcoming events 550 */ 551 function calendar_get_upcoming($courses, $groups, $users, $daysinfuture, $maxevents, $fromtime=0) { 552 global $CFG, $COURSE, $DB; 553 554 $display = new stdClass; 555 $display->range = $daysinfuture; // How many days in the future we 'll look 556 $display->maxevents = $maxevents; 557 558 $output = array(); 559 560 // Prepare "course caching", since it may save us a lot of queries 561 $coursecache = array(); 562 563 $processed = 0; 564 $now = time(); // We 'll need this later 565 $usermidnighttoday = usergetmidnight($now); 566 567 if ($fromtime) { 568 $display->tstart = $fromtime; 569 } else { 570 $display->tstart = $usermidnighttoday; 571 } 572 573 // This works correctly with respect to the user's DST, but it is accurate 574 // only because $fromtime is always the exact midnight of some day! 575 $display->tend = usergetmidnight($display->tstart + DAYSECS * $display->range + 3 * HOURSECS) - 1; 576 577 // Get the events matching our criteria 578 $events = calendar_get_events($display->tstart, $display->tend, $users, $groups, $courses); 579 580 // This is either a genius idea or an idiot idea: in order to not complicate things, we use this rule: if, after 581 // possibly removing SITEID from $courses, there is only one course left, then clicking on a day in the month 582 // will also set the $SESSION->cal_courses_shown variable to that one course. Otherwise, we 'd need to add extra 583 // arguments to this function. 584 585 $hrefparams = array(); 586 if(!empty($courses)) { 587 $courses = array_diff($courses, array(SITEID)); 588 if(count($courses) == 1) { 589 $hrefparams['course'] = reset($courses); 590 } 591 } 592 593 if ($events !== false) { 594 595 $modinfo = get_fast_modinfo($COURSE); 596 597 foreach($events as $event) { 598 599 600 if (!empty($event->modulename)) { 601 if ($event->courseid == $COURSE->id) { 602 if (isset($modinfo->instances[$event->modulename][$event->instance])) { 603 $cm = $modinfo->instances[$event->modulename][$event->instance]; 604 if (!$cm->uservisible) { 605 continue; 606 } 607 } 608 } else { 609 if (!$cm = get_coursemodule_from_instance($event->modulename, $event->instance)) { 610 continue; 611 } 612 if (!\core_availability\info_module::is_user_visible($cm, 0, false)) { 613 continue; 614 } 615 } 616 } 617 618 if ($processed >= $display->maxevents) { 619 break; 620 } 621 622 $event->time = calendar_format_event_time($event, $now, $hrefparams); 623 $output[] = $event; 624 ++$processed; 625 } 626 } 627 return $output; 628 } 629 630 631 /** 632 * Get a HTML link to a course. 633 * 634 * @param int $courseid the course id 635 * @return string a link to the course (as HTML); empty if the course id is invalid 636 */ 637 function calendar_get_courselink($courseid) { 638 639 if (!$courseid) { 640 return ''; 641 } 642 643 calendar_get_course_cached($coursecache, $courseid); 644 $context = context_course::instance($courseid); 645 $fullname = format_string($coursecache[$courseid]->fullname, true, array('context' => $context)); 646 $url = new moodle_url('/course/view.php', array('id' => $courseid)); 647 $link = html_writer::link($url, $fullname); 648 649 return $link; 650 } 651 652 653 /** 654 * Add calendar event metadata 655 * 656 * @param stdClass $event event info 657 * @return stdClass $event metadata 658 */ 659 function calendar_add_event_metadata($event) { 660 global $CFG, $OUTPUT; 661 662 //Support multilang in event->name 663 $event->name = format_string($event->name,true); 664 665 if(!empty($event->modulename)) { // Activity event 666 // The module name is set. I will assume that it has to be displayed, and 667 // also that it is an automatically-generated event. And of course that the 668 // fields for get_coursemodule_from_instance are set correctly. 669 $module = calendar_get_module_cached($coursecache, $event->modulename, $event->instance); 670 671 if ($module === false) { 672 return; 673 } 674 675 $modulename = get_string('modulename', $event->modulename); 676 if (get_string_manager()->string_exists($event->eventtype, $event->modulename)) { 677 // will be used as alt text if the event icon 678 $eventtype = get_string($event->eventtype, $event->modulename); 679 } else { 680 $eventtype = ''; 681 } 682 $icon = $OUTPUT->pix_url('icon', $event->modulename) . ''; 683 684 $event->icon = '<img src="'.$icon.'" alt="'.$eventtype.'" title="'.$modulename.'" class="icon" />'; 685 $event->referer = '<a href="'.$CFG->wwwroot.'/mod/'.$event->modulename.'/view.php?id='.$module->id.'">'.$event->name.'</a>'; 686 $event->courselink = calendar_get_courselink($module->course); 687 $event->cmid = $module->id; 688 689 } else if($event->courseid == SITEID) { // Site event 690 $event->icon = '<img src="'.$OUTPUT->pix_url('i/siteevent') . '" alt="'.get_string('globalevent', 'calendar').'" class="icon" />'; 691 $event->cssclass = 'calendar_event_global'; 692 } else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) { // Course event 693 $event->icon = '<img src="'.$OUTPUT->pix_url('i/courseevent') . '" alt="'.get_string('courseevent', 'calendar').'" class="icon" />'; 694 $event->courselink = calendar_get_courselink($event->courseid); 695 $event->cssclass = 'calendar_event_course'; 696 } else if ($event->groupid) { // Group event 697 if ($group = calendar_get_group_cached($event->groupid)) { 698 $groupname = format_string($group->name, true, context_course::instance($group->courseid)); 699 } else { 700 $groupname = ''; 701 } 702 $event->icon = html_writer::empty_tag('image', array('src' => $OUTPUT->pix_url('i/groupevent'), 703 'alt' => get_string('groupevent', 'calendar'), 'title' => $groupname, 'class' => 'icon')); 704 $event->courselink = calendar_get_courselink($event->courseid) . ', ' . $groupname; 705 $event->cssclass = 'calendar_event_group'; 706 } else if($event->userid) { // User event 707 $event->icon = '<img src="'.$OUTPUT->pix_url('i/userevent') . '" alt="'.get_string('userevent', 'calendar').'" class="icon" />'; 708 $event->cssclass = 'calendar_event_user'; 709 } 710 return $event; 711 } 712 713 /** 714 * Get calendar events 715 * 716 * @param int $tstart Start time of time range for events 717 * @param int $tend End time of time range for events 718 * @param array|int|boolean $users array of users, user id or boolean for all/no user events 719 * @param array|int|boolean $groups array of groups, group id or boolean for all/no group events 720 * @param array|int|boolean $courses array of courses, course id or boolean for all/no course events 721 * @param boolean $withduration whether only events starting within time range selected 722 * or events in progress/already started selected as well 723 * @param boolean $ignorehidden whether to select only visible events or all events 724 * @return array $events of selected events or an empty array if there aren't any (or there was an error) 725 */ 726 function calendar_get_events($tstart, $tend, $users, $groups, $courses, $withduration=true, $ignorehidden=true) { 727 global $DB; 728 729 $whereclause = ''; 730 $params = array(); 731 // Quick test. 732 if (empty($users) && empty($groups) && empty($courses)) { 733 return array(); 734 } 735 736 if ((is_array($users) && !empty($users)) or is_numeric($users)) { 737 // Events from a number of users 738 if(!empty($whereclause)) $whereclause .= ' OR'; 739 list($insqlusers, $inparamsusers) = $DB->get_in_or_equal($users, SQL_PARAMS_NAMED); 740 $whereclause .= " (userid $insqlusers AND courseid = 0 AND groupid = 0)"; 741 $params = array_merge($params, $inparamsusers); 742 } else if($users === true) { 743 // Events from ALL users 744 if(!empty($whereclause)) $whereclause .= ' OR'; 745 $whereclause .= ' (userid != 0 AND courseid = 0 AND groupid = 0)'; 746 } else if($users === false) { 747 // No user at all, do nothing 748 } 749 750 if ((is_array($groups) && !empty($groups)) or is_numeric($groups)) { 751 // Events from a number of groups 752 if(!empty($whereclause)) $whereclause .= ' OR'; 753 list($insqlgroups, $inparamsgroups) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED); 754 $whereclause .= " groupid $insqlgroups "; 755 $params = array_merge($params, $inparamsgroups); 756 } else if($groups === true) { 757 // Events from ALL groups 758 if(!empty($whereclause)) $whereclause .= ' OR '; 759 $whereclause .= ' groupid != 0'; 760 } 761 // boolean false (no groups at all): we don't need to do anything 762 763 if ((is_array($courses) && !empty($courses)) or is_numeric($courses)) { 764 if(!empty($whereclause)) $whereclause .= ' OR'; 765 list($insqlcourses, $inparamscourses) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED); 766 $whereclause .= " (groupid = 0 AND courseid $insqlcourses)"; 767 $params = array_merge($params, $inparamscourses); 768 } else if ($courses === true) { 769 // Events from ALL courses 770 if(!empty($whereclause)) $whereclause .= ' OR'; 771 $whereclause .= ' (groupid = 0 AND courseid != 0)'; 772 } 773 774 // Security check: if, by now, we have NOTHING in $whereclause, then it means 775 // that NO event-selecting clauses were defined. Thus, we won't be returning ANY 776 // events no matter what. Allowing the code to proceed might return a completely 777 // valid query with only time constraints, thus selecting ALL events in that time frame! 778 if(empty($whereclause)) { 779 return array(); 780 } 781 782 if($withduration) { 783 $timeclause = '(timestart >= '.$tstart.' OR timestart + timeduration > '.$tstart.') AND timestart <= '.$tend; 784 } 785 else { 786 $timeclause = 'timestart >= '.$tstart.' AND timestart <= '.$tend; 787 } 788 if(!empty($whereclause)) { 789 // We have additional constraints 790 $whereclause = $timeclause.' AND ('.$whereclause.')'; 791 } 792 else { 793 // Just basic time filtering 794 $whereclause = $timeclause; 795 } 796 797 if ($ignorehidden) { 798 $whereclause .= ' AND visible = 1'; 799 } 800 801 $events = $DB->get_records_select('event', $whereclause, $params, 'timestart'); 802 if ($events === false) { 803 $events = array(); 804 } 805 return $events; 806 } 807 808 /** Get calendar events by id 809 * 810 * @since Moodle 2.5 811 * @param array $eventids list of event ids 812 * @return array Array of event entries, empty array if nothing found 813 */ 814 815 function calendar_get_events_by_id($eventids) { 816 global $DB; 817 818 if (!is_array($eventids) || empty($eventids)) { 819 return array(); 820 } 821 list($wheresql, $params) = $DB->get_in_or_equal($eventids); 822 $wheresql = "id $wheresql"; 823 824 return $DB->get_records_select('event', $wheresql, $params); 825 } 826 827 /** 828 * Get control options for Calendar 829 * 830 * @param string $type of calendar 831 * @param array $data calendar information 832 * @return string $content return available control for the calender in html 833 */ 834 function calendar_top_controls($type, $data) { 835 global $PAGE, $OUTPUT; 836 837 // Get the calendar type we are using. 838 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 839 840 $content = ''; 841 842 // Ensure course id passed if relevant. 843 $courseid = ''; 844 if (!empty($data['id'])) { 845 $courseid = '&course='.$data['id']; 846 } 847 848 // If we are passing a month and year then we need to convert this to a timestamp to 849 // support multiple calendars. No where in core should these be passed, this logic 850 // here is for third party plugins that may use this function. 851 if (!empty($data['m']) && !empty($date['y'])) { 852 if (!isset($data['d'])) { 853 $data['d'] = 1; 854 } 855 if (!checkdate($data['m'], $data['d'], $data['y'])) { 856 $time = time(); 857 } else { 858 $time = make_timestamp($data['y'], $data['m'], $data['d']); 859 } 860 } else if (!empty($data['time'])) { 861 $time = $data['time']; 862 } else { 863 $time = time(); 864 } 865 866 // Get the date for the calendar type. 867 $date = $calendartype->timestamp_to_date_array($time); 868 869 $urlbase = $PAGE->url; 870 871 // We need to get the previous and next months in certain cases. 872 if ($type == 'frontpage' || $type == 'course' || $type == 'month') { 873 $prevmonth = calendar_sub_month($date['mon'], $date['year']); 874 $prevmonthtime = $calendartype->convert_to_gregorian($prevmonth[1], $prevmonth[0], 1); 875 $prevmonthtime = make_timestamp($prevmonthtime['year'], $prevmonthtime['month'], $prevmonthtime['day'], 876 $prevmonthtime['hour'], $prevmonthtime['minute']); 877 878 $nextmonth = calendar_add_month($date['mon'], $date['year']); 879 $nextmonthtime = $calendartype->convert_to_gregorian($nextmonth[1], $nextmonth[0], 1); 880 $nextmonthtime = make_timestamp($nextmonthtime['year'], $nextmonthtime['month'], $nextmonthtime['day'], 881 $nextmonthtime['hour'], $nextmonthtime['minute']); 882 } 883 884 switch ($type) { 885 case 'frontpage': 886 $prevlink = calendar_get_link_previous(get_string('monthprev', 'access'), $urlbase, false, false, false, true, $prevmonthtime); 887 $nextlink = calendar_get_link_next(get_string('monthnext', 'access'), $urlbase, false, false, false, true, $nextmonthtime); 888 $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view' => 'month')), false, false, false, $time); 889 890 if (!empty($data['id'])) { 891 $calendarlink->param('course', $data['id']); 892 } 893 894 if (right_to_left()) { 895 $left = $nextlink; 896 $right = $prevlink; 897 } else { 898 $left = $prevlink; 899 $right = $nextlink; 900 } 901 902 $content .= html_writer::start_tag('div', array('class'=>'calendar-controls')); 903 $content .= $left.'<span class="hide"> | </span>'; 904 $content .= html_writer::tag('span', html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')), array('title'=>get_string('monththis','calendar'))), array('class'=>'current')); 905 $content .= '<span class="hide"> | </span>'. $right; 906 $content .= "<span class=\"clearer\"><!-- --></span>\n"; 907 $content .= html_writer::end_tag('div'); 908 909 break; 910 case 'course': 911 $prevlink = calendar_get_link_previous(get_string('monthprev', 'access'), $urlbase, false, false, false, true, $prevmonthtime); 912 $nextlink = calendar_get_link_next(get_string('monthnext', 'access'), $urlbase, false, false, false, true, $nextmonthtime); 913 $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view' => 'month')), false, false, false, $time); 914 915 if (!empty($data['id'])) { 916 $calendarlink->param('course', $data['id']); 917 } 918 919 if (right_to_left()) { 920 $left = $nextlink; 921 $right = $prevlink; 922 } else { 923 $left = $prevlink; 924 $right = $nextlink; 925 } 926 927 $content .= html_writer::start_tag('div', array('class'=>'calendar-controls')); 928 $content .= $left.'<span class="hide"> | </span>'; 929 $content .= html_writer::tag('span', html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear')), array('title'=>get_string('monththis','calendar'))), array('class'=>'current')); 930 $content .= '<span class="hide"> | </span>'. $right; 931 $content .= "<span class=\"clearer\"><!-- --></span>"; 932 $content .= html_writer::end_tag('div'); 933 break; 934 case 'upcoming': 935 $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view' => 'upcoming')), false, false, false, $time); 936 if (!empty($data['id'])) { 937 $calendarlink->param('course', $data['id']); 938 } 939 $calendarlink = html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear'))); 940 $content .= html_writer::tag('div', $calendarlink, array('class'=>'centered')); 941 break; 942 case 'display': 943 $calendarlink = calendar_get_link_href(new moodle_url(CALENDAR_URL.'view.php', array('view' => 'month')), false, false, false, $time); 944 if (!empty($data['id'])) { 945 $calendarlink->param('course', $data['id']); 946 } 947 $calendarlink = html_writer::link($calendarlink, userdate($time, get_string('strftimemonthyear'))); 948 $content .= html_writer::tag('h3', $calendarlink); 949 break; 950 case 'month': 951 $prevlink = calendar_get_link_previous(userdate($prevmonthtime, get_string('strftimemonthyear')), 'view.php?view=month'.$courseid.'&', false, false, false, false, $prevmonthtime); 952 $nextlink = calendar_get_link_next(userdate($nextmonthtime, get_string('strftimemonthyear')), 'view.php?view=month'.$courseid.'&', false, false, false, false, $nextmonthtime); 953 954 if (right_to_left()) { 955 $left = $nextlink; 956 $right = $prevlink; 957 } else { 958 $left = $prevlink; 959 $right = $nextlink; 960 } 961 962 $content .= html_writer::start_tag('div', array('class'=>'calendar-controls')); 963 $content .= $left . '<span class="hide"> | </span>'; 964 $content .= $OUTPUT->heading(userdate($time, get_string('strftimemonthyear')), 2, 'current'); 965 $content .= '<span class="hide"> | </span>' . $right; 966 $content .= '<span class="clearer"><!-- --></span>'; 967 $content .= html_writer::end_tag('div')."\n"; 968 break; 969 case 'day': 970 $days = calendar_get_days(); 971 972 $prevtimestamp = strtotime('-1 day', $time); 973 $nexttimestamp = strtotime('+1 day', $time); 974 975 $prevdate = $calendartype->timestamp_to_date_array($prevtimestamp); 976 $nextdate = $calendartype->timestamp_to_date_array($nexttimestamp); 977 978 $prevname = $days[$prevdate['wday']]['fullname']; 979 $nextname = $days[$nextdate['wday']]['fullname']; 980 $prevlink = calendar_get_link_previous($prevname, 'view.php?view=day'.$courseid.'&', false, false, false, false, $prevtimestamp); 981 $nextlink = calendar_get_link_next($nextname, 'view.php?view=day'.$courseid.'&', false, false, false, false, $nexttimestamp); 982 983 if (right_to_left()) { 984 $left = $nextlink; 985 $right = $prevlink; 986 } else { 987 $left = $prevlink; 988 $right = $nextlink; 989 } 990 991 $content .= html_writer::start_tag('div', array('class'=>'calendar-controls')); 992 $content .= $left; 993 $content .= '<span class="hide"> | </span><span class="current">'.userdate($time, get_string('strftimedaydate')).'</span>'; 994 $content .= '<span class="hide"> | </span>'. $right; 995 $content .= "<span class=\"clearer\"><!-- --></span>"; 996 $content .= html_writer::end_tag('div')."\n"; 997 998 break; 999 } 1000 return $content; 1001 } 1002 1003 /** 1004 * Formats a filter control element. 1005 * 1006 * @param moodle_url $url of the filter 1007 * @param int $type constant defining the type filter 1008 * @return string html content of the element 1009 */ 1010 function calendar_filter_controls_element(moodle_url $url, $type) { 1011 global $OUTPUT; 1012 switch ($type) { 1013 case CALENDAR_EVENT_GLOBAL: 1014 $typeforhumans = 'global'; 1015 $class = 'calendar_event_global'; 1016 break; 1017 case CALENDAR_EVENT_COURSE: 1018 $typeforhumans = 'course'; 1019 $class = 'calendar_event_course'; 1020 break; 1021 case CALENDAR_EVENT_GROUP: 1022 $typeforhumans = 'groups'; 1023 $class = 'calendar_event_group'; 1024 break; 1025 case CALENDAR_EVENT_USER: 1026 $typeforhumans = 'user'; 1027 $class = 'calendar_event_user'; 1028 break; 1029 } 1030 if (calendar_show_event_type($type)) { 1031 $icon = $OUTPUT->pix_icon('t/hide', get_string('hide')); 1032 $str = get_string('hide'.$typeforhumans.'events', 'calendar'); 1033 } else { 1034 $icon = $OUTPUT->pix_icon('t/show', get_string('show')); 1035 $str = get_string('show'.$typeforhumans.'events', 'calendar'); 1036 } 1037 $content = html_writer::start_tag('li', array('class' => 'calendar_event')); 1038 $content .= html_writer::start_tag('a', array('href' => $url, 'rel' => 'nofollow')); 1039 $content .= html_writer::tag('span', $icon, array('class' => $class)); 1040 $content .= html_writer::tag('span', $str, array('class' => 'eventname')); 1041 $content .= html_writer::end_tag('a'); 1042 $content .= html_writer::end_tag('li'); 1043 return $content; 1044 } 1045 1046 /** 1047 * Get the controls filter for calendar. 1048 * 1049 * Filter is used to hide calendar info from the display page 1050 * 1051 * @param moodle_url $returnurl return-url for filter controls 1052 * @return string $content return filter controls in html 1053 */ 1054 function calendar_filter_controls(moodle_url $returnurl) { 1055 global $CFG, $USER, $OUTPUT; 1056 1057 $groupevents = true; 1058 $id = optional_param( 'id',0,PARAM_INT ); 1059 $seturl = new moodle_url('/calendar/set.php', array('return' => base64_encode($returnurl->out_as_local_url(false)), 'sesskey'=>sesskey())); 1060 $content = html_writer::start_tag('ul'); 1061 1062 $seturl->param('var', 'showglobal'); 1063 $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_GLOBAL); 1064 1065 $seturl->param('var', 'showcourses'); 1066 $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_COURSE); 1067 1068 if (isloggedin() && !isguestuser()) { 1069 if ($groupevents) { 1070 // This course MIGHT have group events defined, so show the filter 1071 $seturl->param('var', 'showgroups'); 1072 $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_GROUP); 1073 } else { 1074 // This course CANNOT have group events, so lose the filter 1075 } 1076 $seturl->param('var', 'showuser'); 1077 $content .= calendar_filter_controls_element($seturl, CALENDAR_EVENT_USER); 1078 } 1079 $content .= html_writer::end_tag('ul'); 1080 1081 return $content; 1082 } 1083 1084 /** 1085 * Return the representation day 1086 * 1087 * @param int $tstamp Timestamp in GMT 1088 * @param int $now current Unix timestamp 1089 * @param bool $usecommonwords 1090 * @return string the formatted date/time 1091 */ 1092 function calendar_day_representation($tstamp, $now = false, $usecommonwords = true) { 1093 1094 static $shortformat; 1095 if(empty($shortformat)) { 1096 $shortformat = get_string('strftimedayshort'); 1097 } 1098 1099 if($now === false) { 1100 $now = time(); 1101 } 1102 1103 // To have it in one place, if a change is needed 1104 $formal = userdate($tstamp, $shortformat); 1105 1106 $datestamp = usergetdate($tstamp); 1107 $datenow = usergetdate($now); 1108 1109 if($usecommonwords == false) { 1110 // We don't want words, just a date 1111 return $formal; 1112 } 1113 else if($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday']) { 1114 // Today 1115 return get_string('today', 'calendar'); 1116 } 1117 else if( 1118 ($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday'] - 1 ) || 1119 ($datestamp['year'] == $datenow['year'] - 1 && $datestamp['mday'] == 31 && $datestamp['mon'] == 12 && $datenow['yday'] == 1) 1120 ) { 1121 // Yesterday 1122 return get_string('yesterday', 'calendar'); 1123 } 1124 else if( 1125 ($datestamp['year'] == $datenow['year'] && $datestamp['yday'] == $datenow['yday'] + 1 ) || 1126 ($datestamp['year'] == $datenow['year'] + 1 && $datenow['mday'] == 31 && $datenow['mon'] == 12 && $datestamp['yday'] == 1) 1127 ) { 1128 // Tomorrow 1129 return get_string('tomorrow', 'calendar'); 1130 } 1131 else { 1132 return $formal; 1133 } 1134 } 1135 1136 /** 1137 * return the formatted representation time 1138 * 1139 * @param int $time the timestamp in UTC, as obtained from the database 1140 * @return string the formatted date/time 1141 */ 1142 function calendar_time_representation($time) { 1143 static $langtimeformat = NULL; 1144 if($langtimeformat === NULL) { 1145 $langtimeformat = get_string('strftimetime'); 1146 } 1147 $timeformat = get_user_preferences('calendar_timeformat'); 1148 if(empty($timeformat)){ 1149 $timeformat = get_config(NULL,'calendar_site_timeformat'); 1150 } 1151 // The ? is needed because the preference might be present, but empty 1152 return userdate($time, empty($timeformat) ? $langtimeformat : $timeformat); 1153 } 1154 1155 /** 1156 * Adds day, month, year arguments to a URL and returns a moodle_url object. 1157 * 1158 * @param string|moodle_url $linkbase 1159 * @param int $d The number of the day. 1160 * @param int $m The number of the month. 1161 * @param int $y The number of the year. 1162 * @param int $time the unixtime, used for multiple calendar support. The values $d, 1163 * $m and $y are kept for backwards compatibility. 1164 * @return moodle_url|null $linkbase 1165 */ 1166 function calendar_get_link_href($linkbase, $d, $m, $y, $time = 0) { 1167 if (empty($linkbase)) { 1168 return ''; 1169 } 1170 if (!($linkbase instanceof moodle_url)) { 1171 $linkbase = new moodle_url($linkbase); 1172 } 1173 1174 // If a day, month and year were passed then convert it to a timestamp. If these were passed 1175 // then we can assume the day, month and year are passed as Gregorian, as no where in core 1176 // should we be passing these values rather than the time. 1177 if (!empty($d) && !empty($m) && !empty($y)) { 1178 if (checkdate($m, $d, $y)) { 1179 $time = make_timestamp($y, $m, $d); 1180 } else { 1181 $time = time(); 1182 } 1183 } else if (empty($time)) { 1184 $time = time(); 1185 } 1186 1187 $linkbase->param('time', $time); 1188 1189 return $linkbase; 1190 } 1191 1192 /** 1193 * Build and return a previous month HTML link, with an arrow. 1194 * 1195 * @param string $text The text label. 1196 * @param string|moodle_url $linkbase The URL stub. 1197 * @param int $d The number of the date. 1198 * @param int $m The number of the month. 1199 * @param int $y year The number of the year. 1200 * @param bool $accesshide Default visible, or hide from all except screenreaders. 1201 * @param int $time the unixtime, used for multiple calendar support. The values $d, 1202 * $m and $y are kept for backwards compatibility. 1203 * @return string HTML string. 1204 */ 1205 function calendar_get_link_previous($text, $linkbase, $d, $m, $y, $accesshide = false, $time = 0) { 1206 $href = calendar_get_link_href(new moodle_url($linkbase), $d, $m, $y, $time); 1207 if (empty($href)) { 1208 return $text; 1209 } 1210 return link_arrow_left($text, (string)$href, $accesshide, 'previous'); 1211 } 1212 1213 /** 1214 * Build and return a next month HTML link, with an arrow. 1215 * 1216 * @param string $text The text label. 1217 * @param string|moodle_url $linkbase The URL stub. 1218 * @param int $d the number of the Day 1219 * @param int $m The number of the month. 1220 * @param int $y The number of the year. 1221 * @param bool $accesshide Default visible, or hide from all except screenreaders. 1222 * @param int $time the unixtime, used for multiple calendar support. The values $d, 1223 * $m and $y are kept for backwards compatibility. 1224 * @return string HTML string. 1225 */ 1226 function calendar_get_link_next($text, $linkbase, $d, $m, $y, $accesshide = false, $time = 0) { 1227 $href = calendar_get_link_href(new moodle_url($linkbase), $d, $m, $y, $time); 1228 if (empty($href)) { 1229 return $text; 1230 } 1231 return link_arrow_right($text, (string)$href, $accesshide, 'next'); 1232 } 1233 1234 /** 1235 * Return the name of the weekday 1236 * 1237 * @param string $englishname 1238 * @return string of the weekeday 1239 */ 1240 function calendar_wday_name($englishname) { 1241 return get_string(strtolower($englishname), 'calendar'); 1242 } 1243 1244 /** 1245 * Return the number of days in month 1246 * 1247 * @param int $month the number of the month. 1248 * @param int $year the number of the year 1249 * @return int 1250 */ 1251 function calendar_days_in_month($month, $year) { 1252 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 1253 return $calendartype->get_num_days_in_month($year, $month); 1254 } 1255 1256 /** 1257 * Get the upcoming event block 1258 * 1259 * @param array $events list of events 1260 * @param moodle_url|string $linkhref link to event referer 1261 * @param boolean $showcourselink whether links to courses should be shown 1262 * @return string|null $content html block content 1263 */ 1264 function calendar_get_block_upcoming($events, $linkhref = NULL, $showcourselink = false) { 1265 $content = ''; 1266 $lines = count($events); 1267 if (!$lines) { 1268 return $content; 1269 } 1270 1271 for ($i = 0; $i < $lines; ++$i) { 1272 if (!isset($events[$i]->time)) { // Just for robustness 1273 continue; 1274 } 1275 $events[$i] = calendar_add_event_metadata($events[$i]); 1276 $content .= '<div class="event"><span class="icon c0">'.$events[$i]->icon.'</span>'; 1277 if (!empty($events[$i]->referer)) { 1278 // That's an activity event, so let's provide the hyperlink 1279 $content .= $events[$i]->referer; 1280 } else { 1281 if(!empty($linkhref)) { 1282 $href = calendar_get_link_href(new moodle_url(CALENDAR_URL . $linkhref), 0, 0, 0, $events[$i]->timestart); 1283 $href->set_anchor('event_'.$events[$i]->id); 1284 $content .= html_writer::link($href, $events[$i]->name); 1285 } 1286 else { 1287 $content .= $events[$i]->name; 1288 } 1289 } 1290 $events[$i]->time = str_replace('»', '<br />»', $events[$i]->time); 1291 if ($showcourselink && !empty($events[$i]->courselink)) { 1292 $content .= html_writer::div($events[$i]->courselink, 'course'); 1293 } 1294 $content .= '<div class="date">'.$events[$i]->time.'</div></div>'; 1295 if ($i < $lines - 1) $content .= '<hr />'; 1296 } 1297 1298 return $content; 1299 } 1300 1301 /** 1302 * Get the next following month 1303 * 1304 * @param int $month the number of the month. 1305 * @param int $year the number of the year. 1306 * @return array the following month 1307 */ 1308 function calendar_add_month($month, $year) { 1309 // Get the calendar type we are using. 1310 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 1311 return $calendartype->get_next_month($year, $month); 1312 } 1313 1314 /** 1315 * Get the previous month. 1316 * 1317 * @param int $month the number of the month. 1318 * @param int $year the number of the year. 1319 * @return array previous month 1320 */ 1321 function calendar_sub_month($month, $year) { 1322 // Get the calendar type we are using. 1323 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 1324 return $calendartype->get_prev_month($year, $month); 1325 } 1326 1327 /** 1328 * Get per-day basis events 1329 * 1330 * @param array $events list of events 1331 * @param int $month the number of the month 1332 * @param int $year the number of the year 1333 * @param array $eventsbyday event on specific day 1334 * @param array $durationbyday duration of the event in days 1335 * @param array $typesbyday event type (eg: global, course, user, or group) 1336 * @param array $courses list of courses 1337 * @return void 1338 */ 1339 function calendar_events_by_day($events, $month, $year, &$eventsbyday, &$durationbyday, &$typesbyday, &$courses) { 1340 // Get the calendar type we are using. 1341 $calendartype = \core_calendar\type_factory::get_calendar_instance(); 1342 1343 $eventsbyday = array(); 1344 $typesbyday = array(); 1345 $durationbyday = array(); 1346 1347 if($events === false) { 1348 return; 1349 } 1350 1351 foreach ($events as $event) { 1352 $startdate = $calendartype->timestamp_to_date_array($event->timestart); 1353 // Set end date = start date if no duration 1354 if ($event->timeduration) { 1355 $enddate = $calendartype->timestamp_to_date_array($event->timestart + $event->timeduration - 1); 1356 } else { 1357 $enddate = $startdate; 1358 } 1359 1360 // Simple arithmetic: $year * 13 + $month is a distinct integer for each distinct ($year, $month) pair 1361 if(!($startdate['year'] * 13 + $startdate['mon'] <= $year * 13 + $month) && ($enddate['year'] * 13 + $enddate['mon'] >= $year * 13 + $month)) { 1362 // Out of bounds 1363 continue; 1364 } 1365 1366 $eventdaystart = intval($startdate['mday']); 1367 1368 if($startdate['mon'] == $month && $startdate['year'] == $year) { 1369 // Give the event to its day 1370 $eventsbyday[$eventdaystart][] = $event->id; 1371 1372 // Mark the day as having such an event 1373 if($event->courseid == SITEID && $event->groupid == 0) { 1374 $typesbyday[$eventdaystart]['startglobal'] = true; 1375 // Set event class for global event 1376 $events[$event->id]->class = 'calendar_event_global'; 1377 } 1378 else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) { 1379 $typesbyday[$eventdaystart]['startcourse'] = true; 1380 // Set event class for course event 1381 $events[$event->id]->class = 'calendar_event_course'; 1382 } 1383 else if($event->groupid) { 1384 $typesbyday[$eventdaystart]['startgroup'] = true; 1385 // Set event class for group event 1386 $events[$event->id]->class = 'calendar_event_group'; 1387 } 1388 else if($event->userid) { 1389 $typesbyday[$eventdaystart]['startuser'] = true; 1390 // Set event class for user event 1391 $events[$event->id]->class = 'calendar_event_user'; 1392 } 1393 } 1394 1395 if($event->timeduration == 0) { 1396 // Proceed with the next 1397 continue; 1398 } 1399 1400 // The event starts on $month $year or before. So... 1401 $lowerbound = $startdate['mon'] == $month && $startdate['year'] == $year ? intval($startdate['mday']) : 0; 1402 1403 // Also, it ends on $month $year or later... 1404 $upperbound = $enddate['mon'] == $month && $enddate['year'] == $year ? intval($enddate['mday']) : calendar_days_in_month($month, $year); 1405 1406 // Mark all days between $lowerbound and $upperbound (inclusive) as duration 1407 for($i = $lowerbound + 1; $i <= $upperbound; ++$i) { 1408 $durationbyday[$i][] = $event->id; 1409 if($event->courseid == SITEID && $event->groupid == 0) { 1410 $typesbyday[$i]['durationglobal'] = true; 1411 } 1412 else if($event->courseid != 0 && $event->courseid != SITEID && $event->groupid == 0) { 1413 $typesbyday[$i]['durationcourse'] = true; 1414 } 1415 else if($event->groupid) { 1416 $typesbyday[$i]['durationgroup'] = true; 1417 } 1418 else if($event->userid) { 1419 $typesbyday[$i]['durationuser'] = true; 1420 } 1421 } 1422 1423 } 1424 return; 1425 } 1426 1427 /** 1428 * Get current module cache 1429 * 1430 * @param array $coursecache list of course cache 1431 * @param string $modulename name of the module 1432 * @param int $instance module instance number 1433 * @return stdClass|bool $module information 1434 */ 1435 function calendar_get_module_cached(&$coursecache, $modulename, $instance) { 1436 $module = get_coursemodule_from_instance($modulename, $instance); 1437 1438 if($module === false) return false; 1439 if(!calendar_get_course_cached($coursecache, $module->course)) { 1440 return false; 1441 } 1442 return $module; 1443 } 1444 1445 /** 1446 * Get current course cache 1447 * 1448 * @param array $coursecache list of course cache 1449 * @param int $courseid id of the course 1450 * @return stdClass $coursecache[$courseid] return the specific course cache 1451 */ 1452 function calendar_get_course_cached(&$coursecache, $courseid) { 1453 if (!isset($coursecache[$courseid])) { 1454 $coursecache[$courseid] = get_course($courseid); 1455 } 1456 return $coursecache[$courseid]; 1457 } 1458 1459 /** 1460 * Get group from groupid for calendar display 1461 * 1462 * @param int $groupid 1463 * @return stdClass group object with fields 'id', 'name' and 'courseid' 1464 */ 1465 function calendar_get_group_cached($groupid) { 1466 static $groupscache = array(); 1467 if (!isset($groupscache[$groupid])) { 1468 $groupscache[$groupid] = groups_get_group($groupid, 'id,name,courseid'); 1469 } 1470 return $groupscache[$groupid]; 1471 } 1472 1473 /** 1474 * Returns the courses to load events for, the 1475 * 1476 * @param array $courseeventsfrom An array of courses to load calendar events for 1477 * @param bool $ignorefilters specify the use of filters, false is set as default 1478 * @return array An array of courses, groups, and user to load calendar events for based upon filters 1479 */ 1480 function calendar_set_filters(array $courseeventsfrom, $ignorefilters = false) { 1481 global $USER, $CFG, $DB; 1482 1483 // For backwards compatability we have to check whether the courses array contains 1484 // just id's in which case we need to load course objects. 1485 $coursestoload = array(); 1486 foreach ($courseeventsfrom as $id => $something) { 1487 if (!is_object($something)) { 1488 $coursestoload[] = $id; 1489 unset($courseeventsfrom[$id]); 1490 } 1491 } 1492 if (!empty($coursestoload)) { 1493 // TODO remove this in 2.2 1494 debugging('calendar_set_filters now preferes an array of course objects with preloaded contexts', DEBUG_DEVELOPER); 1495 $courseeventsfrom = array_merge($courseeventsfrom, $DB->get_records_list('course', 'id', $coursestoload)); 1496 } 1497 1498 $courses = array(); 1499 $user = false; 1500 $group = false; 1501 1502 // capabilities that allow seeing group events from all groups 1503 // TODO: rewrite so that moodle/calendar:manageentries is not necessary here 1504 $allgroupscaps = array('moodle/site:accessallgroups', 'moodle/calendar:manageentries'); 1505 1506 $isloggedin = isloggedin(); 1507 1508 if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_COURSE)) { 1509 $courses = array_keys($courseeventsfrom); 1510 } 1511 if ($ignorefilters || calendar_show_event_type(CALENDAR_EVENT_GLOBAL)) { 1512 $courses[] = SITEID; 1513 } 1514 $courses = array_unique($courses); 1515 sort($courses); 1516 1517 if (!empty($courses) && in_array(SITEID, $courses)) { 1518 // Sort courses for consistent colour highlighting 1519 // Effectively ignoring SITEID as setting as last course id 1520 $key = array_search(SITEID, $courses); 1521 unset($courses[$key]); 1522 $courses[] = SITEID; 1523 } 1524 1525 if ($ignorefilters || ($isloggedin && calendar_show_event_type(CALENDAR_EVENT_USER))) { 1526 $user = $USER->id; 1527 } 1528 1529 if (!empty($courseeventsfrom) && (calendar_show_event_type(CALENDAR_EVENT_GROUP) || $ignorefilters)) { 1530 1531 if (count($courseeventsfrom)==1) { 1532 $course = reset($courseeventsfrom); 1533 if (has_any_capability($allgroupscaps, context_course::instance($course->id))) { 1534 $coursegroups = groups_get_all_groups($course->id, 0, 0, 'g.id'); 1535 $group = array_keys($coursegroups); 1536 } 1537 } 1538 if ($group === false) { 1539 if (!empty($CFG->calendar_adminseesall) && has_any_capability($allgroupscaps, context_system::instance())) { 1540 $group = true; 1541 } else if ($isloggedin) { 1542 $groupids = array(); 1543 1544 // We already have the courses to examine in $courses 1545 // For each course... 1546 foreach ($courseeventsfrom as $courseid => $course) { 1547 // If the user is an editing teacher in there, 1548 if (!empty($USER->groupmember[$course->id])) { 1549 // We've already cached the users groups for this course so we can just use that 1550 $groupids = array_merge($groupids, $USER->groupmember[$course->id]); 1551 } else if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) { 1552 // If this course has groups, show events from all of those related to the current user 1553 $coursegroups = groups_get_user_groups($course->id, $USER->id); 1554 $groupids = array_merge($groupids, $coursegroups['0']); 1555 } 1556 } 1557 if (!empty($groupids)) { 1558 $group = $groupids; 1559 } 1560 } 1561 } 1562 } 1563 if (empty($courses)) { 1564 $courses = false; 1565 } 1566 1567 return array($courses, $group, $user); 1568 } 1569 1570 /** 1571 * Return the capability for editing calendar event 1572 * 1573 * @param calendar_event $event event object 1574 * @return bool capability to edit event 1575 */ 1576 function calendar_edit_event_allowed($event) { 1577 global $USER, $DB; 1578 1579 // Must be logged in 1580 if (!isloggedin()) { 1581 return false; 1582 } 1583 1584 // can not be using guest account 1585 if (isguestuser()) { 1586 return false; 1587 } 1588 1589 // You cannot edit calendar subscription events presently. 1590 if (!empty($event->subscriptionid)) { 1591 return false; 1592 } 1593 1594 $sitecontext = context_system::instance(); 1595 // if user has manageentries at site level, return true 1596 if (has_capability('moodle/calendar:manageentries', $sitecontext)) { 1597 return true; 1598 } 1599 1600 // if groupid is set, it's definitely a group event 1601 if (!empty($event->groupid)) { 1602 // Allow users to add/edit group events if: 1603 // 1) They have manageentries (= entries for whole course) 1604 // 2) They have managegroupentries AND are in the group 1605 $group = $DB->get_record('groups', array('id'=>$event->groupid)); 1606 return $group && ( 1607 has_capability('moodle/calendar:manageentries', $event->context) || 1608 (has_capability('moodle/calendar:managegroupentries', $event->context) 1609 && groups_is_member($event->groupid))); 1610 } else if (!empty($event->courseid)) { 1611 // if groupid is not set, but course is set, 1612 // it's definiely a course event 1613 return has_capability('moodle/calendar:manageentries', $event->context); 1614 } else if (!empty($event->userid) && $event->userid == $USER->id) { 1615 // if course is not set, but userid id set, it's a user event 1616 return (has_capability('moodle/calendar:manageownentries', $event->context)); 1617 } else if (!empty($event->userid)) { 1618 return (has_capability('moodle/calendar:manageentries', $event->context)); 1619 } 1620 return false; 1621 } 1622 1623 /** 1624 * Returns the default courses to display on the calendar when there isn't a specific 1625 * course to display. 1626 * 1627 * @return array $courses Array of courses to display 1628 */ 1629 function calendar_get_default_courses() { 1630 global $CFG, $DB; 1631 1632 if (!isloggedin()) { 1633 return array(); 1634 } 1635 1636 $courses = array(); 1637 if (!empty($CFG->calendar_adminseesall) && has_capability('moodle/calendar:manageentries', context_system::instance())) { 1638 $select = ', ' . context_helper::get_preload_record_columns_sql('ctx'); 1639 $join = "LEFT JOIN {context} ctx ON (ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel)"; 1640 $sql = "SELECT c.* $select 1641 FROM {course} c 1642 $join 1643 WHERE EXISTS (SELECT 1 FROM {event} e WHERE e.courseid = c.id) 1644 "; 1645 $courses = $DB->get_records_sql($sql, array('contextlevel' => CONTEXT_COURSE), 0, 20); 1646 foreach ($courses as $course) { 1647 context_helper::preload_from_record($course); 1648 } 1649 return $courses; 1650 } 1651 1652 $courses = enrol_get_my_courses(); 1653 1654 return $courses; 1655 } 1656 1657 /** 1658 * Display calendar preference button 1659 * 1660 * @param stdClass $course course object 1661 * @return string return preference button in html 1662 */ 1663 function calendar_preferences_button(stdClass $course) { 1664 global $OUTPUT; 1665 1666 // Guests have no preferences 1667 if (!isloggedin() || isguestuser()) { 1668 return ''; 1669 } 1670 1671 return $OUTPUT->single_button(new moodle_url('/calendar/preferences.php', array('course' => $course->id)), get_string("preferences", "calendar")); 1672 } 1673 1674 /** 1675 * Get event format time 1676 * 1677 * @param calendar_event $event event object 1678 * @param int $now current time in gmt 1679 * @param array $linkparams list of params for event link 1680 * @param bool $usecommonwords the words as formatted date/time. 1681 * @param int $showtime determine the show time GMT timestamp 1682 * @return string $eventtime link/string for event time 1683 */ 1684 function calendar_format_event_time($event, $now, $linkparams = null, $usecommonwords = true, $showtime = 0) { 1685 $starttime = $event->timestart; 1686 $endtime = $event->timestart + $event->timeduration; 1687 1688 if (empty($linkparams) || !is_array($linkparams)) { 1689 $linkparams = array(); 1690 } 1691 1692 $linkparams['view'] = 'day'; 1693 1694 // OK, now to get a meaningful display... 1695 // Check if there is a duration for this event. 1696 if ($event->timeduration) { 1697 // Get the midnight of the day the event will start. 1698 $usermidnightstart = usergetmidnight($starttime); 1699 // Get the midnight of the day the event will end. 1700 $usermidnightend = usergetmidnight($endtime); 1701 // Check if we will still be on the same day. 1702 if ($usermidnightstart == $usermidnightend) { 1703 // Check if we are running all day. 1704 if ($event->timeduration == DAYSECS) { 1705 $time = get_string('allday', 'calendar'); 1706 } else { // Specify the time we will be running this from. 1707 $datestart = calendar_time_representation($starttime); 1708 $dateend = calendar_time_representation($endtime); 1709 $time = $datestart . ' <strong>»</strong> ' . $dateend; 1710 } 1711 1712 // Set printable representation. 1713 if (!$showtime) { 1714 $day = calendar_day_representation($event->timestart, $now, $usecommonwords); 1715 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL . 'view.php', $linkparams), 0, 0, 0, $endtime); 1716 $eventtime = html_writer::link($url, $day) . ', ' . $time; 1717 } else { 1718 $eventtime = $time; 1719 } 1720 } else { // It must spans two or more days. 1721 $daystart = calendar_day_representation($event->timestart, $now, $usecommonwords) . ', '; 1722 if ($showtime == $usermidnightstart) { 1723 $daystart = ''; 1724 } 1725 $timestart = calendar_time_representation($event->timestart); 1726 $dayend = calendar_day_representation($event->timestart + $event->timeduration, $now, $usecommonwords) . ', '; 1727 if ($showtime == $usermidnightend) { 1728 $dayend = ''; 1729 } 1730 $timeend = calendar_time_representation($event->timestart + $event->timeduration); 1731 1732 // Set printable representation. 1733 if ($now >= $usermidnightstart && $now < strtotime('+1 day', $usermidnightstart)) { 1734 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL . 'view.php', $linkparams), 0, 0, 0, $endtime); 1735 $eventtime = $timestart . ' <strong>»</strong> ' . html_writer::link($url, $dayend) . $timeend; 1736 } else { 1737 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL . 'view.php', $linkparams), 0, 0, 0, $endtime); 1738 $eventtime = html_writer::link($url, $daystart) . $timestart . ' <strong>»</strong> '; 1739 1740 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL . 'view.php', $linkparams), 0, 0, 0, $starttime); 1741 $eventtime .= html_writer::link($url, $dayend) . $timeend; 1742 } 1743 } 1744 } else { // There is no time duration. 1745 $time = calendar_time_representation($event->timestart); 1746 // Set printable representation. 1747 if (!$showtime) { 1748 $day = calendar_day_representation($event->timestart, $now, $usecommonwords); 1749 $url = calendar_get_link_href(new moodle_url(CALENDAR_URL . 'view.php', $linkparams), 0, 0, 0, $starttime); 1750 $eventtime = html_writer::link($url, $day) . ', ' . trim($time); 1751 } else { 1752 $eventtime = $time; 1753 } 1754 } 1755 1756 // Check if It has expired. 1757 if ($event->timestart + $event->timeduration < $now) { 1758 $eventtime = '<span class="dimmed_text">' . str_replace(' href=', ' class="dimmed" href=', $eventtime) . '</span>'; 1759 } 1760 1761 return $eventtime; 1762 } 1763 1764 /** 1765 * Display month selector options 1766 * 1767 * @param string $name for the select element 1768 * @param string|array $selected options for select elements 1769 */ 1770 function calendar_print_month_selector($name, $selected) { 1771 $months = array(); 1772 for ($i=1; $i<=12; $i++) { 1773 $months[$i] = userdate(gmmktime(12, 0, 0, $i, 15, 2000), '%B'); 1774 } 1775 echo html_writer::label(get_string('months'), 'menu'. $name, false, array('class' => 'accesshide')); 1776 echo html_writer::select($months, $name, $selected, false); 1777 } 1778 1779 /** 1780 * Checks to see if the requested type of event should be shown for the given user. 1781 * 1782 * @param CALENDAR_EVENT_GLOBAL|CALENDAR_EVENT_COURSE|CALENDAR_EVENT_GROUP|CALENDAR_EVENT_USER $type 1783 * The type to check the display for (default is to display all) 1784 * @param stdClass|int|null $user The user to check for - by default the current user 1785 * @return bool True if the tyep should be displayed false otherwise 1786 */ 1787 function calendar_show_event_type($type, $user = null) { 1788 $default = CALENDAR_EVENT_GLOBAL + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP + CALENDAR_EVENT_USER; 1789 if (get_user_preferences('calendar_persistflt', 0, $user) === 0) { 1790 global $SESSION; 1791 if (!isset($SESSION->calendarshoweventtype)) { 1792 $SESSION->calendarshoweventtype = $default; 1793 } 1794 return $SESSION->calendarshoweventtype & $type; 1795 } else { 1796 return get_user_preferences('calendar_savedflt', $default, $user) & $type; 1797 } 1798 } 1799 1800 /** 1801 * Sets the display of the event type given $display. 1802 * 1803 * If $display = true the event type will be shown. 1804 * If $display = false the event type will NOT be shown. 1805 * If $display = null the current value will be toggled and saved. 1806 * 1807 * @param CALENDAR_EVENT_GLOBAL|CALENDAR_EVENT_COURSE|CALENDAR_EVENT_GROUP|CALENDAR_EVENT_USER $type object of CALENDAR_EVENT_XXX 1808 * @param bool $display option to display event type 1809 * @param stdClass|int $user moodle user object or id, null means current user 1810 */ 1811 function calendar_set_event_type_display($type, $display = null, $user = null) { 1812 $persist = get_user_preferences('calendar_persistflt', 0, $user); 1813 $default = CALENDAR_EVENT_GLOBAL + CALENDAR_EVENT_COURSE + CALENDAR_EVENT_GROUP + CALENDAR_EVENT_USER; 1814 if ($persist === 0) { 1815 global $SESSION; 1816 if (!isset($SESSION->calendarshoweventtype)) { 1817 $SESSION->calendarshoweventtype = $default; 1818 } 1819 $preference = $SESSION->calendarshoweventtype; 1820 } else { 1821 $preference = get_user_preferences('calendar_savedflt', $default, $user); 1822 } 1823 $current = $preference & $type; 1824 if ($display === null) { 1825 $display = !$current; 1826 } 1827 if ($display && !$current) { 1828 $preference += $type; 1829 } else if (!$display && $current) { 1830 $preference -= $type; 1831 } 1832 if ($persist === 0) { 1833 $SESSION->calendarshoweventtype = $preference; 1834 } else { 1835 if ($preference == $default) { 1836 unset_user_preference('calendar_savedflt', $user); 1837 } else { 1838 set_user_preference('calendar_savedflt', $preference, $user); 1839 } 1840 } 1841 } 1842 1843 /** 1844 * Get calendar's allowed types 1845 * 1846 * @param stdClass $allowed list of allowed edit for event type 1847 * @param stdClass|int $course object of a course or course id 1848 */ 1849 function calendar_get_allowed_types(&$allowed, $course = null) { 1850 global $USER, $CFG, $DB; 1851 $allowed = new stdClass(); 1852 $allowed->user = has_capability('moodle/calendar:manageownentries', context_system::instance()); 1853 $allowed->groups = false; // This may change just below 1854 $allowed->courses = false; // This may change just below 1855 $allowed->site = has_capability('moodle/calendar:manageentries', context_course::instance(SITEID)); 1856 1857 if (!empty($course)) { 1858 if (!is_object($course)) { 1859 $course = $DB->get_record('course', array('id' => $course), '*', MUST_EXIST); 1860 } 1861 if ($course->id != SITEID) { 1862 $coursecontext = context_course::instance($course->id); 1863 $allowed->user = has_capability('moodle/calendar:manageownentries', $coursecontext); 1864 1865 if (has_capability('moodle/calendar:manageentries', $coursecontext)) { 1866 $allowed->courses = array($course->id => 1); 1867 1868 if ($course->groupmode != NOGROUPS || !$course->groupmodeforce) { 1869 if (has_capability('moodle/site:accessallgroups', $coursecontext)) { 1870 $allowed->groups = groups_get_all_groups($course->id); 1871 } else { 1872 $allowed->groups = groups_get_all_groups($course->id, $USER->id); 1873 } 1874 } 1875 } else if (has_capability('moodle/calendar:managegroupentries', $coursecontext)) { 1876 if($course->groupmode != NOGROUPS || !$course->groupmodeforce) { 1877 if (has_capability('moodle/site:accessallgroups', $coursecontext)) { 1878 $allowed->groups = groups_get_all_groups($course->id); 1879 } else { 1880 $allowed->groups = groups_get_all_groups($course->id, $USER->id); 1881 } 1882 } 1883 } 1884 } 1885 } 1886 } 1887 1888 /** 1889 * See if user can add calendar entries at all 1890 * used to print the "New Event" button 1891 * 1892 * @param stdClass $course object of a course or course id 1893 * @return bool has the capability to add at least one event type 1894 */ 1895 function calendar_user_can_add_event($course) { 1896 if (!isloggedin() || isguestuser()) { 1897 return false; 1898 } 1899 calendar_get_allowed_types($allowed, $course); 1900 return (bool)($allowed->user || $allowed->groups || $allowed->courses || $allowed->site); 1901 } 1902 1903 /** 1904 * Check wether the current user is permitted to add events 1905 * 1906 * @param stdClass $event object of event 1907 * @return bool has the capability to add event 1908 */ 1909 function calendar_add_event_allowed($event) { 1910 global $USER, $DB; 1911 1912 // can not be using guest account 1913 if (!isloggedin() or isguestuser()) { 1914 return false; 1915 } 1916 1917 $sitecontext = context_system::instance(); 1918 // if user has manageentries at site level, always return true 1919 if (has_capability('moodle/calendar:manageentries', $sitecontext)) { 1920 return true; 1921 } 1922 1923 switch ($event->eventtype) { 1924 case 'course': 1925 return has_capability('moodle/calendar:manageentries', $event->context); 1926 1927 case 'group': 1928 // Allow users to add/edit group events if: 1929 // 1) They have manageentries (= entries for whole course) 1930 // 2) They have managegroupentries AND are in the group 1931 $group = $DB->get_record('groups', array('id'=>$event->groupid)); 1932 return $group && ( 1933 has_capability('moodle/calendar:manageentries', $event->context) || 1934 (has_capability('moodle/calendar:managegroupentries', $event->context) 1935 && groups_is_member($event->groupid))); 1936 1937 case 'user': 1938 if ($event->userid == $USER->id) { 1939 return (has_capability('moodle/calendar:manageownentries', $event->context)); 1940 } 1941 //there is no 'break;' intentionally 1942 1943 case 'site': 1944 return has_capability('moodle/calendar:manageentries', $event->context); 1945 1946 default: 1947 return has_capability('moodle/calendar:manageentries', $event->context); 1948 } 1949 } 1950 1951 /** 1952 * Manage calendar events 1953 * 1954 * This class provides the required functionality in order to manage calendar events. 1955 * It was introduced as part of Moodle 2.0 and was created in order to provide a 1956 * better framework for dealing with calendar events in particular regard to file 1957 * handling through the new file API 1958 * 1959 * @package core_calendar 1960 * @category calendar 1961 * @copyright 2009 Sam Hemelryk 1962 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 1963 * 1964 * @property int $id The id within the event table 1965 * @property string $name The name of the event 1966 * @property string $description The description of the event 1967 * @property int $format The format of the description FORMAT_? 1968 * @property int $courseid The course the event is associated with (0 if none) 1969 * @property int $groupid The group the event is associated with (0 if none) 1970 * @property int $userid The user the event is associated with (0 if none) 1971 * @property int $repeatid If this is a repeated event this will be set to the 1972 * id of the original 1973 * @property string $modulename If added by a module this will be the module name 1974 * @property int $instance If added by a module this will be the module instance 1975 * @property string $eventtype The event type 1976 * @property int $timestart The start time as a timestamp 1977 * @property int $timeduration The duration of the event in seconds 1978 * @property int $visible 1 if the event is visible 1979 * @property int $uuid ? 1980 * @property int $sequence ? 1981 * @property int $timemodified The time last modified as a timestamp 1982 */ 1983 class calendar_event { 1984 1985 /** @var array An object containing the event properties can be accessed via the magic __get/set methods */ 1986 protected $properties = null; 1987 1988 /** 1989 * @var string The converted event discription with file paths resolved. This gets populated when someone requests description for the first time */ 1990 protected $_description = null; 1991 1992 /** @var array The options to use with this description editor */ 1993 protected $editoroptions = array( 1994 'subdirs'=>false, 1995 'forcehttps'=>false, 1996 'maxfiles'=>-1, 1997 'maxbytes'=>null, 1998 'trusttext'=>false); 1999 2000 /** @var object The context to use with the description editor */ 2001 protected $editorcontext = null; 2002 2003 /** 2004 * Instantiates a new event and optionally populates its properties with the 2005 * data provided 2006 * 2007 * @param stdClass $data Optional. An object containing the properties to for 2008 * an event 2009 */ 2010 public function __construct($data=null) { 2011 global $CFG, $USER; 2012 2013 // First convert to object if it is not already (should either be object or assoc array) 2014 if (!is_object($data)) { 2015 $data = (object)$data; 2016 } 2017 2018 $this->editoroptions['maxbytes'] = $CFG->maxbytes; 2019 2020 $data->eventrepeats = 0; 2021 2022 if (empty($data->id)) { 2023 $data->id = null; 2024 } 2025 2026 if (!empty($data->subscriptionid)) { 2027 $data->subscription = calendar_get_subscription($data->subscriptionid); 2028 } 2029 2030 // Default to a user event 2031 if (empty($data->eventtype)) { 2032 $data->eventtype = 'user'; 2033 } 2034 2035 // Default to the current user 2036 if (empty($data->userid)) { 2037 $data->userid = $USER->id; 2038 } 2039 2040 if (!empty($data->timeduration) && is_array($data->timeduration)) { 2041 $data->timeduration = make_timestamp($data->timeduration['year'], $data->timeduration['month'], $data->timeduration['day'], $data->timeduration['hour'], $data->timeduration['minute']) - $data->timestart; 2042 } 2043 if (!empty($data->description) && is_array($data->description)) { 2044 $data->format = $data->description['format']; 2045 $data->description = $data->description['text']; 2046 } else if (empty($data->description)) { 2047 $data->description = ''; 2048 $data->format = editors_get_preferred_format(); 2049 } 2050 // Ensure form is defaulted correctly 2051 if (empty($data->format)) { 2052 $data->format = editors_get_preferred_format(); 2053 } 2054 2055 if (empty($data->context)) { 2056 $data->context = $this->calculate_context($data); 2057 } 2058 $this->properties = $data; 2059 } 2060 2061 /** 2062 * Magic property method 2063 * 2064 * Attempts to call a set_$key method if one exists otherwise falls back 2065 * to simply set the property 2066 * 2067 * @param string $key property name 2068 * @param mixed $value value of the property 2069 */ 2070 public function __set($key, $value) { 2071 if (method_exists($this, 'set_'.$key)) { 2072 $this->{'set_'.$key}($value); 2073 } 2074 $this->properties->{$key} = $value; 2075 } 2076 2077 /** 2078 * Magic get method 2079 * 2080 * Attempts to call a get_$key method to return the property and ralls over 2081 * to return the raw property 2082 * 2083 * @param string $key property name 2084 * @return mixed property value 2085 */ 2086 public function __get($key) { 2087 if (method_exists($this, 'get_'.$key)) { 2088 return $this->{'get_'.$key}(); 2089 } 2090 if (!isset($this->properties->{$key})) { 2091 throw new coding_exception('Undefined property requested'); 2092 } 2093 return $this->properties->{$key}; 2094 } 2095 2096 /** 2097 * Stupid PHP needs an isset magic method if you use the get magic method and 2098 * still want empty calls to work.... blah ~! 2099 * 2100 * @param string $key $key property name 2101 * @return bool|mixed property value, false if property is not exist 2102 */ 2103 public function __isset($key) { 2104 return !empty($this->properties->{$key}); 2105 } 2106 2107 /** 2108 * Calculate the context value needed for calendar_event. 2109 * Event's type can be determine by the available value store in $data 2110 * It is important to check for the existence of course/courseid to determine 2111 * the course event. 2112 * Default value is set to CONTEXT_USER 2113 * 2114 * @param stdClass $data information about event 2115 * @return stdClass The context object. 2116 */ 2117 protected function calculate_context(stdClass $data) { 2118 global $USER, $DB; 2119 2120 $context = null; 2121 if (isset($data->courseid) && $data->courseid > 0) { 2122 $context = context_course::instance($data->courseid); 2123 } else if (isset($data->course) && $data->course > 0) { 2124 $context = context_course::instance($data->course); 2125 } else if (isset($data->groupid) && $data->groupid > 0) { 2126 $group = $DB->get_record('groups', array('id'=>$data->groupid)); 2127 $context = context_course::instance($group->courseid); 2128 } else if (isset($data->userid) && $data->userid > 0 && $data->userid == $USER->id) { 2129 $context = context_user::instance($data->userid); 2130 } else if (isset($data->userid) && $data->userid > 0 && $data->userid != $USER->id && 2131 isset($data->instance) && $data->instance > 0) { 2132 $cm = get_coursemodule_from_instance($data->modulename, $data->instance, 0, false, MUST_EXIST); 2133 $context = context_course::instance($cm->course); 2134 } else { 2135 $context = context_user::instance($data->userid); 2136 } 2137 2138 return $context; 2139 } 2140 2141 /** 2142 * Returns an array of editoroptions for this event: Called by __get 2143 * Please use $blah = $event->editoroptions; 2144 * 2145 * @return array event editor options 2146 */ 2147 protected function get_editoroptions() { 2148 return $this->editoroptions; 2149 } 2150 2151 /** 2152 * Returns an event description: Called by __get 2153 * Please use $blah = $event->description; 2154 * 2155 * @return string event description 2156 */ 2157 protected function get_description() { 2158 global $CFG; 2159 2160 require_once($CFG->libdir . '/filelib.php'); 2161 2162 if ($this->_description === null) { 2163 // Check if we have already resolved the context for this event 2164 if ($this->editorcontext === null) { 2165 // Switch on the event type to decide upon the appropriate context 2166 // to use for this event 2167 $this->editorcontext = $this->properties->context; 2168 if ($this->properties->eventtype != 'user' && $this->properties->eventtype != 'course' 2169 && $this->properties->eventtype != 'site' && $this->properties->eventtype != 'group') { 2170 return clean_text($this->properties->description, $this->properties->format); 2171 } 2172 } 2173 2174 // Work out the item id for the editor, if this is a repeated event then the files will 2175 // be associated with the original 2176 if (!empty($this->properties->repeatid) && $this->properties->repeatid > 0) { 2177 $itemid = $this->properties->repeatid; 2178 } else { 2179 $itemid = $this->properties->id; 2180 } 2181 2182 // Convert file paths in the description so that things display correctly 2183 $this->_description = file_rewrite_pluginfile_urls($this->properties->description, 'pluginfile.php', $this->editorcontext->id, 'calendar', 'event_description', $itemid); 2184 // Clean the text so no nasties get through 2185 $this->_description = clean_text($this->_description, $this->properties->format); 2186 } 2187 // Finally return the description 2188 return $this->_description; 2189 } 2190 2191 /** 2192 * Return the number of repeat events there are in this events series 2193 * 2194 * @return int number of event repeated 2195 */ 2196 public function count_repeats() { 2197 global $DB; 2198 if (!empty($this->properties->repeatid)) { 2199 $this->properties->eventrepeats = $DB->count_records('event', array('repeatid'=>$this->properties->repeatid)); 2200 // We don't want to count ourselves 2201 $this->properties->eventrepeats--; 2202 } 2203 return $this->properties->eventrepeats; 2204 } 2205 2206 /** 2207 * Update or create an event within the database 2208 * 2209 * Pass in a object containing the event properties and this function will 2210 * insert it into the database and deal with any associated files 2211 * 2212 * @see self::create() 2213 * @see self::update() 2214 * 2215 * @param stdClass $data object of event 2216 * @param bool $checkcapability if moodle should check calendar managing capability or not 2217 * @return bool event updated 2218 */ 2219 public function update($data, $checkcapability=true) { 2220 global $DB, $USER; 2221 2222 foreach ($data as $key=>$value) { 2223 $this->properties->$key = $value; 2224 } 2225 2226 $this->properties->timemodified = time(); 2227 $usingeditor = (!empty($this->properties->description) && is_array($this->properties->description)); 2228 2229 // Prepare event data. 2230 $eventargs = array( 2231 'context' => $this->properties->context, 2232 'objectid' => $this->properties->id, 2233 'other' => array( 2234 'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid, 2235 'timestart' => $this->properties->timestart, 2236 'name' => $this->properties->name 2237 ) 2238 ); 2239 2240 if (empty($this->properties->id) || $this->properties->id < 1) { 2241 2242 if ($checkcapability) { 2243 if (!calendar_add_event_allowed($this->properties)) { 2244 print_error('nopermissiontoupdatecalendar'); 2245 } 2246 } 2247 2248 if ($usingeditor) { 2249 switch ($this->properties->eventtype) { 2250 case 'user': 2251 $this->properties->courseid = 0; 2252 $this->properties->course = 0; 2253 $this->properties->groupid = 0; 2254 $this->properties->userid = $USER->id; 2255 break; 2256 case 'site': 2257 $this->properties->courseid = SITEID; 2258 $this->properties->course = SITEID; 2259 $this->properties->groupid = 0; 2260 $this->properties->userid = $USER->id; 2261 break; 2262 case 'course': 2263 $this->properties->groupid = 0; 2264 $this->properties->userid = $USER->id; 2265 break; 2266 case 'group': 2267 $this->properties->userid = $USER->id; 2268 break; 2269 default: 2270 // Ewww we should NEVER get here, but just incase we do lets 2271 // fail gracefully 2272 $usingeditor = false; 2273 break; 2274 } 2275 2276 // If we are actually using the editor, we recalculate the context because some default values 2277 // were set when calculate_context() was called from the constructor. 2278 if ($usingeditor) { 2279 $this->properties->context = $this->calculate_context($this->properties); 2280 $this->editorcontext = $this->properties->context; 2281 } 2282 2283 $editor = $this->properties->description; 2284 $this->properties->format = $this->properties->description['format']; 2285 $this->properties->description = $this->properties->description['text']; 2286 } 2287 2288 // Insert the event into the database 2289 $this->properties->id = $DB->insert_record('event', $this->properties); 2290 2291 if ($usingeditor) { 2292 $this->properties->description = file_save_draft_area_files( 2293 $editor['itemid'], 2294 $this->editorcontext->id, 2295 'calendar', 2296 'event_description', 2297 $this->properties->id, 2298 $this->editoroptions, 2299 $editor['text'], 2300 $this->editoroptions['forcehttps']); 2301 $DB->set_field('event', 'description', $this->properties->description, array('id'=>$this->properties->id)); 2302 } 2303 2304 // Log the event entry. 2305 $eventargs['objectid'] = $this->properties->id; 2306 $eventargs['context'] = $this->properties->context; 2307 $event = \core\event\calendar_event_created::create($eventargs); 2308 $event->trigger(); 2309 2310 $repeatedids = array(); 2311 2312 if (!empty($this->properties->repeat)) { 2313 $this->properties->repeatid = $this->properties->id; 2314 $DB->set_field('event', 'repeatid', $this->properties->repeatid, array('id'=>$this->properties->id)); 2315 2316 $eventcopy = clone($this->properties); 2317 unset($eventcopy->id); 2318 2319 $timestart = new DateTime('@' . $eventcopy->timestart); 2320 $timestart->setTimezone(core_date::get_user_timezone_object()); 2321 2322 for($i = 1; $i < $eventcopy->repeats; $i++) { 2323 2324 $timestart->add(new DateInterval('P7D')); 2325 $eventcopy->timestart = $timestart->getTimestamp(); 2326 2327 // Get the event id for the log record. 2328 $eventcopyid = $DB->insert_record('event', $eventcopy); 2329 2330 // If the context has been set delete all associated files 2331 if ($usingeditor) { 2332 $fs = get_file_storage(); 2333 $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id); 2334 foreach ($files as $file) { 2335 $fs->create_file_from_storedfile(array('itemid'=>$eventcopyid), $file); 2336 } 2337 } 2338 2339 $repeatedids[] = $eventcopyid; 2340 2341 // Trigger an event. 2342 $eventargs['objectid'] = $eventcopyid; 2343 $eventargs['other']['timestart'] = $eventcopy->timestart; 2344 $event = \core\event\calendar_event_created::create($eventargs); 2345 $event->trigger(); 2346 } 2347 } 2348 2349 // Hook for tracking added events 2350 self::calendar_event_hook('add_event', array($this->properties, $repeatedids)); 2351 return true; 2352 } else { 2353 2354 if ($checkcapability) { 2355 if(!calendar_edit_event_allowed($this->properties)) { 2356 print_error('nopermissiontoupdatecalendar'); 2357 } 2358 } 2359 2360 if ($usingeditor) { 2361 if ($this->editorcontext !== null) { 2362 $this->properties->description = file_save_draft_area_files( 2363 $this->properties->description['itemid'], 2364 $this->editorcontext->id, 2365 'calendar', 2366 'event_description', 2367 $this->properties->id, 2368 $this->editoroptions, 2369 $this->properties->description['text'], 2370 $this->editoroptions['forcehttps']); 2371 } else { 2372 $this->properties->format = $this->properties->description['format']; 2373 $this->properties->description = $this->properties->description['text']; 2374 } 2375 } 2376 2377 $event = $DB->get_record('event', array('id'=>$this->properties->id)); 2378 2379 $updaterepeated = (!empty($this->properties->repeatid) && !empty($this->properties->repeateditall)); 2380 2381 if ($updaterepeated) { 2382 // Update all 2383 if ($this->properties->timestart != $event->timestart) { 2384 $timestartoffset = $this->properties->timestart - $event->timestart; 2385 $sql = "UPDATE {event} 2386 SET name = ?, 2387 description = ?, 2388 timestart = timestart + ?, 2389 timeduration = ?, 2390 timemodified = ? 2391 WHERE repeatid = ?"; 2392 $params = array($this->properties->name, $this->properties->description, $timestartoffset, $this->properties->timeduration, time(), $event->repeatid); 2393 } else { 2394 $sql = "UPDATE {event} SET name = ?, description = ?, timeduration = ?, timemodified = ? WHERE repeatid = ?"; 2395 $params = array($this->properties->name, $this->properties->description, $this->properties->timeduration, time(), $event->repeatid); 2396 } 2397 $DB->execute($sql, $params); 2398 2399 // Trigger an update event for each of the calendar event. 2400 $events = $DB->get_records('event', array('repeatid' => $event->repeatid), '', 'id,timestart'); 2401 foreach ($events as $event) { 2402 $eventargs['objectid'] = $event->id; 2403 $eventargs['other']['timestart'] = $event->timestart; 2404 $event = \core\event\calendar_event_updated::create($eventargs); 2405 $event->trigger(); 2406 } 2407 } else { 2408 $DB->update_record('event', $this->properties); 2409 $event = calendar_event::load($this->properties->id); 2410 $this->properties = $event->properties(); 2411 2412 // Trigger an update event. 2413 $event = \core\event\calendar_event_updated::create($eventargs); 2414 $event->trigger(); 2415 } 2416 2417 // Hook for tracking event updates 2418 self::calendar_event_hook('update_event', array($this->properties, $updaterepeated)); 2419 return true; 2420 } 2421 } 2422 2423 /** 2424 * Deletes an event and if selected an repeated events in the same series 2425 * 2426 * This function deletes an event, any associated events if $deleterepeated=true, 2427 * and cleans up any files associated with the events. 2428 * 2429 * @see self::delete() 2430 * 2431 * @param bool $deleterepeated delete event repeatedly 2432 * @return bool succession of deleting event 2433 */ 2434 public function delete($deleterepeated=false) { 2435 global $DB; 2436 2437 // If $this->properties->id is not set then something is wrong 2438 if (empty($this->properties->id)) { 2439 debugging('Attempting to delete an event before it has been loaded', DEBUG_DEVELOPER); 2440 return false; 2441 } 2442 $calevent = $DB->get_record('event', array('id' => $this->properties->id), '*', MUST_EXIST); 2443 // Delete the event 2444 $DB->delete_records('event', array('id'=>$this->properties->id)); 2445 2446 // Trigger an event for the delete action. 2447 $eventargs = array( 2448 'context' => $this->properties->context, 2449 'objectid' => $this->properties->id, 2450 'other' => array( 2451 'repeatid' => empty($this->properties->repeatid) ? 0 : $this->properties->repeatid, 2452 'timestart' => $this->properties->timestart, 2453 'name' => $this->properties->name 2454 )); 2455 $event = \core\event\calendar_event_deleted::create($eventargs); 2456 $event->add_record_snapshot('event', $calevent); 2457 $event->trigger(); 2458 2459 // If we are deleting parent of a repeated event series, promote the next event in the series as parent 2460 if (($this->properties->id == $this->properties->repeatid) && !$deleterepeated) { 2461 $newparent = $DB->get_field_sql("SELECT id from {event} where repeatid = ? order by id ASC", array($this->properties->id), IGNORE_MULTIPLE); 2462 if (!empty($newparent)) { 2463 $DB->execute("UPDATE {event} SET repeatid = ? WHERE repeatid = ?", array($newparent, $this->properties->id)); 2464 // Get all records where the repeatid is the same as the event being removed 2465 $events = $DB->get_records('event', array('repeatid' => $newparent)); 2466 // For each of the returned events trigger the event_update hook and an update event. 2467 foreach ($events as $event) { 2468 // Trigger an event for the update. 2469 $eventargs['objectid'] = $event->id; 2470 $eventargs['other']['timestart'] = $event->timestart; 2471 $event = \core\event\calendar_event_updated::create($eventargs); 2472 $event->trigger(); 2473 2474 self::calendar_event_hook('update_event', array($event, false)); 2475 } 2476 } 2477 } 2478 2479 // If the editor context hasn't already been set then set it now 2480 if ($this->editorcontext === null) { 2481 $this->editorcontext = $this->properties->context; 2482 } 2483 2484 // If the context has been set delete all associated files 2485 if ($this->editorcontext !== null) { 2486 $fs = get_file_storage(); 2487 $files = $fs->get_area_files($this->editorcontext->id, 'calendar', 'event_description', $this->properties->id); 2488 foreach ($files as $file) { 2489 $file->delete(); 2490 } 2491 } 2492 2493 // Fire the event deleted hook 2494 self::calendar_event_hook('delete_event', array($this->properties->id, $deleterepeated)); 2495 2496 // If we need to delete repeated events then we will fetch them all and delete one by one 2497 if ($deleterepeated && !empty($this->properties->repeatid) && $this->properties->repeatid > 0) { 2498 // Get all records where the repeatid is the same as the event being removed 2499 $events = $DB->get_records('event', array('repeatid'=>$this->properties->repeatid)); 2500 // For each of the returned events populate a calendar_event object and call delete 2501 // make sure the arg passed is false as we are already deleting all repeats 2502 foreach ($events as $event) { 2503 $event = new calendar_event($event); 2504 $event->delete(false); 2505 } 2506 } 2507 2508 return true; 2509 } 2510 2511 /** 2512 * Fetch all event properties 2513 * 2514 * This function returns all of the events properties as an object and optionally 2515 * can prepare an editor for the description field at the same time. This is 2516 * designed to work when the properties are going to be used to set the default 2517 * values of a moodle forms form. 2518 * 2519 * @param bool $prepareeditor If set to true a editor is prepared for use with 2520 * the mforms editor element. (for description) 2521 * @return stdClass Object containing event properties 2522 */ 2523 public function properties($prepareeditor=false) { 2524 global $USER, $CFG, $DB; 2525 2526 // First take a copy of the properties. We don't want to actually change the 2527 // properties or we'd forever be converting back and forwards between an 2528 // editor formatted description and not 2529 $properties = clone($this->properties); 2530 // Clean the description here 2531 $properties->description = clean_text($properties->description, $properties->format); 2532 2533 // If set to true we need to prepare the properties for use with an editor 2534 // and prepare the file area 2535 if ($prepareeditor) { 2536 2537 // We may or may not have a property id. If we do then we need to work 2538 // out the context so we can copy the existing files to the draft area 2539 if (!empty($properties->id)) { 2540 2541 if ($properties->eventtype === 'site') { 2542 // Site context 2543 $this->editorcontext = $this->properties->context; 2544 } else if ($properties->eventtype === 'user') { 2545 // User context 2546 $this->editorcontext = $this->properties->context; 2547 } else if ($properties->eventtype === 'group' || $properties->eventtype === 'course') { 2548 // First check the course is valid 2549 $course = $DB->get_record('course', array('id'=>$properties->courseid)); 2550 if (!$course) { 2551 print_error('invalidcourse'); 2552 } 2553 // Course context 2554 $this->editorcontext = $this->properties->context; 2555 // We have a course and are within the course context so we had 2556 // better use the courses max bytes value 2557 $this->editoroptions['maxbytes'] = $course->maxbytes; 2558 } else { 2559 // If we get here we have a custom event type as used by some 2560 // modules. In this case the event will have been added by 2561 // code and we won't need the editor 2562 $this->editoroptions['maxbytes'] = 0; 2563 $this->editoroptions['maxfiles'] = 0; 2564 } 2565 2566 if (empty($this->editorcontext) || empty($this->editorcontext->id)) { 2567 $contextid = false; 2568 } else { 2569 // Get the context id that is what we really want 2570 $contextid = $this->editorcontext->id; 2571 } 2572 } else { 2573 2574 // If we get here then this is a new event in which case we don't need a 2575 // context as there is no existing files to copy to the draft area. 2576 $contextid = null; 2577 } 2578 2579 // If the contextid === false we don't support files so no preparing 2580 // a draft area 2581 if ($contextid !== false) { 2582 // Just encase it has already been submitted 2583 $draftiddescription = file_get_submitted_draft_itemid('description'); 2584 // Prepare the draft area, this copies existing files to the draft area as well 2585 $properties->description = file_prepare_draft_area($draftiddescription, $contextid, 'calendar', 'event_description', $properties->id, $this->editoroptions, $properties->description); 2586 } else { 2587 $draftiddescription = 0; 2588 } 2589 2590 // Structure the description field as the editor requires 2591 $properties->description = array('text'=>$properties->description, 'format'=>$properties->format, 'itemid'=>$draftiddescription); 2592 } 2593 2594 // Finally return the properties 2595 return $properties; 2596 } 2597 2598 /** 2599 * Toggles the visibility of an event 2600 * 2601 * @param null|bool $force If it is left null the events visibility is flipped, 2602 * If it is false the event is made hidden, if it is true it 2603 * is made visible. 2604 * @return bool if event is successfully updated, toggle will be visible 2605 */ 2606 public function toggle_visibility($force=null) { 2607 global $CFG, $DB; 2608 2609 // Set visible to the default if it is not already set 2610 if (empty($this->properties->visible)) { 2611 $this->properties->visible = 1; 2612 } 2613 2614 if ($force === true || ($force !== false && $this->properties->visible == 0)) { 2615 // Make this event visible 2616 $this->properties->visible = 1; 2617 // Fire the hook 2618 self::calendar_event_hook('show_event', array($this->properties)); 2619 } else { 2620 // Make this event hidden 2621 $this->properties->visible = 0; 2622 // Fire the hook 2623 self::calendar_event_hook('hide_event', array($this->properties)); 2624 } 2625 2626 // Update the database to reflect this change 2627 return $DB->set_field('event', 'visible', $this->properties->visible, array('id'=>$this->properties->id)); 2628 } 2629 2630 /** 2631 * Attempts to call the hook for the specified action should a calendar type 2632 * by set $CFG->calendar, and the appopriate function defined 2633 * 2634 * @param string $action One of `update_event`, `add_event`, `delete_event`, `show_event`, `hide_event` 2635 * @param array $args The args to pass to the hook, usually the event is the first element 2636 * @return bool attempts to call event hook 2637 */ 2638 public static function calendar_event_hook($action, array $args) { 2639 global $CFG; 2640 static $extcalendarinc; 2641 if ($extcalendarinc === null) { 2642 if (!empty($CFG->calendar)) { 2643 if (is_readable($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php')) { 2644 include_once($CFG->dirroot .'/calendar/'. $CFG->calendar .'/lib.php'); 2645 $extcalendarinc = true; 2646 } else { 2647 debugging("Calendar lib file missing or not readable at /calendar/{$CFG->calendar}/lib.php.", 2648 DEBUG_DEVELOPER); 2649 $extcalendarinc = false; 2650 } 2651 } else { 2652 $extcalendarinc = false; 2653 } 2654 } 2655 if($extcalendarinc === false) { 2656 return false; 2657 } 2658 $hook = $CFG->calendar .'_'.$action; 2659 if (function_exists($hook)) { 2660 call_user_func_array($hook, $args); 2661 return true; 2662 } 2663 return false; 2664 } 2665 2666 /** 2667 * Returns a calendar_event object when provided with an event id 2668 * 2669 * This function makes use of MUST_EXIST, if the event id passed in is invalid 2670 * it will result in an exception being thrown 2671 * 2672 * @param int|object $param event object or event id 2673 * @return calendar_event|false status for loading calendar_event 2674 */ 2675 public static function load($param) { 2676 global $DB; 2677 if (is_object($param)) { 2678 $event = new calendar_event($param); 2679 } else { 2680 $event = $DB->get_record('event', array('id'=>(int)$param), '*', MUST_EXIST); 2681 $event = new calendar_event($event); 2682 } 2683 return $event; 2684 } 2685 2686 /** 2687 * Creates a new event and returns a calendar_event object 2688 * 2689 * @param stdClass|array $properties An object containing event properties 2690 * @param bool $checkcapability Check caps or not 2691 * @throws coding_exception 2692 * 2693 * @return calendar_event|bool The event object or false if it failed 2694 */ 2695 public static function create($properties, $checkcapability = true) { 2696 if (is_array($properties)) { 2697 $properties = (object)$properties; 2698 } 2699 if (!is_object($properties)) { 2700 throw new coding_exception('When creating an event properties should be either an object or an assoc array'); 2701 } 2702 $event = new calendar_event($properties); 2703 if ($event->update($properties, $checkcapability)) { 2704 return $event; 2705 } else { 2706 return false; 2707 } 2708 } 2709 2710 /** 2711 * Format the text using the external API. 2712 * This function should we used when text formatting is required in external functions. 2713 * 2714 * @return array an array containing the text formatted and the text format 2715 */ 2716 public function format_external_text() { 2717 2718 if ($this->editorcontext === null) { 2719 // Switch on the event type to decide upon the appropriate context to use for this event. 2720 $this->editorcontext = $this->properties->context; 2721 2722 if ($this->properties->eventtype != 'user' && $this->properties->eventtype != 'course' 2723 && $this->properties->eventtype != 'site' && $this->properties->eventtype != 'group') { 2724 // We don't have a context here, do a normal format_text. 2725 return external_format_text($this->properties->description, $this->properties->format, $this->editorcontext->id); 2726 } 2727 } 2728 2729 // Work out the item id for the editor, if this is a repeated event then the files will be associated with the original. 2730 if (!empty($this->properties->repeatid) && $this->properties->repeatid > 0) { 2731 $itemid = $this->properties->repeatid; 2732 } else { 2733 $itemid = $this->properties->id; 2734 } 2735 2736 return external_format_text($this->properties->description, $this->properties->format, $this->editorcontext->id, 2737 'calendar', 'event_description', $itemid); 2738 } 2739 } 2740 2741 /** 2742 * Calendar information class 2743 * 2744 * This class is used simply to organise the information pertaining to a calendar 2745 * and is used primarily to make information easily available. 2746 * 2747 * @package core_calendar 2748 * @category calendar 2749 * @copyright 2010 Sam Hemelryk 2750 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 2751 */ 2752 class calendar_information { 2753 2754 /** 2755 * @var int The timestamp 2756 * 2757 * Rather than setting the day, month and year we will set a timestamp which will be able 2758 * to be used by multiple calendars. 2759 */ 2760 public $time; 2761 2762 /** @var int A course id */ 2763 public $courseid = null; 2764 2765 /** @var array An array of courses */ 2766 public $courses = array(); 2767 2768 /** @var array An array of groups */ 2769 public $groups = array(); 2770 2771 /** @var array An array of users */ 2772 public $users = array(); 2773 2774 /** 2775 * Creates a new instance 2776 * 2777 * @param int $day the number of the day 2778 * @param int $month the number of the month 2779 * @param int $year the number of the year 2780 * @param int $time the unixtimestamp representing the date we want to view, this is used instead of $calmonth 2781 * and $calyear to support multiple calendars 2782 */ 2783 public function __construct($day = 0, $month = 0, $year = 0, $time = 0) { 2784 // If a day, month and year were passed then convert it to a timestamp. If these were passed 2785 // then we can assume the day, month and year are passed as Gregorian, as no where in core 2786 // should we be passing these values rather than the time. This is done for BC. 2787 if (!empty($day) || !empty($month) || !empty($year)) { 2788 $date = usergetdate(time()); 2789 if (empty($day)) { 2790 $day = $date['mday']; 2791 } 2792 if (empty($month)) { 2793 $month = $date['mon']; 2794 } 2795 if (empty($year)) { 2796 $year = $date['year']; 2797 } 2798 if (checkdate($month, $day, $year)) { 2799 $this->time = make_timestamp($year, $month, $day); 2800 } else { 2801 $this->time = time(); 2802 } 2803 } else if (!empty($time)) { 2804 $this->time = $time; 2805 } else { 2806 $this->time = time(); 2807 } 2808 } 2809 2810 /** 2811 * Initialize calendar information 2812 * 2813 * @param stdClass $course object 2814 * @param array $coursestoload An array of courses [$course->id => $course] 2815 * @param bool $ignorefilters options to use filter 2816 */ 2817 public function prepare_for_view(stdClass $course, array $coursestoload, $ignorefilters = false) { 2818 $this->courseid = $course->id; 2819 $this->course = $course; 2820 list($courses, $group, $user) = calendar_set_filters($coursestoload, $ignorefilters); 2821 $this->courses = $courses; 2822 $this->groups = $group; 2823 $this->users = $user; 2824 } 2825 2826 /** 2827 * Ensures the date for the calendar is correct and either sets it to now 2828 * or throws a moodle_exception if not 2829 * 2830 * @param bool $defaultonow use current time 2831 * @throws moodle_exception 2832 * @return bool validation of checkdate 2833 */ 2834 public function checkdate($defaultonow = true) { 2835 if (!checkdate($this->month, $this->day, $this->year)) { 2836 if ($defaultonow) { 2837 $now = usergetdate(time()); 2838 $this->day = intval($now['mday']); 2839 $this->month = intval($now['mon']); 2840 $this->year = intval($now['year']); 2841 return true; 2842 } else { 2843 throw new moodle_exception('invaliddate'); 2844 } 2845 } 2846 return true; 2847 } 2848 2849 /** 2850 * Gets todays timestamp for the calendar 2851 * 2852 * @return int today timestamp 2853 */ 2854 public function timestamp_today() { 2855 return $this->time; 2856 } 2857 /** 2858 * Gets tomorrows timestamp for the calendar 2859 * 2860 * @return int tomorrow timestamp 2861 */ 2862 public function timestamp_tomorrow() { 2863 return strtotime('+1 day', $this->time); 2864 } 2865 /** 2866 * Adds the pretend blocks for the calendar 2867 * 2868 * @param core_calendar_renderer $renderer 2869 * @param bool $showfilters display filters, false is set as default 2870 * @param string|null $view preference view options (eg: day, month, upcoming) 2871 */ 2872 public function add_sidecalendar_blocks(core_calendar_renderer $renderer, $showfilters=false, $view=null) { 2873 if ($showfilters) { 2874 $filters = new block_contents(); 2875 $filters->content = $renderer->fake_block_filters($this->courseid, 0, 0, 0, $view, $this->courses); 2876 $filters->footer = ''; 2877 $filters->title = get_string('eventskey', 'calendar'); 2878 $renderer->add_pretend_calendar_block($filters, BLOCK_POS_RIGHT); 2879 } 2880 $block = new block_contents; 2881 $block->content = $renderer->fake_block_threemonths($this); 2882 $block->footer = ''; 2883 $block->title = get_string('monthlyview', 'calendar'); 2884 $renderer->add_pretend_calendar_block($block, BLOCK_POS_RIGHT); 2885 } 2886 } 2887 2888 /** 2889 * Returns option list for the poll interval setting. 2890 * 2891 * @return array An array of poll interval options. Interval => description. 2892 */ 2893 function calendar_get_pollinterval_choices() { 2894 return array( 2895 '0' => new lang_string('never', 'calendar'), 2896 HOURSECS => new lang_string('hourly', 'calendar'), 2897 DAYSECS => new lang_string('daily', 'calendar'), 2898 WEEKSECS => new lang_string('weekly', 'calendar'), 2899 '2628000' => new lang_string('monthly', 'calendar'), 2900 YEARSECS => new lang_string('annually', 'calendar') 2901 ); 2902 } 2903 2904 /** 2905 * Returns option list of available options for the calendar event type, given the current user and course. 2906 * 2907 * @param int $courseid The id of the course 2908 * @return array An array containing the event types the user can create. 2909 */ 2910 function calendar_get_eventtype_choices($courseid) { 2911 $choices = array(); 2912 $allowed = new stdClass; 2913 calendar_get_allowed_types($allowed, $courseid); 2914 2915 if ($allowed->user) { 2916 $choices['user'] = get_string('userevents', 'calendar'); 2917 } 2918 if ($allowed->site) { 2919 $choices['site'] = get_string('siteevents', 'calendar'); 2920 } 2921 if (!empty($allowed->courses)) { 2922 $choices['course'] = get_string('courseevents', 'calendar'); 2923 } 2924 if (!empty($allowed->groups) and is_array($allowed->groups)) { 2925 $choices['group'] = get_string('group'); 2926 } 2927 2928 return array($choices, $allowed->groups); 2929 } 2930 2931 /** 2932 * Add an iCalendar subscription to the database. 2933 * 2934 * @param stdClass $sub The subscription object (e.g. from the form) 2935 * @return int The insert ID, if any. 2936 */ 2937 function calendar_add_subscription($sub) { 2938 global $DB, $USER, $SITE; 2939 2940 if ($sub->eventtype === 'site') { 2941 $sub->courseid = $SITE->id; 2942 } else if ($sub->eventtype === 'group' || $sub->eventtype === 'course') { 2943 $sub->courseid = $sub->course; 2944 } else { 2945 // User events. 2946 $sub->courseid = 0; 2947 } 2948 $sub->userid = $USER->id; 2949 2950 // File subscriptions never update. 2951 if (empty($sub->url)) { 2952 $sub->pollinterval = 0; 2953 } 2954 2955 if (!empty($sub->name)) { 2956 if (empty($sub->id)) { 2957 $id = $DB->insert_record('event_subscriptions', $sub); 2958 // we cannot cache the data here because $sub is not complete. 2959 $sub->id = $id; 2960 // Trigger event, calendar subscription added. 2961 $eventparams = array('objectid' => $sub->id, 2962 'context' => calendar_get_calendar_context($sub), 2963 'other' => array('eventtype' => $sub->eventtype, 'courseid' => $sub->courseid) 2964 ); 2965 $event = \core\event\calendar_subscription_created::create($eventparams); 2966 $event->trigger(); 2967 return $id; 2968 } else { 2969 // Why are we doing an update here? 2970 calendar_update_subscription($sub); 2971 return $sub->id; 2972 } 2973 } else { 2974 print_error('errorbadsubscription', 'importcalendar'); 2975 } 2976 } 2977 2978 /** 2979 * Add an iCalendar event to the Moodle calendar. 2980 * 2981 * @param stdClass $event The RFC-2445 iCalendar event 2982 * @param int $courseid The course ID 2983 * @param int $subscriptionid The iCalendar subscription ID 2984 * @param string $timezone The X-WR-TIMEZONE iCalendar property if provided 2985 * @throws dml_exception A DML specific exception is thrown for invalid subscriptionids. 2986 * @return int Code: CALENDAR_IMPORT_EVENT_UPDATED = updated, CALENDAR_IMPORT_EVENT_INSERTED = inserted, 0 = error 2987 */ 2988 function calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timezone='UTC') { 2989 global $DB; 2990 2991 // Probably an unsupported X-MICROSOFT-CDO-BUSYSTATUS event. 2992 if (empty($event->properties['SUMMARY'])) { 2993 return 0; 2994 } 2995 2996 $name = $event->properties['SUMMARY'][0]->value; 2997 $name = str_replace('\n', '<br />', $name); 2998 $name = str_replace('\\', '', $name); 2999 $name = preg_replace('/\s+/u', ' ', $name); 3000 3001 $eventrecord = new stdClass; 3002 $eventrecord->name = clean_param($name, PARAM_NOTAGS); 3003 3004 if (empty($event->properties['DESCRIPTION'][0]->value)) { 3005 $description = ''; 3006 } else { 3007 $description = $event->properties['DESCRIPTION'][0]->value; 3008 $description = clean_param($description, PARAM_NOTAGS); 3009 $description = str_replace('\n', '<br />', $description); 3010 $description = str_replace('\\', '', $description); 3011 $description = preg_replace('/\s+/u', ' ', $description); 3012 } 3013 $eventrecord->description = $description; 3014 3015 // Probably a repeating event with RRULE etc. TODO: skip for now. 3016 if (empty($event->properties['DTSTART'][0]->value)) { 3017 return 0; 3018 } 3019 3020 $tz = isset($event->properties['DTSTART'][0]->parameters['TZID']) ? $event->properties['DTSTART'][0]->parameters['TZID'] : 3021 $timezone; 3022 $tz = core_date::normalise_timezone($tz); 3023 $eventrecord->timestart = strtotime($event->properties['DTSTART'][0]->value . ' ' . $tz); 3024 if (empty($event->properties['DTEND'])) { 3025 $eventrecord->timeduration = 0; // no duration if no end time specified 3026 } else { 3027 $endtz = isset($event->properties['DTEND'][0]->parameters['TZID']) ? $event->properties['DTEND'][0]->parameters['TZID'] : 3028 $timezone; 3029 $endtz = core_date::normalise_timezone($endtz); 3030 $eventrecord->timeduration = strtotime($event->properties['DTEND'][0]->value . ' ' . $endtz) - $eventrecord->timestart; 3031 } 3032 3033 // Check to see if it should be treated as an all day event. 3034 if ($eventrecord->timeduration == DAYSECS) { 3035 // Check to see if the event started at Midnight on the imported calendar. 3036 date_default_timezone_set($timezone); 3037 if (date('H:i:s', $eventrecord->timestart) === "00:00:00") { 3038 // This event should be an all day event. 3039 $eventrecord->timeduration = 0; 3040 } 3041 core_date::set_default_server_timezone(); 3042 } 3043 3044 $eventrecord->uuid = $event->properties['UID'][0]->value; 3045 $eventrecord->timemodified = time(); 3046 3047 // Add the iCal subscription details if required. 3048 // We should never do anything with an event without a subscription reference. 3049 $sub = calendar_get_subscription($subscriptionid); 3050 $eventrecord->subscriptionid = $subscriptionid; 3051 $eventrecord->userid = $sub->userid; 3052 $eventrecord->groupid = $sub->groupid; 3053 $eventrecord->courseid = $sub->courseid; 3054 $eventrecord->eventtype = $sub->eventtype; 3055 3056 if ($updaterecord = $DB->get_record('event', array('uuid' => $eventrecord->uuid))) { 3057 $eventrecord->id = $updaterecord->id; 3058 $return = CALENDAR_IMPORT_EVENT_UPDATED; // Update. 3059 } else { 3060 $return = CALENDAR_IMPORT_EVENT_INSERTED; // Insert. 3061 } 3062 if ($createdevent = calendar_event::create($eventrecord, false)) { 3063 if (!empty($event->properties['RRULE'])) { 3064 // Repeating events. 3065 date_default_timezone_set($tz); // Change time zone to parse all events. 3066 $rrule = new \core_calendar\rrule_manager($event->properties['RRULE'][0]->value); 3067 $rrule->parse_rrule(); 3068 $rrule->create_events($createdevent); 3069 core_date::set_default_server_timezone(); // Change time zone back to what it was. 3070 } 3071 return $return; 3072 } else { 3073 return 0; 3074 } 3075 } 3076 3077 /** 3078 * Update a subscription from the form data in one of the rows in the existing subscriptions table. 3079 * 3080 * @param int $subscriptionid The ID of the subscription we are acting upon. 3081 * @param int $pollinterval The poll interval to use. 3082 * @param int $action The action to be performed. One of update or remove. 3083 * @throws dml_exception if invalid subscriptionid is provided 3084 * @return string A log of the import progress, including errors 3085 */ 3086 function calendar_process_subscription_row($subscriptionid, $pollinterval, $action) { 3087 3088 // Fetch the subscription from the database making sure it exists. 3089 $sub = calendar_get_subscription($subscriptionid); 3090 3091 // Update or remove the subscription, based on action. 3092 switch ($action) { 3093 case CALENDAR_SUBSCRIPTION_UPDATE: 3094 // Skip updating file subscriptions. 3095 if (empty($sub->url)) { 3096 break; 3097 } 3098 $sub->pollinterval = $pollinterval; 3099 calendar_update_subscription($sub); 3100 3101 // Update the events. 3102 return "<p>".get_string('subscriptionupdated', 'calendar', $sub->name)."</p>" . calendar_update_subscription_events($subscriptionid); 3103 3104 case CALENDAR_SUBSCRIPTION_REMOVE: 3105 calendar_delete_subscription($subscriptionid); 3106 return get_string('subscriptionremoved', 'calendar', $sub->name); 3107 break; 3108 3109 default: 3110 break; 3111 } 3112 return ''; 3113 } 3114 3115 /** 3116 * Delete subscription and all related events. 3117 * 3118 * @param int|stdClass $subscription subscription or it's id, which needs to be deleted. 3119 */ 3120 function calendar_delete_subscription($subscription) { 3121 global $DB; 3122 3123 if (!is_object($subscription)) { 3124 $subscription = $DB->get_record('event_subscriptions', array('id' => $subscription), '*', MUST_EXIST); 3125 } 3126 // Delete subscription and related events. 3127 $DB->delete_records('event', array('subscriptionid' => $subscription->id)); 3128 $DB->delete_records('event_subscriptions', array('id' => $subscription->id)); 3129 cache_helper::invalidate_by_definition('core', 'calendar_subscriptions', array(), array($subscription->id)); 3130 3131 // Trigger event, calendar subscription deleted. 3132 $eventparams = array('objectid' => $subscription->id, 3133 'context' => calendar_get_calendar_context($subscription), 3134 'other' => array('courseid' => $subscription->courseid) 3135 ); 3136 $event = \core\event\calendar_subscription_deleted::create($eventparams); 3137 $event->trigger(); 3138 } 3139 /** 3140 * From a URL, fetch the calendar and return an iCalendar object. 3141 * 3142 * @param string $url The iCalendar URL 3143 * @return stdClass The iCalendar object 3144 */ 3145 function calendar_get_icalendar($url) { 3146 global $CFG; 3147 3148 require_once($CFG->libdir.'/filelib.php'); 3149 3150 $curl = new curl(); 3151 $curl->setopt(array('CURLOPT_FOLLOWLOCATION' => 1, 'CURLOPT_MAXREDIRS' => 5)); 3152 $calendar = $curl->get($url); 3153 // Http code validation should actually be the job of curl class. 3154 if (!$calendar || $curl->info['http_code'] != 200 || !empty($curl->errorno)) { 3155 throw new moodle_exception('errorinvalidicalurl', 'calendar'); 3156 } 3157 3158 $ical = new iCalendar(); 3159 $ical->unserialize($calendar); 3160 return $ical; 3161 } 3162 3163 /** 3164 * Import events from an iCalendar object into a course calendar. 3165 * 3166 * @param stdClass $ical The iCalendar object. 3167 * @param int $courseid The course ID for the calendar. 3168 * @param int $subscriptionid The subscription ID. 3169 * @return string A log of the import progress, including errors. 3170 */ 3171 function calendar_import_icalendar_events($ical, $courseid, $subscriptionid = null) { 3172 global $DB; 3173 $return = ''; 3174 $eventcount = 0; 3175 $updatecount = 0; 3176 3177 // Large calendars take a while... 3178 if (!CLI_SCRIPT) { 3179 core_php_time_limit::raise(300); 3180 } 3181 3182 // Mark all events in a subscription with a zero timestamp. 3183 if (!empty($subscriptionid)) { 3184 $sql = "UPDATE {event} SET timemodified = :time WHERE subscriptionid = :id"; 3185 $DB->execute($sql, array('time' => 0, 'id' => $subscriptionid)); 3186 } 3187 // Grab the timezone from the iCalendar file to be used later. 3188 if (isset($ical->properties['X-WR-TIMEZONE'][0]->value)) { 3189 $timezone = $ical->properties['X-WR-TIMEZONE'][0]->value; 3190 } else { 3191 $timezone = 'UTC'; 3192 } 3193 foreach ($ical->components['VEVENT'] as $event) { 3194 $res = calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timezone); 3195 switch ($res) { 3196 case CALENDAR_IMPORT_EVENT_UPDATED: 3197 $updatecount++; 3198 break; 3199 case CALENDAR_IMPORT_EVENT_INSERTED: 3200 $eventcount++; 3201 break; 3202 case 0: 3203 $return .= '<p>'.get_string('erroraddingevent', 'calendar').': '.(empty($event->properties['SUMMARY'])?'('.get_string('notitle', 'calendar').')':$event->properties['SUMMARY'][0]->value)." </p>\n"; 3204 break; 3205 } 3206 } 3207 $return .= "<p> ".get_string('eventsimported', 'calendar', $eventcount)."</p>"; 3208 $return .= "<p> ".get_string('eventsupdated', 'calendar', $updatecount)."</p>"; 3209 3210 // Delete remaining zero-marked events since they're not in remote calendar. 3211 if (!empty($subscriptionid)) { 3212 $deletecount = $DB->count_records('event', array('timemodified' => 0, 'subscriptionid' => $subscriptionid)); 3213 if (!empty($deletecount)) { 3214 $sql = "DELETE FROM {event} WHERE timemodified = :time AND subscriptionid = :id"; 3215 $DB->execute($sql, array('time' => 0, 'id' => $subscriptionid)); 3216 $return .= "<p> ".get_string('eventsdeleted', 'calendar').": {$deletecount} </p>\n"; 3217 } 3218 } 3219 3220 return $return; 3221 } 3222 3223 /** 3224 * Fetch a calendar subscription and update the events in the calendar. 3225 * 3226 * @param int $subscriptionid The course ID for the calendar. 3227 * @return string A log of the import progress, including errors. 3228 */ 3229 function calendar_update_subscription_events($subscriptionid) { 3230 global $DB; 3231 3232 $sub = calendar_get_subscription($subscriptionid); 3233 // Don't update a file subscription. TODO: Update from a new uploaded file. 3234 if (empty($sub->url)) { 3235 return 'File subscription not updated.'; 3236 } 3237 $ical = calendar_get_icalendar($sub->url); 3238 $return = calendar_import_icalendar_events($ical, $sub->courseid, $subscriptionid); 3239 $sub->lastupdated = time(); 3240 calendar_update_subscription($sub); 3241 return $return; 3242 } 3243 3244 /** 3245 * Update a calendar subscription. Also updates the associated cache. 3246 * 3247 * @param stdClass|array $subscription Subscription record. 3248 * @throws coding_exception If something goes wrong 3249 * @since Moodle 2.5 3250 */ 3251 function calendar_update_subscription($subscription) { 3252 global $DB; 3253 3254 if (is_array($subscription)) { 3255 $subscription = (object)$subscription; 3256 } 3257 if (empty($subscription->id) || !$DB->record_exists('event_subscriptions', array('id' => $subscription->id))) { 3258 throw new coding_exception('Cannot update a subscription without a valid id'); 3259 } 3260 3261 $DB->update_record('event_subscriptions', $subscription); 3262 // Update cache. 3263 $cache = cache::make('core', 'calendar_subscriptions'); 3264 $cache->set($subscription->id, $subscription); 3265 // Trigger event, calendar subscription updated. 3266 $eventparams = array('userid' => $subscription->userid, 3267 'objectid' => $subscription->id, 3268 'context' => calendar_get_calendar_context($subscription), 3269 'other' => array('eventtype' => $subscription->eventtype, 'courseid' => $subscription->courseid) 3270 ); 3271 $event = \core\event\calendar_subscription_updated::create($eventparams); 3272 $event->trigger(); 3273 } 3274 3275 /** 3276 * Checks to see if the user can edit a given subscription feed. 3277 * 3278 * @param mixed $subscriptionorid Subscription object or id 3279 * @return bool true if current user can edit the subscription else false 3280 */ 3281 function calendar_can_edit_subscription($subscriptionorid) { 3282 global $DB; 3283 3284 if (is_array($subscriptionorid)) { 3285 $subscription = (object)$subscriptionorid; 3286 } else if (is_object($subscriptionorid)) { 3287 $subscription = $subscriptionorid; 3288 } else { 3289 $subscription = calendar_get_subscription($subscriptionorid); 3290 } 3291 $allowed = new stdClass; 3292 $courseid = $subscription->courseid; 3293 $groupid = $subscription->groupid; 3294 calendar_get_allowed_types($allowed, $courseid); 3295 switch ($subscription->eventtype) { 3296 case 'user': 3297 return $allowed->user; 3298 case 'course': 3299 if (isset($allowed->courses[$courseid])) { 3300 return $allowed->courses[$courseid]; 3301 } else { 3302 return false; 3303 } 3304 case 'site': 3305 return $allowed->site; 3306 case 'group': 3307 if (isset($allowed->groups[$groupid])) { 3308 return $allowed->groups[$groupid]; 3309 } else { 3310 return false; 3311 } 3312 default: 3313 return false; 3314 } 3315 } 3316 3317 /** 3318 * Update calendar subscriptions. 3319 * 3320 * @return bool 3321 */ 3322 function calendar_cron() { 3323 global $CFG, $DB; 3324 3325 // In order to execute this we need bennu. 3326 require_once($CFG->libdir.'/bennu/bennu.inc.php'); 3327 3328 mtrace('Updating calendar subscriptions:'); 3329 cron_trace_time_and_memory(); 3330 3331 $time = time(); 3332 $subscriptions = $DB->get_records_sql('SELECT * FROM {event_subscriptions} WHERE pollinterval > 0 AND lastupdated + pollinterval < ?', array($time)); 3333 foreach ($subscriptions as $sub) { 3334 mtrace("Updating calendar subscription {$sub->name} in course {$sub->courseid}"); 3335 try { 3336 $log = calendar_update_subscription_events($sub->id); 3337 mtrace(trim(strip_tags($log))); 3338 } catch (moodle_exception $ex) { 3339 mtrace('Error updating calendar subscription: ' . $ex->getMessage()); 3340 } 3341 } 3342 3343 mtrace('Finished updating calendar subscriptions.'); 3344 3345 return true; 3346 } 3347 3348 /** 3349 * Helper function to determine the context of a calendar subscription. 3350 * Subscriptions can be created in two contexts COURSE, or USER. 3351 * 3352 * @param stdClass $subscription 3353 * @return context instance 3354 */ 3355 function calendar_get_calendar_context($subscription) { 3356 3357 // Determine context based on calendar type. 3358 if ($subscription->eventtype === 'site') { 3359 $context = context_course::instance(SITEID); 3360 } else if ($subscription->eventtype === 'group' || $subscription->eventtype === 'course') { 3361 $context = context_course::instance($subscription->courseid); 3362 } else { 3363 $context = context_user::instance($subscription->userid); 3364 } 3365 return $context; 3366 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |