e686f6aa6d5ca0719ee48ce3ab000462a735220e
[moodle.git] / lib / testing / generator / module_generator.php
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/>.
17 /**
18  * Module generator base class.
19  *
20  * @package    core
21  * @category   test
22  * @copyright  2012 Petr Skoda {@link http://skodak.org}
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 /**
29  * Module generator base class.
30  *
31  * Extend in mod/xxxx/tests/generator/lib.php as class mod_xxxx_generator.
32  *
33  * @package    core
34  * @category   test
35  * @copyright  2012 Petr Skoda {@link http://skodak.org}
36  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37  */
38 abstract class testing_module_generator extends component_generator_base {
40     /**
41      * @var number of created instances
42      */
43     protected $instancecount = 0;
45     /**
46      * To be called from data reset code only,
47      * do not use in tests.
48      * @return void
49      */
50     public function reset() {
51         $this->instancecount = 0;
52     }
54     /**
55      * Returns module name
56      * @return string name of module that this class describes
57      * @throws coding_exception if class invalid
58      */
59     public function get_modulename() {
60         $matches = null;
61         if (!preg_match('/^mod_([a-z0-9]+)_generator$/', get_class($this), $matches)) {
62             throw new coding_exception('Invalid module generator class name: '.get_class($this));
63         }
65         if (empty($matches[1])) {
66             throw new coding_exception('Invalid module generator class name: '.get_class($this));
67         }
68         return $matches[1];
69     }
71     /**
72      * Create course module and link it to course
73      *
74      * Since 2.6 it is recommended to use function add_moduleinfo() to create a module.
75      *
76      * @deprecated since 2.6
77      * @see testing_module_generator::create_instance()
78      *
79      * @param integer $courseid
80      * @param array $options section, visible
81      * @return integer $cm instance id
82      */
83     protected function precreate_course_module($courseid, array $options) {
84         global $DB, $CFG;
85         require_once("$CFG->dirroot/course/lib.php");
87         $modulename = $this->get_modulename();
88         $sectionnum = isset($options['section']) ? $options['section'] : 0;
89         unset($options['section']); // Prevent confusion, it would be overridden later in course_add_cm_to_section() anyway.
91         $cm = new stdClass();
92         $cm->course             = $courseid;
93         $cm->module             = $DB->get_field('modules', 'id', array('name'=>$modulename));
94         $cm->instance           = 0;
95         $cm->section            = 0;
96         $cm->idnumber           = isset($options['idnumber']) ? $options['idnumber'] : 0;
97         $cm->added              = time();
99         $columns = $DB->get_columns('course_modules');
100         foreach ($options as $key => $value) {
101             if ($key === 'id' or !isset($columns[$key])) {
102                 continue;
103             }
104             if (property_exists($cm, $key)) {
105                 continue;
106             }
107             $cm->$key = $value;
108         }
110         $cm->id = $DB->insert_record('course_modules', $cm);
112         course_add_cm_to_section($courseid, $cm->id, $sectionnum);
114         return $cm->id;
115     }
117     /**
118      * Called after *_add_instance()
119      *
120      * Since 2.6 it is recommended to use function add_moduleinfo() to create a module.
121      *
122      * @deprecated since 2.6
123      * @see testing_module_generator::create_instance()
124      *
125      * @param int $id
126      * @param int $cmid
127      * @return stdClass module instance
128      */
129     protected function post_add_instance($id, $cmid) {
130         global $DB;
132         $DB->set_field('course_modules', 'instance', $id, array('id'=>$cmid));
134         $instance = $DB->get_record($this->get_modulename(), array('id'=>$id), '*', MUST_EXIST);
136         $cm = get_coursemodule_from_id($this->get_modulename(), $cmid, $instance->course, true, MUST_EXIST);
137         context_module::instance($cm->id);
139         $instance->cmid = $cm->id;
141         return $instance;
142     }
144     /**
145      * Merges together arguments $record and $options and fills default module
146      * fields that are shared by all module types
147      *
148      * @param object|array $record fields (different from defaults) for this module
149      * @param null|array $options for backward-compatibility this may include fields from course_modules
150      *     table. They are merged into $record
151      * @throws coding_exception if $record->course is not specified
152      */
153     protected function prepare_moduleinfo_record($record, $options) {
154         global $DB;
155         // Make sure we don't modify the original object.
156         $moduleinfo = (object)(array)$record;
158         if (empty($moduleinfo->course)) {
159             throw new coding_exception('module generator requires $record->course');
160         }
162         $moduleinfo->modulename = $this->get_modulename();
163         $moduleinfo->module = $DB->get_field('modules', 'id', array('name' => $moduleinfo->modulename));
165         // Allow idnumber to be set as either $options['idnumber'] or $moduleinfo->cmidnumber or $moduleinfo->idnumber.
166         // The actual field name is 'idnumber' but add_moduleinfo() expects 'cmidnumber'.
167         if (isset($options['idnumber'])) {
168             $moduleinfo->cmidnumber = $options['idnumber'];
169         } else if (!isset($moduleinfo->cmidnumber) && isset($moduleinfo->idnumber)) {
170             $moduleinfo->cmidnumber = $moduleinfo->idnumber;
171         }
173         // These are the fields from table 'course_modules' in 2.6 when the second
174         // argument $options is being deprecated.
175         // List excludes fields: instance (does not exist yet), course, module and idnumber (set above)
176         $easymergefields = array('section', 'added', 'score', 'indent',
177             'visible', 'visibleold', 'groupmode', 'groupingid',
178             'completion', 'completiongradeitemnumber', 'completionview', 'completionexpected',
179             'availability', 'showdescription');
180         foreach ($easymergefields as $key) {
181             if (isset($options[$key])) {
182                 $moduleinfo->$key = $options[$key];
183             }
184         }
186         // Set default values. Note that visibleold and completiongradeitemnumber are not used when creating a module.
187         $defaults = array(
188             'section' => 0,
189             'visible' => 1,
190             'cmidnumber' => '',
191             'groupmode' => 0,
192             'groupingid' => 0,
193             'availability' => null,
194             'completion' => 0,
195             'completionview' => 0,
196             'completionexpected' => 0,
197             'conditiongradegroup' => array(),
198             'conditionfieldgroup' => array(),
199             'conditioncompletiongroup' => array()
200         );
201         foreach ($defaults as $key => $value) {
202             if (!isset($moduleinfo->$key)) {
203                 $moduleinfo->$key = $value;
204             }
205         }
207         return $moduleinfo;
208     }
210     /**
211      * Creates an instance of the module for testing purposes.
212      *
213      * Module type will be taken from the class name. Each module type may overwrite
214      * this function to add other default values used by it.
215      *
216      * @param array|stdClass $record data for module being generated. Requires 'course' key
217      *     (an id or the full object). Also can have any fields from add module form.
218      * @param null|array $options general options for course module. Since 2.6 it is
219      *     possible to omit this argument by merging options into $record
220      * @return stdClass record from module-defined table with additional field
221      *     cmid (corresponding id in course_modules table)
222      */
223     public function create_instance($record = null, array $options = null) {
224         global $CFG, $DB;
225         require_once($CFG->dirroot.'/course/modlib.php');
227         $this->instancecount++;
229         // Merge options into record and add default values.
230         $record = $this->prepare_moduleinfo_record($record, $options);
232         // Retrieve the course record.
233         if (!empty($record->course->id)) {
234             $course = $record->course;
235             $record->course = $record->course->id;
236         } else {
237             $course = get_course($record->course);
238         }
240         // Fill the name and intro with default values (if missing).
241         if (empty($record->name)) {
242             $record->name = get_string('pluginname', $this->get_modulename()).' '.$this->instancecount;
243         }
244         if (empty($record->introeditor) && empty($record->intro)) {
245             $record->intro = 'Test '.$this->get_modulename().' ' . $this->instancecount;
246         }
247         if (empty($record->introeditor) && empty($record->introformat)) {
248             $record->introformat = FORMAT_MOODLE;
249         }
251         // Before Moodle 2.6 it was possible to create a module with completion tracking when
252         // it is not setup for course and/or site-wide. Display debugging message so it is
253         // easier to trace an error in unittests.
254         if ($record->completion && empty($CFG->enablecompletion)) {
255             debugging('Did you forget to set $CFG->enablecompletion before generating module with completion tracking?', DEBUG_DEVELOPER);
256         }
257         if ($record->completion && empty($course->enablecompletion)) {
258             debugging('Did you forget to enable completion tracking for the course before generating module with completion tracking?', DEBUG_DEVELOPER);
259         }
261         // Add the module to the course.
262         $moduleinfo = add_moduleinfo($record, $course, $mform = null);
264         // Prepare object to return with additional field cmid.
265         $instance = $DB->get_record($this->get_modulename(), array('id' => $moduleinfo->instance), '*', MUST_EXIST);
266         $instance->cmid = $moduleinfo->coursemodule;
267         return $instance;
268     }
270     /**
271      * Generates a piece of content for the module.
272      * User is usually taken from global $USER variable.
273      * @param stdClass $instance object returned from create_instance() call
274      * @param stdClass|array $record
275      * @return stdClass generated object
276      * @throws coding_exception if function is not implemented by module
277      */
278     public function create_content($instance, $record = array()) {
279         throw new coding_exception('Module generator for '.$this->get_modulename().' does not implement method create_content()');
280     }