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
11 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
15 function page_import_types($path) {
18 static $types = array();
20 if(substr($path, -1) != '/') {
24 $path = clean_param($path, PARAM_PATH
);
26 if(isset($types[$path])) {
30 $file = $CFG->dirroot
.'/'.$path.'pagelib.php';
34 if(!isset($DEFINEDPAGES)) {
35 error('Imported '.$file.' but found no page classes');
37 return $types[$path] = $DEFINEDPAGES;
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);
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) {
62 $data->pagetype
= $type;
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);
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) {
87 static $mappings = NULL;
89 if ($mappings === NULL) {
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
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.
120 * The string identifier for the type of page being described.
126 * The numeric identifier of the page being described.
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
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() {
159 function construct() {
160 page_id_and_class($this->body_id
, $this->body_class
);
163 // USER-RELATED THINGS
165 // By default, no user is editing anything and none CAN edit anything. Developers
166 // will have to override these settings to let Moodle know when it should grant
167 // editing rights to the user viewing the page.
168 function user_allowed_editing() {
169 trigger_error('Page class does not implement method <strong>user_allowed_editing()</strong>', E_USER_WARNING
);
172 function user_is_editing() {
173 trigger_error('Page class does not implement method <strong>user_is_editing()</strong>', E_USER_WARNING
);
177 // HTML OUTPUT SECTION
179 // We have absolutely no idea what derived pages are all about
180 function print_header($title, $morenavlinks=NULL) {
181 trigger_error('Page class does not implement method <strong>print_header()</strong>', E_USER_WARNING
);
185 // BLOCKS RELATED SECTION
187 // By default, pages don't have any blocks. Override this in your derived class if you need blocks.
188 function blocks_get_positions() {
192 // Thus there is no default block position. If you override the above you should override this one too.
193 // Because this makes sense only if blocks_get_positions() is overridden and because these two should
194 // be overridden as a group or not at all, this one issues a warning. The sneaky part is that this warning
195 // will only be seen if you override blocks_get_positions() but NOT blocks_default_position().
196 function blocks_default_position() {
197 trigger_error('Page class does not implement method <strong>blocks_default_position()</strong>', E_USER_WARNING
);
201 // If you don't override this, newly constructed pages of this kind won't have any blocks.
202 function blocks_get_default() {
206 // If you don't override this, your blocks will not be able to change positions
207 function blocks_move_position(&$instance, $move) {
208 return $instance->position
;
211 // SELF-REPORTING SECTION
213 // Derived classes HAVE to define their "home url"
214 function url_get_path() {
215 trigger_error('Page class does not implement method <strong>url_get_path()</strong>', E_USER_WARNING
);
219 // It's not always required to pass any arguments to the home url, so this doesn't trigger any errors (sensible default)
220 function url_get_parameters() {
224 // This should actually NEVER be overridden unless you have GOOD reason. Works fine as it is.
225 function url_get_full($extraparams = array()) {
226 $path = $this->url_get_path();
231 $params = $this->url_get_parameters();
232 if (!empty($params)) {
233 $params = array_merge($params, $extraparams);
235 $params = $extraparams;
244 foreach($params as $var => $value) {
245 $path .= $first?
'?' : '&';
246 $path .= $var .'='. urlencode($value);
253 // This forces implementers to actually hardwire their page identification constant in the class.
254 // Good thing, if you ask me. That way we can later auto-detect "installed" page types by querying
255 // the classes themselves in the future.
256 function get_type() {
257 trigger_error('Page class does not implement method <strong>get_type()</strong>', E_USER_ERROR
);
261 // Simple stuff, do not override this.
266 // "Sensible default" case here. Take it from the body id.
267 function get_format_name() {
268 return $this->body_id
;
271 // Returns $this->body_class
272 function get_body_class() {
273 return $this->body_class
;
276 // Returns $this->body_id
277 function get_body_id() {
278 return $this->body_id
;
281 // Initialize the data members of the parent class
282 function init_quick($data) {
283 $this->type
= $data->pagetype
;
284 $this->id
= $data->pageid
;
287 function init_full() {
288 $this->full_init_done
= true;
292 // is this page always editable, regardless of anything else?
293 function edit_always() {
294 return (has_capability('moodle/site:manageblocks', get_context_instance(CONTEXT_SYSTEM
)) && defined('ADMIN_STICKYBLOCKS'));
300 * Class that models the behavior of a moodle course
302 * @author Jon Papaioannou
306 class page_course
extends page_base
{
308 // Any data we might need to store specifically about ourself should be declared here.
309 // After init_full() is called for the first time, ALL of these variables should be
310 // initialized correctly and ready for use.
311 var $courserecord = NULL;
313 // Do any validation of the officially recognized bits of the data and forward to parent.
314 // Do NOT load up "expensive" resouces (e.g. SQL data) here!
315 function init_quick($data) {
316 if(empty($data->pageid
) && !defined('ADMIN_STICKYBLOCKS')) {
317 error('Cannot quickly initialize page: empty course id');
319 parent
::init_quick($data);
322 // Here you should load up all heavy-duty data for your page. Basically everything that
323 // does not NEED to be loaded for the class to make basic decisions should NOT be loaded
324 // in init_quick() and instead deferred here. Of course this function had better recognize
325 // $this->full_init_done to prevent wasteful multiple-time data retrieval.
326 function init_full() {
328 if($this->full_init_done
) {
331 if (empty($this->id
)) {
332 $this->id
= 0; // avoid db errors
334 if ($this->id
== $COURSE->id
) {
335 $this->courserecord
= $COURSE;
337 $this->courserecord
= get_record('course', 'id', $this->id
);
340 if(empty($this->courserecord
) && !defined('ADMIN_STICKYBLOCKS')) {
341 error('Cannot fully initialize page: invalid course id '. $this->id
);
344 $this->context
= get_context_instance(CONTEXT_COURSE
, $this->id
);
346 // Preload - ensures that the context cache is populated
347 // in one DB query...
348 $this->childcontexts
= get_child_contexts($this->context
);
351 $this->full_init_done
= true;
354 // USER-RELATED THINGS
356 // Can user edit the course page or "sticky page"?
357 // This is also about editting of blocks BUT mainly activities in course page layout, see
358 // update_course_icon() has very similar checks - it must use the same capabilities
360 // this is a _very_ expensive check - so cache it during execution
362 function user_allowed_editing() {
366 if (isset($this->_user_allowed_editing
)) {
367 return $this->_user_allowed_editing
;
370 if (has_capability('moodle/site:manageblocks', get_context_instance(CONTEXT_SYSTEM
))
371 && defined('ADMIN_STICKYBLOCKS')) {
372 $this->_user_allowed_editing
= true;
375 if (has_capability('moodle/course:manageactivities', $this->context
)) {
376 $this->_user_allowed_editing
= true;
380 // Exhaustive (and expensive!) checks to see if the user
381 // has editing abilities to a specific module/block/group...
382 // This code would benefit from the ability to check specifically
384 foreach ($this->childcontexts
as $cc) {
385 if (($cc->contextlevel
== CONTEXT_MODULE
&&
386 has_capability('moodle/course:manageactivities', $cc)) ||
387 ($cc->contextlevel
== CONTEXT_BLOCK
&&
388 has_capability('moodle/site:manageblocks', $cc))) {
389 $this->_user_allowed_editing
= true;
395 // Is the user actually editing this course page or "sticky page" right now?
396 function user_is_editing() {
397 if (has_capability('moodle/site:manageblocks', get_context_instance(CONTEXT_SYSTEM
)) && defined('ADMIN_STICKYBLOCKS')) {
398 //always in edit mode on sticky page
401 return isediting($this->id
);
404 // HTML OUTPUT SECTION
406 // This function prints out the common part of the page's header.
407 // You should NEVER print the header "by hand" in other code.
408 function print_header($title, $morenavlinks=NULL, $meta='', $bodytags='', $extrabuttons='') {
412 $replacements = array(
413 '%fullname%' => $this->courserecord
->fullname
415 foreach($replacements as $search => $replace) {
416 $title = str_replace($search, $replace, $title);
421 if(!empty($morenavlinks)) {
422 $navlinks = array_merge($navlinks, $morenavlinks);
425 $navigation = build_navigation($navlinks);
427 // The "Editing On" button will be appearing only in the "main" course screen
428 // (i.e., no breadcrumbs other than the default one added inside this function)
429 $buttons = switchroles_form($this->courserecord
->id
);
430 if ($this->user_allowed_editing()) {
431 $buttons .= update_course_icon($this->courserecord
->id
);
433 $buttons = empty($morenavlinks) ?
$buttons : ' ';
435 // Add any extra buttons requested (by the resource module, for example)
436 if ($extrabuttons != '') {
437 $buttons = ($buttons == ' ') ?
$extrabuttons : $buttons.$extrabuttons;
440 print_header($title, $this->courserecord
->fullname
, $navigation,
441 '', $meta, true, $buttons, user_login_string($this->courserecord
, $USER), false, $bodytags);
444 // SELF-REPORTING SECTION
446 // This is hardwired here so the factory function page_create_object() can be sure there was no mistake.
447 // Also, it doubles as a way to let others inquire about our type.
448 function get_type() {
449 return PAGE_COURSE_VIEW
;
452 // This is like the "category" of a page of this "type". For example, if the type is PAGE_COURSE_VIEW
453 // the format_name is the actual name of the course format. If the type were PAGE_ACTIVITY_VIEW, then
454 // the format_name might be that activity's name etc.
455 function get_format_name() {
457 if (defined('ADMIN_STICKYBLOCKS')) {
458 return PAGE_COURSE_VIEW
;
460 if($this->id
== SITEID
) {
461 return parent
::get_format_name();
463 // This needs to reflect the path hierarchy under Moodle root.
464 return 'course-view-'.$this->courserecord
->format
;
467 // This should return a fully qualified path to the URL which is responsible for displaying us.
468 function url_get_path() {
470 if (defined('ADMIN_STICKYBLOCKS')) {
471 return $CFG->wwwroot
.'/'.$CFG->admin
.'/stickyblocks.php';
473 if($this->id
== SITEID
) {
474 return $CFG->wwwroot
.'/index.php';
477 return $CFG->wwwroot
.'/course/view.php';
481 // This should return an associative array of any GET/POST parameters that are needed by the URL
482 // which displays us to make it work. If none are needed, return an empty array.
483 function url_get_parameters() {
484 if (defined('ADMIN_STICKYBLOCKS')) {
485 return array('pt' => ADMIN_STICKYBLOCKS
);
487 if($this->id
== SITEID
) {
491 return array('id' => $this->id
);
495 // BLOCKS RELATED SECTION
497 // Which are the positions in this page which support blocks? Return an array containing their identifiers.
498 // BE CAREFUL, ORDER DOES MATTER! In textual representations, lists of blocks in a page use the ':' character
499 // to delimit different positions in the page. The part before the first ':' in such a representation will map
500 // directly to the first item of the array you return here, the second to the next one and so on. This way,
501 // you can add more positions in the future without interfering with legacy textual representations.
502 function blocks_get_positions() {
503 return array(BLOCK_POS_LEFT
, BLOCK_POS_RIGHT
);
506 // When a new block is created in this page, which position should it go to?
507 function blocks_default_position() {
508 return BLOCK_POS_RIGHT
;
511 // When we are creating a new page, use the data at your disposal to provide a textual representation of the
512 // blocks that are going to get added to this new page. Delimit block names with commas (,) and use double
513 // colons (:) to delimit between block positions in the page. See blocks_get_positions() for additional info.
514 function blocks_get_default() {
519 if($this->id
== SITEID
) {
521 if (!empty($CFG->defaultblocks_site
)) {
522 $blocknames = $CFG->defaultblocks_site
;
524 /// Failsafe - in case nothing was defined.
526 $blocknames = 'site_main_menu,admin_tree:course_summary,calendar_month';
529 // It's a normal course, so do it according to the course format
531 $pageformat = $this->courserecord
->format
;
532 if (!empty($CFG->{'defaultblocks_'. $pageformat})) {
533 $blocknames = $CFG->{'defaultblocks_'. $pageformat};
536 $format_config = $CFG->dirroot
.'/course/format/'.$pageformat.'/config.php';
537 if (@is_file
($format_config) && is_readable($format_config)) {
538 require($format_config);
540 if (!empty($format['defaultblocks'])) {
541 $blocknames = $format['defaultblocks'];
543 else if (!empty($CFG->defaultblocks
)){
544 $blocknames = $CFG->defaultblocks
;
546 /// Failsafe - in case nothing was defined.
548 $blocknames = 'participants,activity_modules,search_forums,admin,course_list:news_items,calendar_upcoming,recent_activity';
556 // Given an instance of a block in this page and the direction in which we want to move it, where is
557 // it going to go? Return the identifier of the instance's new position. This allows us to tell blocklib
558 // how we want the blocks to move around in this page in an arbitrarily complex way. If the move as given
559 // does not make sense, make sure to return the instance's original position.
561 // Since this is going to get called a LOT, pass the instance by reference purely for speed. Do **NOT**
562 // modify its data in any way, this will actually confuse blocklib!!!
563 function blocks_move_position(&$instance, $move) {
564 if($instance->position
== BLOCK_POS_LEFT
&& $move == BLOCK_MOVE_RIGHT
) {
565 return BLOCK_POS_RIGHT
;
566 } else if ($instance->position
== BLOCK_POS_RIGHT
&& $move == BLOCK_MOVE_LEFT
) {
567 return BLOCK_POS_LEFT
;
569 return $instance->position
;
574 * Class that models the common parts of all activity modules
576 * @author Jon Papaioannou
580 class page_generic_activity
extends page_base
{
581 var $activityname = NULL;
582 var $courserecord = NULL;
583 var $modulerecord = NULL;
584 var $activityrecord = NULL;
586 function init_full() {
587 if($this->full_init_done
) {
590 if(empty($this->activityname
)) {
591 error('Page object derived from page_generic_activity but did not define $this->activityname');
593 if (!$this->modulerecord
= get_coursemodule_from_instance($this->activityname
, $this->id
)) {
594 error('Cannot fully initialize page: invalid '.$this->activityname
.' instance id '. $this->id
);
596 $this->courserecord
= get_record('course', 'id', $this->modulerecord
->course
);
597 if(empty($this->courserecord
)) {
598 error('Cannot fully initialize page: invalid course id '. $this->modulerecord
->course
);
600 $this->activityrecord
= get_record($this->activityname
, 'id', $this->id
);
601 if(empty($this->activityrecord
)) {
602 error('Cannot fully initialize page: invalid '.$this->activityname
.' id '. $this->id
);
604 $this->full_init_done
= true;
607 function user_allowed_editing() {
609 // Yu: I think this is wrong, should be checking manageactivities instead
610 //return has_capability('moodle/site:manageblocks', get_context_instance(CONTEXT_COURSE, $this->modulerecord->course));
611 return has_capability('moodle/course:manageactivities', get_context_instance(CONTEXT_MODULE
, $this->modulerecord
->id
));
614 function user_is_editing() {
616 return isediting($this->modulerecord
->course
);
619 function url_get_path() {
621 return $CFG->wwwroot
.'/mod/'.$this->activityname
.'/view.php';
624 function url_get_parameters() {
626 return array('id' => $this->modulerecord
->id
);
629 function blocks_get_positions() {
630 return array(BLOCK_POS_LEFT
);
633 function blocks_default_position() {
634 return BLOCK_POS_LEFT
;
637 function print_header($title, $morenavlinks = NULL, $bodytags = '', $meta = '') {
641 $replacements = array(
642 '%fullname%' => format_string($this->activityrecord
->name
)
644 foreach ($replacements as $search => $replace) {
645 $title = str_replace($search, $replace, $title);
648 if (empty($morenavlinks) && $this->user_allowed_editing()) {
649 $buttons = '<table><tr><td>'.update_module_button($this->modulerecord
->id
, $this->courserecord
->id
, get_string('modulename', $this->activityname
)).'</td>';
650 if (!empty($CFG->showblocksonmodpages
)) {
651 $buttons .= '<td><form '.$CFG->frametarget
.' method="get" action="view.php"><div>'.
652 '<input type="hidden" name="id" value="'.$this->modulerecord
->id
.'" />'.
653 '<input type="hidden" name="edit" value="'.($this->user_is_editing()?
'off':'on').'" />'.
654 '<input type="submit" value="'.get_string($this->user_is_editing()?
'blockseditoff':'blocksediton').'" /></div></form></td>';
656 $buttons .= '</tr></table>';
661 if (empty($morenavlinks)) {
662 $morenavlinks = array();
664 $navigation = build_navigation($morenavlinks, $this->modulerecord
);
665 print_header($title, $this->courserecord
->fullname
, $navigation, '', $meta, true, $buttons, navmenu($this->courserecord
, $this->modulerecord
), false, $bodytags);