Commit | Line | Data |
---|---|---|
7d2a0492 | 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 | /** | |
62a3f7ef | 18 | * This file contains main class for the course format Weeks |
7d2a0492 | 19 | * |
62a3f7ef MG |
20 | * @since 2.0 |
21 | * @package format_weeks | |
7d2a0492 | 22 | * @copyright 2009 Sam Hemelryk |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
24 | */ | |
25 | ||
62a3f7ef MG |
26 | /** |
27 | * Main class for the Weeks course format | |
28 | * | |
29 | * @package format_weeks | |
30 | * @copyright 2012 Marina Glancy | |
31 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
32 | */ | |
33 | class format_weeks extends format_base { | |
34 | ||
35 | /** | |
36 | * Returns true if this course format uses sections | |
37 | * | |
38 | * This function calls function callback_FORMATNAME_uses_sections() if it exists | |
39 | * | |
40 | * @return bool | |
41 | */ | |
42 | public function uses_sections() { | |
43 | global $CFG; | |
44 | // Note that lib.php in course format folder is already included by now | |
45 | $featurefunction = 'callback_'.$this->format.'_uses_sections'; | |
46 | if (function_exists($featurefunction)) { | |
47 | return $featurefunction(); | |
48 | } | |
49 | return false; | |
50 | } | |
51 | ||
52 | /** | |
53 | * Returns the display name of the given section that the course prefers. | |
54 | * | |
55 | * This function calls function callback_FORMATNAME_get_section_name() if it exists | |
56 | * | |
57 | * @param int|stdClass $section Section object from database or just field section.section | |
58 | * @return string Display name that the course format prefers, e.g. "Topic 2" | |
59 | */ | |
60 | public function get_section_name($section) { | |
61 | // Use course formatter callback if it exists | |
62 | $namingfunction = 'callback_'.$this->format.'_get_section_name'; | |
63 | if (function_exists($namingfunction) && ($course = $this->get_course())) { | |
64 | return $namingfunction($course, $this->get_section($section)); | |
65 | } | |
66 | ||
67 | // else, default behavior: | |
68 | return parent::get_section_name($section); | |
69 | } | |
70 | ||
71 | /** | |
72 | * The URL to use for the specified course (with section) | |
73 | * | |
74 | * This function calls function callback_FORMATNAME_get_section_url() if it exists | |
75 | * | |
76 | * @param int|stdClass $section Section object from database or just field course_sections.section | |
77 | * if omitted the course view page is returned | |
78 | * @param array $options options for view URL. At the moment core uses: | |
79 | * 'navigation' (bool) if true and section has no separate page, the function returns null | |
80 | * 'sr' (int) used by multipage formats to specify to which section to return | |
81 | * @return null|moodle_url | |
82 | */ | |
83 | public function get_view_url($section, $options = array()) { | |
84 | // Use course formatter callback if it exists | |
85 | $featurefunction = 'callback_'.$this->format.'_get_section_url'; | |
86 | if (function_exists($featurefunction) && ($course = $this->get_course())) { | |
87 | if (is_object($section)) { | |
88 | $sectionnum = $section->section; | |
89 | } else { | |
90 | $sectionnum = $section; | |
91 | } | |
92 | if ($sectionnum) { | |
93 | $url = $featurefunction($course, $sectionnum); | |
94 | if ($url || !empty($options['navigation'])) { | |
95 | return $url; | |
96 | } | |
97 | } | |
98 | } | |
99 | ||
100 | // if function is not defined | |
101 | if (!$this->uses_sections() || | |
102 | !array_key_exists('coursedisplay', $this->course_format_options())) { | |
103 | // default behaviour | |
104 | return parent::get_view_url($section, $options); | |
105 | } | |
106 | ||
107 | $course = $this->get_course(); | |
108 | $url = new moodle_url('/course/view.php', array('id' => $course->id)); | |
109 | ||
110 | $sr = null; | |
111 | if (array_key_exists('sr', $options)) { | |
112 | $sr = $options['sr']; | |
113 | } | |
114 | if (is_object($section)) { | |
115 | $sectionno = $section->section; | |
116 | } else { | |
117 | $sectionno = $section; | |
118 | } | |
119 | if ($sectionno !== null) { | |
120 | if ($sr !== null) { | |
121 | if ($sr) { | |
122 | $usercoursedisplay = COURSE_DISPLAY_MULTIPAGE; | |
123 | $sectionno = $sr; | |
124 | } else { | |
125 | $usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE; | |
126 | } | |
127 | } else { | |
128 | $usercoursedisplay = $course->coursedisplay; | |
129 | } | |
130 | if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) { | |
131 | $url->param('section', $sectionno); | |
132 | } else { | |
133 | if (!empty($options['navigation'])) { | |
134 | return null; | |
135 | } | |
136 | $url->set_anchor('section-'.$sectionno); | |
137 | } | |
138 | } | |
139 | return $url; | |
140 | } | |
141 | ||
142 | /** | |
143 | * Returns the information about the ajax support in the given source format | |
144 | * | |
145 | * This function calls function callback_FORMATNAME_ajax_support() if it exists | |
146 | * | |
147 | * The returned object's property (boolean)capable indicates that | |
148 | * the course format supports Moodle course ajax features. | |
149 | * The property (array)testedbrowsers can be used as a parameter for {@link ajaxenabled()}. | |
150 | * | |
151 | * @return stdClass | |
152 | */ | |
153 | public function supports_ajax() { | |
154 | // set up default values | |
155 | $ajaxsupport = parent::supports_ajax(); | |
156 | ||
157 | // get the information from the course format library | |
158 | $featurefunction = 'callback_'.$this->format.'_ajax_support'; | |
159 | if (function_exists($featurefunction)) { | |
160 | $formatsupport = $featurefunction(); | |
161 | if (isset($formatsupport->capable)) { | |
162 | $ajaxsupport->capable = $formatsupport->capable; | |
163 | } | |
164 | if (is_array($formatsupport->testedbrowsers)) { | |
165 | $ajaxsupport->testedbrowsers = $formatsupport->testedbrowsers; | |
166 | } | |
167 | } | |
168 | return $ajaxsupport; | |
169 | } | |
170 | ||
171 | /** | |
172 | * Loads all of the course sections into the navigation | |
173 | * | |
174 | * First this function calls callback_FORMATNAME_display_content() if it exists to check | |
175 | * if the navigation should be extended at all | |
176 | * | |
177 | * Then it calls function callback_FORMATNAME_load_content() if it exists to actually extend | |
178 | * navigation | |
179 | * | |
180 | * By default the parent method is called | |
181 | * | |
182 | * @param global_navigation $navigation | |
183 | * @param navigation_node $node The course node within the navigation | |
184 | */ | |
185 | public function extend_course_navigation($navigation, navigation_node $node) { | |
186 | global $PAGE; | |
187 | // if course format displays section on separate pages and we are on course/view.php page | |
188 | // and the section parameter is specified, make sure this section is expanded in | |
189 | // navigation | |
190 | if ($navigation->includesectionnum === false) { | |
191 | $selectedsection = optional_param('section', null, PARAM_INT); | |
192 | if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') && | |
193 | $PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) { | |
194 | $navigation->includesectionnum = $selectedsection; | |
195 | } | |
196 | } | |
197 | ||
198 | // check if there are callbacks to extend course navigation | |
199 | $displayfunc = 'callback_'.$this->format.'_display_content'; | |
200 | if (function_exists($displayfunc) && !$displayfunc()) { | |
201 | return; | |
202 | } | |
203 | $featurefunction = 'callback_'.$this->format.'_load_content'; | |
204 | if (function_exists($featurefunction) && ($course = $this->get_course())) { | |
205 | $featurefunction($navigation, $course, $node); | |
206 | } else { | |
207 | parent::extend_course_navigation($navigation, $node); | |
208 | } | |
209 | } | |
210 | ||
211 | /** | |
212 | * Custom action after section has been moved in AJAX mode | |
213 | * | |
214 | * Used in course/rest.php | |
215 | * | |
216 | * This function calls function callback_FORMATNAME_ajax_section_move() if it exists | |
217 | * | |
218 | * @return array This will be passed in ajax respose | |
219 | */ | |
220 | function ajax_section_move() { | |
221 | $featurefunction = 'callback_'.$this->format.'_ajax_section_move'; | |
222 | if (function_exists($featurefunction) && ($course = $this->get_course())) { | |
223 | return $featurefunction($course); | |
224 | } else { | |
225 | return parent::ajax_section_move(); | |
226 | } | |
227 | } | |
228 | ||
229 | /** | |
230 | * Returns the list of blocks to be automatically added for the newly created course | |
231 | * | |
232 | * This function checks the existence of the file config.php in the course format folder. | |
233 | * If file exists and contains the code | |
234 | * $format['defaultblocks'] = 'leftblock1,leftblock2:rightblock1,rightblock2'; | |
235 | * these blocks are used, otherwise parent function is called | |
236 | * | |
237 | * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT | |
238 | * each of values is an array of block names (for left and right side columns) | |
239 | */ | |
240 | public function get_default_blocks() { | |
241 | global $CFG; | |
242 | $formatconfig = $CFG->dirroot.'/course/format/'.$this->format.'/config.php'; | |
243 | $format = array(); // initialize array in external file | |
244 | if (is_readable($formatconfig)) { | |
245 | include($formatconfig); | |
246 | } | |
247 | if (!empty($format['defaultblocks'])) { | |
248 | return blocks_parse_default_blocks_list($format['defaultblocks']); | |
249 | } | |
250 | return parent::get_default_blocks(); | |
251 | } | |
252 | ||
253 | /** | |
254 | * Definitions of the additional options that this course format uses for course | |
255 | * | |
256 | * By default course formats have the options that existed in Moodle 2.3: | |
257 | * - coursedisplay | |
258 | * - numsections | |
259 | * - hiddensections | |
260 | * | |
261 | * @param bool $foreditform | |
262 | * @return array of options | |
263 | */ | |
264 | public function course_format_options($foreditform = false) { | |
265 | static $courseformatoptions = false; | |
266 | if ($courseformatoptions === false) { | |
267 | $courseconfig = get_config('moodlecourse'); | |
268 | $courseformatoptions = array( | |
269 | 'numsections' => array( | |
270 | 'default' => $courseconfig->numsections, | |
271 | 'type' => PARAM_INT, | |
272 | ), | |
273 | 'hiddensections' => array( | |
274 | 'default' => $courseconfig->hiddensections, | |
275 | 'type' => PARAM_INT, | |
276 | ), | |
277 | 'coursedisplay' => array( | |
278 | 'default' => $courseconfig->coursedisplay, | |
279 | 'type' => PARAM_INT, | |
280 | ), | |
281 | ); | |
282 | } | |
283 | if ($foreditform && !isset($courseformatoptions['coursedisplay']['label'])) { | |
284 | $courseconfig = get_config('moodlecourse'); | |
285 | $sectionmenu = array(); | |
286 | for ($i = 0; $i <= $courseconfig->maxsections; $i++) { | |
287 | $sectionmenu[$i] = "$i"; | |
288 | } | |
289 | $courseformatoptionsedit = array( | |
290 | 'numsections' => array( | |
291 | 'label' => new lang_string('numberweeks'), | |
292 | 'element_type' => 'select', | |
293 | 'element_attributes' => array($sectionmenu), | |
294 | ), | |
295 | 'hiddensections' => array( | |
296 | 'label' => new lang_string('hiddensections'), | |
297 | 'help' => 'hiddensections', | |
298 | 'help_component' => 'moodle', | |
299 | 'element_type' => 'select', | |
300 | 'element_attributes' => array( | |
301 | array( | |
302 | 0 => new lang_string('hiddensectionscollapsed'), | |
303 | 1 => new lang_string('hiddensectionsinvisible') | |
304 | ) | |
305 | ), | |
306 | ), | |
307 | 'coursedisplay' => array( | |
308 | 'label' => new lang_string('coursedisplay'), | |
309 | 'element_type' => 'select', | |
310 | 'element_attributes' => array( | |
311 | array( | |
312 | COURSE_DISPLAY_SINGLEPAGE => new lang_string('coursedisplay_single'), | |
313 | COURSE_DISPLAY_MULTIPAGE => new lang_string('coursedisplay_multi') | |
314 | ) | |
315 | ), | |
316 | 'help' => 'coursedisplay', | |
317 | 'help_component' => 'moodle', | |
318 | ) | |
319 | ); | |
320 | $courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit); | |
321 | } | |
322 | return $courseformatoptions; | |
323 | } | |
324 | ||
325 | /** | |
326 | * Updates format options for a course | |
327 | * | |
328 | * Legacy course formats may assume that course format options | |
329 | * ('coursedisplay', 'numsections' and 'hiddensections') are shared between formats. | |
330 | * Therefore we make sure to copy them from the previous format | |
331 | * | |
332 | * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data | |
333 | * @param stdClass $oldcourse if this function is called from {@link update_course()} | |
334 | * this object contains information about the course before update | |
335 | * @return bool whether there were any changes to the options values | |
336 | */ | |
337 | public function update_course_format_options($data, $oldcourse = null) { | |
338 | if ($oldcourse !== null) { | |
339 | $data = (array)$data; | |
340 | $oldcourse = (array)$oldcourse; | |
341 | $options = $this->course_format_options(); | |
342 | foreach ($options as $key => $unused) { | |
343 | if (!array_key_exists($key, $data)) { | |
344 | if (array_key_exists($key, $oldcourse)) { | |
345 | $data[$key] = $oldcourse[$key]; | |
346 | } else if ($key === 'numsections') { | |
347 | // If previous format does not have the field 'numsections' and this one does, | |
348 | // and $data['numsections'] is not set fill it with the maximum section number from the DB | |
349 | $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections} | |
350 | WHERE course = ?', array($this->courseid)); | |
351 | if ($maxsection) { | |
352 | // If there are no sections, or just default 0-section, 'numsections' will be set to default | |
353 | $data['numsections'] = $maxsection; | |
354 | } | |
355 | } | |
356 | } | |
357 | } | |
358 | } | |
359 | return $this->update_format_options($data); | |
360 | } | |
361 | } | |
7487c856 SH |
362 | |
363 | /** | |
364 | * Indicates this format uses sections. | |
365 | * | |
366 | * @return bool Returns true | |
367 | */ | |
368 | function callback_weeks_uses_sections() { | |
369 | return true; | |
370 | } | |
371 | ||
7d2a0492 | 372 | /** |
373 | * Used to display the course structure for a course where format=weeks | |
374 | * | |
375 | * This is called automatically by {@link load_course()} if the current course | |
376 | * format = weeks. | |
377 | * | |
47c96a77 | 378 | * @param navigation_node $navigation The course node |
379 | * @param array $path An array of keys to the course node | |
380 | * @param stdClass $course The course we are loading the section for | |
7d2a0492 | 381 | */ |
3406acde | 382 | function callback_weeks_load_content(&$navigation, $course, $coursenode) { |
0f4ab67d | 383 | return $navigation->load_generic_course_sections($course, $coursenode, 'weeks'); |
7d2a0492 | 384 | } |
385 | ||
386 | /** | |
387 | * The string that is used to describe a section of the course | |
388 | * e.g. Topic, Week... | |
389 | * | |
390 | * @return string | |
391 | */ | |
392 | function callback_weeks_definition() { | |
393 | return get_string('week'); | |
394 | } | |
395 | ||
7487c856 SH |
396 | /** |
397 | * Gets the name for the provided section. | |
398 | * | |
399 | * @param stdClass $course | |
400 | * @param stdClass $section | |
401 | * @return string | |
402 | */ | |
403 | function callback_weeks_get_section_name($course, $section) { | |
0f4ab67d | 404 | // We can't add a node without text |
bb410a1e | 405 | if ((string)$section->name !== '') { |
b6283a49 DP |
406 | // Return the name the user set. |
407 | return format_string($section->name, true, array('context' => context_course::instance($course->id))); | |
0f4ab67d | 408 | } else if ($section->section == 0) { |
b6283a49 | 409 | // Return the general section. |
0f4ab67d SH |
410 | return get_string('section0name', 'format_weeks'); |
411 | } else { | |
b6283a49 DP |
412 | $dates = format_weeks_get_section_dates($section, $course); |
413 | ||
414 | // We subtract 24 hours for display purposes. | |
415 | $dates->end = ($dates->end - 86400); | |
416 | ||
417 | $dateformat = ' '.get_string('strftimedateshort'); | |
418 | $weekday = userdate($dates->start, $dateformat); | |
419 | $endweekday = userdate($dates->end, $dateformat); | |
0f4ab67d SH |
420 | return $weekday.' - '.$endweekday; |
421 | } | |
422 | } | |
c0b5d925 DM |
423 | |
424 | /** | |
425 | * Declares support for course AJAX features | |
426 | * | |
427 | * @see course_format_ajax_support() | |
428 | * @return stdClass | |
429 | */ | |
430 | function callback_weeks_ajax_support() { | |
431 | $ajaxsupport = new stdClass(); | |
432 | $ajaxsupport->capable = true; | |
433 | $ajaxsupport->testedbrowsers = array('MSIE' => 6.0, 'Gecko' => 20061111, 'Safari' => 531, 'Chrome' => 6.0); | |
434 | return $ajaxsupport; | |
435 | } | |
b6283a49 DP |
436 | |
437 | /** | |
438 | * Return the start and end date of the passed section | |
439 | * | |
440 | * @param stdClass $section The course_section entry from the DB | |
441 | * @param stdClass $course The course entry from DB | |
442 | * @return stdClass property start for startdate, property end for enddate | |
443 | */ | |
444 | function format_weeks_get_section_dates($section, $course) { | |
445 | $oneweekseconds = 604800; | |
446 | // Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight | |
447 | // savings and the date changes. | |
448 | $startdate = $course->startdate + 7200; | |
449 | ||
450 | $dates = new stdClass(); | |
451 | $dates->start = $startdate + ($oneweekseconds * ($section->section - 1)); | |
452 | $dates->end = $dates->start + $oneweekseconds; | |
453 | ||
454 | return $dates; | |
455 | } | |
786cd60e | 456 | |
9f3015ec RK |
457 | /** |
458 | * Callback function to do some action after section move | |
459 | * | |
460 | * @param stdClass $course The course entry from DB | |
461 | * @return array This will be passed in ajax respose. | |
462 | */ | |
463 | function callback_weeks_ajax_section_move($course) { | |
464 | global $COURSE, $PAGE; | |
465 | ||
466 | $titles = array(); | |
467 | rebuild_course_cache($course->id); | |
468 | $modinfo = get_fast_modinfo($COURSE); | |
469 | $renderer = $PAGE->get_renderer('format_weeks'); | |
470 | if ($renderer && ($sections = $modinfo->get_section_info_all())) { | |
471 | foreach ($sections as $number => $section) { | |
472 | $titles[$number] = $renderer->section_title($section, $course); | |
473 | } | |
474 | } | |
475 | return array('sectiontitles' => $titles, 'action' => 'move'); | |
476 | } |