calendar/lib: calendar_set_filters() use pre-fetched context and course recs
[moodle-pu.git] / lib / pagelib.php
blob5dc5a4d5449c718d320fe8ea5fccf90c8cbeb739
1 <?php //$Id$
3 /**
4 * This file contains the parent class for moodle pages, page_base,
5 * as well as the page_course subclass.
6 * A page is defined by its page type (ie. course, blog, activity) and its page id
7 * (courseid, blogid, activity id, etc).
9 * @author Jon Papaioannou
10 * @version $Id$
11 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
12 * @package pages
15 function page_import_types($path) {
16 global $CFG;
18 static $types = array();
20 if(substr($path, -1) != '/') {
21 $path .= '/';
24 $path = clean_param($path, PARAM_PATH);
26 if(isset($types[$path])) {
27 return $types[$path];
30 $file = $CFG->dirroot.'/'.$path.'pagelib.php';
32 if(is_file($file)) {
33 require($file);
34 if(!isset($DEFINEDPAGES)) {
35 error('Imported '.$file.' but found no page classes');
37 return $types[$path] = $DEFINEDPAGES;
40 return false;
43 /**
44 * Factory function page_create_object(). Called with a numeric ID for a page, it autodetects
45 * the page type, constructs the correct object and returns it.
48 function page_create_instance($instance) {
49 page_id_and_class($id, $class);
50 return page_create_object($id, $instance);
53 /**
54 * Factory function page_create_object(). Called with a pagetype identifier and possibly with
55 * its numeric ID. Returns a fully constructed page_base subclass you can work with.
58 function page_create_object($type, $id = NULL) {
59 global $CFG;
61 $data = new stdClass;
62 $data->pagetype = $type;
63 $data->pageid = $id;
65 $classname = page_map_class($type);
67 $object = &new $classname;
68 // TODO: subclassing check here
70 if ($object->get_type() !== $type) {
71 // Somehow somewhere someone made a mistake
72 debugging('Page object\'s type ('. $object->get_type() .') does not match requested type ('. $type .')');
75 $object->init_quick($data);
76 return $object;
79 /**
80 * Function page_map_class() is the way for your code to define its own page subclasses and let Moodle recognize them.
81 * Use it to associate the textual identifier of your Page with the actual class name that has to be instantiated.
84 function page_map_class($type, $classname = NULL) {
85 global $CFG;
87 static $mappings = NULL;
89 if ($mappings === NULL) {
90 $mappings = array(
91 PAGE_COURSE_VIEW => 'page_course'
95 if (!empty($type) && !empty($classname)) {
96 $mappings[$type] = $classname;
99 if (!isset($mappings[$type])) {
100 debugging('Page class mapping requested for unknown type: '.$type);
103 if (empty($classname) && !class_exists($mappings[$type])) {
104 debugging('Page class mapping for id "'.$type.'" exists but class "'.$mappings[$type].'" is not defined');
107 return $mappings[$type];
111 * Parent class from which all Moodle page classes derive
113 * @author Jon Papaioannou
114 * @package pages
115 * @todo This parent class is very messy still. Please for the moment ignore it and move on to the derived class page_course to see the comments there.
118 class page_base {
120 * The string identifier for the type of page being described.
121 * @var string $type
123 var $type = NULL;
126 * The numeric identifier of the page being described.
127 * @var int $id
129 var $id = NULL;
132 * Class bool to determine if the instance's full initialization has been completed.
133 * @var boolean $full_init_done
135 var $full_init_done = false;
138 * The class attribute that Moodle has to assign to the BODY tag for this page.
139 * @var string $body_class
141 var $body_class = NULL;
144 * The id attribute that Moodle has to assign to the BODY tag for this page.
145 * @var string $body_id
147 var $body_id = NULL;
149 /// Class Functions
151 // CONSTRUCTION
153 // A whole battery of functions to allow standardized-name constructors in all versions of PHP.
154 // The constructor is actually called construct()
155 function page_base() {
156 $this->construct();
159 function __construct() {
160 $this->construct();
163 function construct() {
164 page_id_and_class($this->body_id, $this->body_class);
167 // USER-RELATED THINGS
169 // By default, no user is editing anything and none CAN edit anything. Developers
170 // will have to override these settings to let Moodle know when it should grant
171 // editing rights to the user viewing the page.
172 function user_allowed_editing() {
173 trigger_error('Page class does not implement method <strong>user_allowed_editing()</strong>', E_USER_WARNING);
174 return false;
176 function user_is_editing() {
177 trigger_error('Page class does not implement method <strong>user_is_editing()</strong>', E_USER_WARNING);
178 return false;
181 // HTML OUTPUT SECTION
183 // We have absolutely no idea what derived pages are all about
184 function print_header($title, $morenavlinks) {
185 trigger_error('Page class does not implement method <strong>print_header()</strong>', E_USER_WARNING);
186 return;
189 // BLOCKS RELATED SECTION
191 // By default, pages don't have any blocks. Override this in your derived class if you need blocks.
192 function blocks_get_positions() {
193 return array();
196 // Thus there is no default block position. If you override the above you should override this one too.
197 // Because this makes sense only if blocks_get_positions() is overridden and because these two should
198 // be overridden as a group or not at all, this one issues a warning. The sneaky part is that this warning
199 // will only be seen if you override blocks_get_positions() but NOT blocks_default_position().
200 function blocks_default_position() {
201 trigger_error('Page class does not implement method <strong>blocks_default_position()</strong>', E_USER_WARNING);
202 return NULL;
205 // If you don't override this, newly constructed pages of this kind won't have any blocks.
206 function blocks_get_default() {
207 return '';
210 // If you don't override this, your blocks will not be able to change positions
211 function blocks_move_position(&$instance, $move) {
212 return $instance->position;
215 // SELF-REPORTING SECTION
217 // Derived classes HAVE to define their "home url"
218 function url_get_path() {
219 trigger_error('Page class does not implement method <strong>url_get_path()</strong>', E_USER_WARNING);
220 return NULL;
223 // It's not always required to pass any arguments to the home url, so this doesn't trigger any errors (sensible default)
224 function url_get_parameters() {
225 return array();
228 // This should actually NEVER be overridden unless you have GOOD reason. Works fine as it is.
229 function url_get_full($extraparams = array()) {
230 $path = $this->url_get_path();
231 if(empty($path)) {
232 return NULL;
235 $params = $this->url_get_parameters();
236 if (!empty($params)) {
237 $params = array_merge($params, $extraparams);
238 } else {
239 $params = $extraparams;
242 if(empty($params)) {
243 return $path;
246 $first = true;
248 foreach($params as $var => $value) {
249 $path .= $first? '?' : '&amp;';
250 $path .= $var .'='. urlencode($value);
251 $first = false;
254 return $path;
257 // This forces implementers to actually hardwire their page identification constant in the class.
258 // Good thing, if you ask me. That way we can later auto-detect "installed" page types by querying
259 // the classes themselves in the future.
260 function get_type() {
261 trigger_error('Page class does not implement method <strong>get_type()</strong>', E_USER_ERROR);
262 return NULL;
265 // Simple stuff, do not override this.
266 function get_id() {
267 return $this->id;
270 // "Sensible default" case here. Take it from the body id.
271 function get_format_name() {
272 return $this->body_id;
275 // Returns $this->body_class
276 function get_body_class() {
277 return $this->body_class;
280 // Returns $this->body_id
281 function get_body_id() {
282 return $this->body_id;
285 // Initialize the data members of the parent class
286 function init_quick($data) {
287 $this->type = $data->pagetype;
288 $this->id = $data->pageid;
291 function init_full() {
292 $this->full_init_done = true;
296 // is this page always editable, regardless of anything else?
297 function edit_always() {
298 return (has_capability('moodle/site:manageblocks', get_context_instance(CONTEXT_SYSTEM)) && defined('ADMIN_STICKYBLOCKS'));
304 * Class that models the behavior of a moodle course
306 * @author Jon Papaioannou
307 * @package pages
310 class page_course extends page_base {
312 // Any data we might need to store specifically about ourself should be declared here.
313 // After init_full() is called for the first time, ALL of these variables should be
314 // initialized correctly and ready for use.
315 var $courserecord = NULL;
317 // Do any validation of the officially recognized bits of the data and forward to parent.
318 // Do NOT load up "expensive" resouces (e.g. SQL data) here!
319 function init_quick($data) {
320 if(empty($data->pageid) && !defined('ADMIN_STICKYBLOCKS')) {
321 error('Cannot quickly initialize page: empty course id');
323 parent::init_quick($data);
326 // Here you should load up all heavy-duty data for your page. Basically everything that
327 // does not NEED to be loaded for the class to make basic decisions should NOT be loaded
328 // in init_quick() and instead deferred here. Of course this function had better recognize
329 // $this->full_init_done to prevent wasteful multiple-time data retrieval.
330 function init_full() {
331 global $COURSE;
332 if($this->full_init_done) {
333 return;
335 if (empty($this->id)) {
336 $this->id = 0; // avoid db errors
338 if ($this->id == $COURSE->id) {
339 $this->courserecord = $COURSE;
340 } else {
341 $this->courserecord = get_record('course', 'id', $this->id);
344 if(empty($this->courserecord) && !defined('ADMIN_STICKYBLOCKS')) {
345 error('Cannot fully initialize page: invalid course id '. $this->id);
348 $this->context = get_context_instance(CONTEXT_COURSE, $this->id);
350 // Preload - ensures that the context cache is populated
351 // in one DB query...
352 $this->childcontexts = get_child_contexts($this->context);
354 // Mark we're done
355 $this->full_init_done = true;
358 // USER-RELATED THINGS
360 // Can user edit the course page or "sticky page"?
361 // This is also about editting of blocks BUT mainly activities in course page layout, see
362 // update_course_icon() has very similar checks - it must use the same capabilities
364 // this is a _very_ expensive check - so cache it during execution
366 function user_allowed_editing() {
368 $this->init_full();
370 if (isset($this->_user_allowed_editing)) {
371 return $this->_user_allowed_editing;
374 if (has_capability('moodle/site:manageblocks', get_context_instance(CONTEXT_SYSTEM))
375 && defined('ADMIN_STICKYBLOCKS')) {
376 $this->_user_allowed_editing = true;
377 return true;
379 if (has_capability('moodle/course:manageactivities', $this->context)) {
380 $this->_user_allowed_editing = true;
381 return true;
384 // Exhaustive (and expensive!) checks to see if the user
385 // has editing abilities to a specific module/block/group...
386 // This code would benefit from the ability to check specifically
387 // for overrides.
388 foreach ($this->childcontexts as $cc) {
389 if (($cc->contextlevel == CONTEXT_MODULE &&
390 has_capability('moodle/course:manageactivities', $cc)) ||
391 ($cc->contextlevel == CONTEXT_BLOCK &&
392 has_capability('moodle/site:manageblocks', $cc))) {
393 $this->_user_allowed_editing = true;
394 return true;
399 // Is the user actually editing this course page or "sticky page" right now?
400 function user_is_editing() {
401 if (has_capability('moodle/site:manageblocks', get_context_instance(CONTEXT_SYSTEM)) && defined('ADMIN_STICKYBLOCKS')) {
402 //always in edit mode on sticky page
403 return true;
405 return isediting($this->id);
408 // HTML OUTPUT SECTION
410 // This function prints out the common part of the page's header.
411 // You should NEVER print the header "by hand" in other code.
412 function print_header($title, $morenavlinks=NULL, $meta='', $bodytags='') {
413 global $USER, $CFG;
415 $this->init_full();
416 $replacements = array(
417 '%fullname%' => $this->courserecord->fullname
419 foreach($replacements as $search => $replace) {
420 $title = str_replace($search, $replace, $title);
423 $navlinks = array();
425 if(!empty($morenavlinks)) {
426 $navlinks = array_merge($navlinks, $morenavlinks);
429 $navigation = build_navigation($navlinks);
431 // The "Editing On" button will be appearing only in the "main" course screen
432 // (i.e., no breadcrumbs other than the default one added inside this function)
433 $buttons = switchroles_form($this->courserecord->id);
434 if ($this->user_allowed_editing()) {
435 $buttons .= update_course_icon($this->courserecord->id );
437 $buttons = empty($morenavlinks) ? $buttons : '&nbsp;';
439 print_header($title, $this->courserecord->fullname, $navigation,
440 '', $meta, true, $buttons, user_login_string($this->courserecord, $USER), false, $bodytags);
442 echo '<div class="accesshide"><a href="#startofcontent">'.get_string('skiptomaincontent').'</a></div>';
445 // SELF-REPORTING SECTION
447 // This is hardwired here so the factory function page_create_object() can be sure there was no mistake.
448 // Also, it doubles as a way to let others inquire about our type.
449 function get_type() {
450 return PAGE_COURSE_VIEW;
453 // This is like the "category" of a page of this "type". For example, if the type is PAGE_COURSE_VIEW
454 // the format_name is the actual name of the course format. If the type were PAGE_ACTIVITY_VIEW, then
455 // the format_name might be that activity's name etc.
456 function get_format_name() {
457 $this->init_full();
458 if (defined('ADMIN_STICKYBLOCKS')) {
459 return PAGE_COURSE_VIEW;
461 if($this->id == SITEID) {
462 return parent::get_format_name();
464 // This needs to reflect the path hierarchy under Moodle root.
465 return 'course-view-'.$this->courserecord->format;
468 // This should return a fully qualified path to the URL which is responsible for displaying us.
469 function url_get_path() {
470 global $CFG;
471 if (defined('ADMIN_STICKYBLOCKS')) {
472 return $CFG->wwwroot.'/'.$CFG->admin.'/stickyblocks.php';
474 if($this->id == SITEID) {
475 return $CFG->wwwroot .'/index.php';
477 else {
478 return $CFG->wwwroot .'/course/view.php';
482 // This should return an associative array of any GET/POST parameters that are needed by the URL
483 // which displays us to make it work. If none are needed, return an empty array.
484 function url_get_parameters() {
485 if (defined('ADMIN_STICKYBLOCKS')) {
486 return array('pt' => ADMIN_STICKYBLOCKS);
488 if($this->id == SITEID) {
489 return array();
491 else {
492 return array('id' => $this->id);
496 // BLOCKS RELATED SECTION
498 // Which are the positions in this page which support blocks? Return an array containing their identifiers.
499 // BE CAREFUL, ORDER DOES MATTER! In textual representations, lists of blocks in a page use the ':' character
500 // to delimit different positions in the page. The part before the first ':' in such a representation will map
501 // directly to the first item of the array you return here, the second to the next one and so on. This way,
502 // you can add more positions in the future without interfering with legacy textual representations.
503 function blocks_get_positions() {
504 return array(BLOCK_POS_LEFT, BLOCK_POS_RIGHT);
507 // When a new block is created in this page, which position should it go to?
508 function blocks_default_position() {
509 return BLOCK_POS_RIGHT;
512 // When we are creating a new page, use the data at your disposal to provide a textual representation of the
513 // blocks that are going to get added to this new page. Delimit block names with commas (,) and use double
514 // colons (:) to delimit between block positions in the page. See blocks_get_positions() for additional info.
515 function blocks_get_default() {
516 global $CFG;
518 $this->init_full();
520 if($this->id == SITEID) {
521 // Is it the site?
522 if (!empty($CFG->defaultblocks_site)) {
523 $blocknames = $CFG->defaultblocks_site;
525 /// Failsafe - in case nothing was defined.
526 else {
527 $blocknames = 'site_main_menu,admin_tree:course_summary,calendar_month';
530 // It's a normal course, so do it according to the course format
531 else {
532 $pageformat = $this->courserecord->format;
533 if (!empty($CFG->{'defaultblocks_'. $pageformat})) {
534 $blocknames = $CFG->{'defaultblocks_'. $pageformat};
536 else {
537 $format_config = $CFG->dirroot.'/course/format/'.$pageformat.'/config.php';
538 if (@is_file($format_config) && is_readable($format_config)) {
539 require($format_config);
541 if (!empty($format['defaultblocks'])) {
542 $blocknames = $format['defaultblocks'];
544 else if (!empty($CFG->defaultblocks)){
545 $blocknames = $CFG->defaultblocks;
547 /// Failsafe - in case nothing was defined.
548 else {
549 $blocknames = 'participants,activity_modules,search_forums,admin,course_list:news_items,calendar_upcoming,recent_activity';
554 return $blocknames;
557 // Given an instance of a block in this page and the direction in which we want to move it, where is
558 // it going to go? Return the identifier of the instance's new position. This allows us to tell blocklib
559 // how we want the blocks to move around in this page in an arbitrarily complex way. If the move as given
560 // does not make sense, make sure to return the instance's original position.
562 // Since this is going to get called a LOT, pass the instance by reference purely for speed. Do **NOT**
563 // modify its data in any way, this will actually confuse blocklib!!!
564 function blocks_move_position(&$instance, $move) {
565 if($instance->position == BLOCK_POS_LEFT && $move == BLOCK_MOVE_RIGHT) {
566 return BLOCK_POS_RIGHT;
567 } else if ($instance->position == BLOCK_POS_RIGHT && $move == BLOCK_MOVE_LEFT) {
568 return BLOCK_POS_LEFT;
570 return $instance->position;
575 * Class that models the common parts of all activity modules
577 * @author Jon Papaioannou
578 * @package pages
581 class page_generic_activity extends page_base {
582 var $activityname = NULL;
583 var $courserecord = NULL;
584 var $modulerecord = NULL;
585 var $activityrecord = NULL;
587 function init_full() {
588 if($this->full_init_done) {
589 return;
591 if(empty($this->activityname)) {
592 error('Page object derived from page_generic_activity but did not define $this->activityname');
594 $module = get_record('modules', 'name', $this->activityname);
595 $this->modulerecord = get_record('course_modules', 'module', $module->id, 'instance', $this->id);
596 if(empty($this->modulerecord)) {
597 error('Cannot fully initialize page: invalid '.$this->activityname.' instance id '. $this->id);
599 $this->courserecord = get_record('course', 'id', $this->modulerecord->course);
600 if(empty($this->courserecord)) {
601 error('Cannot fully initialize page: invalid course id '. $this->modulerecord->course);
603 $this->activityrecord = get_record($this->activityname, 'id', $this->id);
604 if(empty($this->courserecord)) {
605 error('Cannot fully initialize page: invalid '.$this->activityname.' id '. $this->id);
607 $this->full_init_done = true;
610 function user_allowed_editing() {
611 $this->init_full();
612 // Yu: I think this is wrong, should be checking manageactivities instead
613 //return has_capability('moodle/site:manageblocks', get_context_instance(CONTEXT_COURSE, $this->modulerecord->course));
614 return has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE, $this->modulerecord->id));
617 function user_is_editing() {
618 $this->init_full();
619 return isediting($this->modulerecord->course);
622 function url_get_path() {
623 global $CFG;
624 return $CFG->wwwroot .'/mod/'.$this->activityname.'/view.php';
627 function url_get_parameters() {
628 $this->init_full();
629 return array('id' => $this->modulerecord->id);
632 function blocks_get_positions() {
633 return array(BLOCK_POS_LEFT);
636 function blocks_default_position() {
637 return BLOCK_POS_LEFT;
640 function print_header($title, $morenavlinks = NULL, $bodytags = '', $meta = '') {
641 global $USER, $CFG;
643 $this->init_full();
644 $replacements = array(
645 '%fullname%' => format_string($this->activityrecord->name)
647 foreach ($replacements as $search => $replace) {
648 $title = str_replace($search, $replace, $title);
651 $navlinks = array();
652 $navlinks[] = array('name' => get_string('modulenameplural', $this->activityname), 'link' => $CFG->wwwroot."/mod/{$this->activityname}/index.php?id={$this->courserecord->id}", 'type' => 'activity');
653 $navlinks[] = array('name' => format_string($this->activityrecord->name), 'link' => $CFG->wwwroot."/mod/{$this->activityname}/view.php?id={$this->modulerecord->id}", 'type' => 'activityinstance');
655 if (!empty($morenavlinks)) {
656 $navlinks = array_merge($navlinks, $morenavlinks);
659 if (empty($morenavlinks) && $this->user_allowed_editing()) {
660 $buttons = '<table><tr><td>'.update_module_button($this->modulerecord->id, $this->courserecord->id, get_string('modulename', $this->activityname)).'</td>';
661 if (!empty($CFG->showblocksonmodpages)) {
662 $buttons .= '<td><form target="'.$CFG->framename.'" method="get" action="view.php">'.
663 '<input type="hidden" name="id" value="'.$this->modulerecord->id.'" />'.
664 '<input type="hidden" name="edit" value="'.($this->user_is_editing()?'off':'on').'" />'.
665 '<input type="submit" value="'.get_string($this->user_is_editing()?'blockseditoff':'blocksediton').'" /></form></td>';
667 $buttons .= '</tr></table>';
668 } else {
669 $buttons = '&nbsp;';
672 $navigation = build_navigation($navlinks);
674 print_header($title, $this->courserecord->fullname, $navigation, '', $meta, true, $buttons, navmenu($this->courserecord, $this->modulerecord), false, $bodytags);