prefix.'block_instance SET pagetype = \'course-view\' WHERE pagetype = \'course\'', false); } // End of dirty compatibility hack -- remove this before 1.5 goes gold /// Constants /** * Definition of course page type. */ define('PAGE_COURSE_VIEW', 'course-view'); define('PAGE_QUIZ_VIEW', 'mod-quiz-view'); /** * Factory function page_create_object(). Called with a pagetype identifier and possibly with * its numeric ID. Returns a fully constructed page_base subclass you can work with. */ function page_create_object($type, $id = NULL) { global $CFG; $data = new stdClass; $data->pagetype = $type; $data->pageid = $id; $classname = page_map_class($type); $object = &new $classname; // TODO: subclassing check here if ($object->get_type() !== $type) { // Somehow somewhere someone made a mistake if ($CFG->debug > 7) { error('Page object\'s type ('. $object->get_type() .') does not match requested type ('. $type .')'); } } $object->init_quick($data); return $object; } /** * Function page_map_class() is the way for your code to define its own page subclasses and let Moodle recognize them. * Use it to associate the textual identifier of your Page with the actual class name that has to be instantiated. */ function page_map_class($type, $classname = NULL) { global $CFG; static $mappings = NULL; if ($mappings === NULL) { $mappings = array( PAGE_COURSE_VIEW => 'page_course', PAGE_QUIZ_VIEW => 'page_quiz', ); } if (!empty($type) && !empty($classname)) { $mappings[$type] = $classname; } if (!isset($mappings[$type])) { if ($CFG->debug > 7) { error('Page class mapping requested for unknown type: '.$type); } } if (!class_exists($mappings[$type])) { if ($CFG->debug > 7) { error('Page class mapping for id "'.$type.'" exists but class "'.$mappings[$type].'" is not defined'); } } return $mappings[$type]; } /** * Parent class from which all Moodle page classes derive * * @author Jon Papaioannou * @package pages * @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. */ class page_base { /** * The string identifier for the type of page being described. * @var string $type */ var $type = NULL; /** * The numeric identifier of the page being described. * @var int $id */ var $id = NULL; /** * Class bool to determine if the instance's full initialization has been completed. * @var boolean $full_init_done */ var $full_init_done = false; /** * The class attribute that Moodle has to assign to the BODY tag for this page. * @var string $body_class */ var $body_class = NULL; /** * The id attribute that Moodle has to assign to the BODY tag for this page. * @var string $body_id */ var $body_id = NULL; /// Class Functions // CONSTRUCTION // A whole battery of functions to allow standardized-name constructors in all versions of PHP. // The constructor is actually called construct() function page_base() { $this->construct(); } function __construct() { $this->construct(); } function construct() { global $CFG, $ME; $path = substr($ME, strlen($CFG->wwwroot) + 1); $path = str_replace('.php', '', $path); if (substr($path, -1) == '/') { $path .= 'index'; } if (empty($path)) { $this->body_id = 'index'; $this->body_class = 'course-view'; } else { $this->body_id = str_replace('/', '-', $path); $classarray = explode('-', $this->body_id); array_pop($classarray); $this->body_class = implode('-', $classarray); } } // USER-RELATED THINGS // By default, no user is editing anything and none CAN edit anything. Developers // will have to override these settings to let Moodle know when it should grant // editing rights to the user viewing the page. function user_allowed_editing() { trigger_error('Page class does not implement method user_allowed_editing()', E_USER_WARNING); return false; } function user_is_editing() { trigger_error('Page class does not implement method user_is_editing()', E_USER_WARNING); return false; } // HTML OUTPUT SECTION // We have absolutely no idea what derived pages are all about function print_header($title, $morebreadcrumbs) { trigger_error('Page class does not implement method print_header()', E_USER_WARNING); return; } // Returns $this->body_class function body_class() { return $this->body_class; } // Returns $this->body_id function body_id() { return $this->body_id; } // BLOCKS RELATED SECTION // By default, pages don't have any blocks. Override this in your derived class if you need blocks. function blocks_get_positions() { return array(); } // Thus there is no default block position. If you override the above you should override this one too. // Because this makes sense only if blocks_get_positions() is overridden and because these two should // be overridden as a group or not at all, this one issues a warning. The sneaky part is that this warning // will only be seen if you override blocks_get_positions() but NOT blocks_default_position(). function blocks_default_position() { trigger_error('Page class does not implement method blocks_default_position()', E_USER_WARNING); return NULL; } // If you don't override this, newly constructed pages of this kind won't have any blocks. function blocks_get_default() { return ''; } // If you don't override this, your blocks will not be able to change positions function blocks_move_position(&$instance, $move) { return $instance->position; } // SELF-REPORTING SECTION // Derived classes HAVE to define their "home url" function url_get_path() { trigger_error('Page class does not implement method url_get_path()', E_USER_WARNING); return NULL; } // It's not always required to pass any arguments to the home url, so this doesn't trigger any errors (sensible default) function url_get_parameters() { return array(); } // This should actually NEVER be overridden unless you have GOOD reason. Works fine as it is. function url_get_full($extraparams = array()) { $path = $this->url_get_path(); if(empty($path)) { return NULL; } $params = $this->url_get_parameters(); $params = array_merge($params, $extraparams); if(empty($params)) { return $path; } $first = true; foreach($params as $var => $value) { $path .= $first? '?' : '&'; $path .= $var .'='. urlencode($value); $first = false; } return $path; } // This forces implementers to actually hardwire their page identification constant in the class. // Good thing, if you ask me. That way we can later auto-detect "installed" page types by querying // the classes themselves in the future. function get_type() { trigger_error('Page class does not implement method get_type()', E_USER_ERROR); return NULL; } // Simple stuff, do not override this. function get_id() { return $this->id; } // "Sensible default" case here. Don't trigger any error. function get_format_name() { return NULL; } // Initialize the data members of the parent class function init_quick($data) { $this->type = $data->pagetype; $this->id = $data->pageid; } function init_full() { $this->full_init_done = true; } } /** * Class that models the behavior of a moodle course * * @author Jon Papaioannou * @package pages */ class page_course extends page_base { // Any data we might need to store specifically about ourself should be declared here. // After init_full() is called for the first time, ALL of these variables should be // initialized correctly and ready for use. var $courserecord = NULL; // Do any validation of the officially recognized bits of the data and forward to parent. // Do NOT load up "expensive" resouces (e.g. SQL data) here! function init_quick($data) { if(empty($data->pageid)) { error('Cannot quickly initialize page: empty course id'); } parent::init_quick($data); } // Here you should load up all heavy-duty data for your page. Basically everything that // does not NEED to be loaded for the class to make basic decisions should NOT be loaded // in init_quick() and instead deferred here. Of course this function had better recognize // $this->full_init_done to prevent wasteful multiple-time data retrieval. function init_full() { if($this->full_init_done) { return; } $this->courserecord = get_record('course', 'id', $this->id); if(empty($this->courserecord)) { error('Cannot fully initialize page: invalid course id '. $this->id); } $this->full_init_done = true; } // USER-RELATED THINGS // When is a user said to have "editing rights" in this page? This would have something // to do with roles, in the future. function user_allowed_editing() { return isteacheredit($this->id); } // Is the user actually editing this page right now? This would have something // to do with roles, in the future. function user_is_editing() { return isediting($this->id); } // HTML OUTPUT SECTION // This function prints out the common part of the page's header. // You should NEVER print the header "by hand" in other code. function print_header($title, $morebreadcrumbs = NULL) { global $USER, $CFG; $this->init_full(); $replacements = array( '%fullname%' => $this->courserecord->fullname ); foreach($replacements as $search => $replace) { $title = str_replace($search, $replace, $title); } if($this->courserecord->id == SITEID) { $breadcrumbs = array(); } else { $breadcrumbs = array($this->courserecord->shortname => $CFG->wwwroot.'/course/view.php?id='.$this->courserecord->id); } if(!empty($morebreadcrumbs)) { $breadcrumbs = array_merge($breadcrumbs, $morebreadcrumbs); } $total = count($breadcrumbs); $current = 1; $crumbtext = ''; foreach($breadcrumbs as $text => $href) { if($current++ == $total) { $crumbtext .= ' '.$text; } else { $crumbtext .= ' '.$text.' ->'; } } // The "Editing On" button will be appearing only in the "main" course screen // (i.e., no breadcrumbs other than the default one added inside this function) $button = empty($morebreadcrumbs) ? update_course_icon($this->courserecord->id) : ' '; $loggedinas = '

'. user_login_string($this->courserecord, $USER) .'

'; print_header($title, $this->courserecord->fullname, $crumbtext, '', '', true, $button, $loggedinas); } // SELF-REPORTING SECTION // This is hardwired here so the factory function page_create_object() can be sure there was no mistake. // Also, it doubles as a way to let others inquire about our type. function get_type() { return PAGE_COURSE_VIEW; } // This is like the "category" of a page of this "type". For example, if the type is PAGE_COURSE_VIEW // the format_name is the actual name of the course format. If the type were PAGE_ACTIVITY_VIEW, then // the format_name might be that activity's name etc. function get_format_name() { $this->init_full(); return $this->courserecord->format; } // This should return a fully qualified path to the URL which is responsible for displaying us. function url_get_path() { global $CFG; if($this->id == SITEID) { return $CFG->wwwroot .'/index.php'; } else { return $CFG->wwwroot .'/course/view.php'; } } // This should return an associative array of any GET/POST parameters that are needed by the URL // which displays us to make it work. If none are needed, return an empty array. function url_get_parameters() { if($this->id == SITEID) { return array(); } else { return array('id' => $this->id); } } // BLOCKS RELATED SECTION // Which are the positions in this page which support blocks? Return an array containing their identifiers. // BE CAREFUL, ORDER DOES MATTER! In textual representations, lists of blocks in a page use the ':' character // to delimit different positions in the page. The part before the first ':' in such a representation will map // directly to the first item of the array you return here, the second to the next one and so on. This way, // you can add more positions in the future without interfering with legacy textual representations. function blocks_get_positions() { return array(BLOCK_POS_LEFT, BLOCK_POS_RIGHT); } // When a new block is created in this page, which position should it go to? function blocks_default_position() { return BLOCK_POS_RIGHT; } // When we are creating a new page, use the data at your disposal to provide a textual representation of the // blocks that are going to get added to this new page. Delimit block names with commas (,) and use double // colons (:) to delimit between block positions in the page. See blocks_get_positions() for additional info. function blocks_get_default() { global $CFG; $this->init_full(); if($this->id == SITEID) { // Is it the site? if (!empty($CFG->defaultblocks_site)) { $blocknames = $CFG->defaultblocks_site; } /// Failsafe - in case nothing was defined. else { $blocknames = 'site_main_menu,admin,course_list:course_summary,calendar_month'; } } // It's a normal course, so do it according to the course format else { $pageformat = $this->courserecord->format; if (!empty($CFG->{'defaultblocks_'. $pageformat})) { $blocknames = $CFG->{'defaultblocks_'. $pageformat}; } else { $format_config = $CFG->dirroot.'/course/format/'.$pageformat.'/config.php'; if (@is_file($format_config) && is_readable($format_config)) { require($format_config); } if (!empty($format['defaultblocks'])) { $blocknames = $format['defaultblocks']; } else if (!empty($CFG->defaultblocks)){ $blocknames = $CFG->defaultblocks; } /// Failsafe - in case nothing was defined. else { $blocknames = 'participants,activity_modules,search_forums,admin,course_list:news_items,calendar_upcoming,recent_activity'; } } } return $blocknames; } // Given an instance of a block in this page and the direction in which we want to move it, where is // it going to go? Return the identifier of the instance's new position. This allows us to tell blocklib // how we want the blocks to move around in this page in an arbitrarily complex way. If the move as given // does not make sense, make sure to return the instance's original position. // // Since this is going to get called a LOT, pass the instance by reference purely for speed. Do **NOT** // modify its data in any way, this will actually confuse blocklib!!! function blocks_move_position(&$instance, $move) { if($instance->position == BLOCK_POS_LEFT && $move == BLOCK_MOVE_RIGHT) { return BLOCK_POS_RIGHT; } else if ($instance->position == BLOCK_POS_RIGHT && $move == BLOCK_MOVE_LEFT) { return BLOCK_POS_LEFT; } return $instance->position; } } /** * Class that models the behavior of a moodle mod * * @author Jon Papaioannou * @package pages */ class page_quiz extends page_base { // Any data we might need to store specifically about ourself should be declared here. // After init_full() is called for the first time, ALL of these variables should be // initialized correctly and ready for use. var $courserecord = NULL; var $modulerecord = NULL; var $quizrecord = NULL; // Do any validation of the officially recognized bits of the data and forward to parent. // Do NOT load up "expensive" resouces (e.g. SQL data) here! function init_quick($data) { if(empty($data->pageid)) { error('Cannot quickly initialize page: empty course id'); } parent::init_quick($data); } // Here you should load up all heavy-duty data for your page. Basically everything that // does not NEED to be loaded for the class to make basic decisions should NOT be loaded // in init_quick() and instead deferred here. Of course this function had better recognize // $this->full_init_done to prevent wasteful multiple-time data retrieval. function init_full() { if($this->full_init_done) { return; } $module = get_record('modules', 'name', 'quiz'); $this->modulerecord = get_record('course_modules', 'module', $module->id, 'instance', $this->id); if(empty($this->modulerecord)) { error('Cannot fully initialize page: invalid quiz instance id '. $this->id); } $this->courserecord = get_record('course', 'id', $this->modulerecord->course); if(empty($this->courserecord)) { error('Cannot fully initialize page: invalid course id '. $this->modulerecord->course); } $this->quizrecord = get_record('quiz', 'id', $this->id); if(empty($this->courserecord)) { error('Cannot fully initialize page: invalid quiz id '. $this->id); } $this->full_init_done = true; } // USER-RELATED THINGS // When is a user said to have "editing rights" in this page? This would have something // to do with roles, in the future. function user_allowed_editing() { return isteacheredit($this->modulerecord->course); } // Is the user actually editing this page right now? This would have something // to do with roles, in the future. function user_is_editing() { return isediting($this->modulerecord->course); } // HTML OUTPUT SECTION // This function prints out the common part of the page's header. // You should NEVER print the header "by hand" in other code. function print_header($title, $morebreadcrumbs = NULL) { global $USER, $CFG; $this->init_full(); $replacements = array( '%fullname%' => $this->quizrecord->name ); foreach($replacements as $search => $replace) { $title = str_replace($search, $replace, $title); } $breadcrumbs = array( $this->courserecord->shortname => $CFG->wwwroot.'/course/view.php?id='.$this->courserecord->id, get_string('modulenameplural', 'quiz') => $CFG->wwwroot.'/mod/quiz/index.php?id='.$this->courserecord->id, $this->quizrecord->name => $CFG->wwwroot.'/mod/quiz/view.php?id='.$this->modulerecord->id, ); if(!empty($morebreadcrumbs)) { $breadcrumbs = array_merge($breadcrumbs, $morebreadcrumbs); } $total = count($breadcrumbs); $current = 1; $crumbtext = ''; foreach($breadcrumbs as $text => $href) { if($current++ == $total) { $crumbtext .= ' '.$text; } else { $crumbtext .= ' '.$text.' ->'; } } // The "Editing On" button will be appearing only in the "main" course screen // (i.e., no breadcrumbs other than the default one added inside this function) $button = empty($morebreadcrumbs) ? update_course_icon($this->courserecord->id) : ' '; $loggedinas = '

'. user_login_string($this->courserecord, $USER) .'

'; print_header($title, $this->courserecord->fullname, $crumbtext, '', '', true, $button, $loggedinas); } // SELF-REPORTING SECTION // This is hardwired here so the factory function page_create_object() can be sure there was no mistake. // Also, it doubles as a way to let others inquire about our type. function get_type() { return PAGE_QUIZ_VIEW; } // This is like the "category" of a page of this "type". For example, if the type is PAGE_COURSE_VIEW // the format_name is the actual name of the course format. If the type were PAGE_ACTIVITY_VIEW, then // the format_name might be that activity's name etc. function get_format_name() { return 'quiz'; } // This should return a fully qualified path to the URL which is responsible for displaying us. function url_get_path() { global $CFG; return $CFG->wwwroot .'/mod/quiz/view.php'; } // This should return an associative array of any GET/POST parameters that are needed by the URL // which displays us to make it work. If none are needed, return an empty array. function url_get_parameters() { $this->init_full(); return array('id' => $this->modulerecord->id); } // BLOCKS RELATED SECTION // Which are the positions in this page which support blocks? Return an array containing their identifiers. // BE CAREFUL, ORDER DOES MATTER! In textual representations, lists of blocks in a page use the ':' character // to delimit different positions in the page. The part before the first ':' in such a representation will map // directly to the first item of the array you return here, the second to the next one and so on. This way, // you can add more positions in the future without interfering with legacy textual representations. function blocks_get_positions() { return array(BLOCK_POS_LEFT); } // When a new block is created in this page, which position should it go to? function blocks_default_position() { return BLOCK_POS_LEFT; } // When we are creating a new page, use the data at your disposal to provide a textual representation of the // blocks that are going to get added to this new page. Delimit block names with commas (,) and use double // colons (:) to delimit between block positions in the page. See blocks_get_positions() for additional info. function blocks_get_default() { global $CFG; return ''; } // Given an instance of a block in this page and the direction in which we want to move it, where is // it going to go? Return the identifier of the instance's new position. This allows us to tell blocklib // how we want the blocks to move around in this page in an arbitrarily complex way. If the move as given // does not make sense, make sure to return the instance's original position. // // Since this is going to get called a LOT, pass the instance by reference purely for speed. Do **NOT** // modify its data in any way, this will actually confuse blocklib!!! function blocks_move_position(&$instance, $move) { return $instance->position; } } ?>