3 // This file is part of Moodle - http://moodle.org/
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
19 * Random course generator. By Nicolas Connault and friends.
21 * To use go to .../admin/generator.php?web_interface=1 in your browser.
24 * @copyright 2009 Nicolas Connault
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28 require_once(dirname(__FILE__
).'/../config.php');
29 require_once($CFG->libdir
. '/formslib.php');
30 require_once($CFG->dirroot
.'/course/lib.php');
31 require_once($CFG->libdir
.'/filelib.php');
33 define('GENERATOR_RANDOM', 0);
34 define('GENERATOR_SEQUENCE', 1);
37 * Controller class for data generation
40 public $modules_to_ignore = array('hotpot', 'lams', 'journal', 'scorm', 'exercise', 'dialogue');
41 public $modules_list = array('forum' => 'forum',
42 'assignment' => 'assignment',
45 'glossary' => 'glossary',
47 'comments' => 'comments',
48 'feedback' => 'feedback',
53 'resource' => 'resource',
56 'workshop' => 'workshop');
58 public $resource_types = array('text', 'file', 'html', 'repository', 'directory', 'ims');
59 public $glossary_formats = array('continuous', 'encyclopedia', 'entrylist', 'faq', 'fullwithauthor', 'fullwithoutauthor', 'dictionary');
60 public $assignment_types = array('upload', 'uploadsingle', 'online', 'offline');
61 public $forum_types = array('general'); // others include 'single', 'eachuser', 'qanda'
63 public $resource_type_counter = 0;
64 public $assignment_type_counter = 0;
65 public $forum_type_counter = 0;
67 public $settings = array();
68 public $eolchar = '<br />';
69 public $do_generation = false;
73 public function __construct($settings = array(), $generate=false) {
76 $this->starttime
= time()+
microtime();
79 array('short'=>'u', 'long'=>'username',
80 'help' => 'Your moodle username', 'type'=>'STRING', 'default' => ''),
81 array('short'=>'pw', 'long'=>'password',
82 'help' => 'Your moodle password', 'type'=>'STRING', 'default' => ''),
83 array('short'=>'P', 'long' => 'database_prefix',
84 'help' => 'Database prefix to use: tables must already exist or the script will abort!',
85 'type'=>'STRING', 'default' => $CFG->prefix
),
86 array('short'=>'c', 'long' => 'pre_cleanup', 'help' => 'Delete previously generated data'),
87 array('short'=>'C', 'long' => 'post_cleanup',
88 'help' => 'Deletes all generated data at the end of the script (for benchmarking of generation only)'),
89 array('short'=>'t', 'long' => 'time_limit',
90 'help' => 'Optional time limit after which to abort the generation, 0 = no limit. Default=0',
91 'type'=>'SECONDS', 'default' => 0),
92 array('short'=>'v', 'long' => 'verbose', 'help' => 'Display extra information about the data generation'),
93 array('short'=>'q', 'long' => 'quiet', 'help' => 'Inhibits all outputs'),
94 array('short'=>'i', 'long' => 'ignore_errors', 'help' => 'Continue script execution when errors occur'),
95 array('short'=>'N', 'long' => 'no_data', 'help' => 'Generate nothing (used for cleaning up only)'),
96 array('short'=>'T', 'long' => 'tiny',
97 'help' => 'Generates a tiny data set (1 of each course, module, user and section)',
99 array('short'=>'nc', 'long' => 'number_of_courses',
100 'help' => 'The number of courses to generate. Default=1',
101 'type'=>'NUMBER', 'default' => 1),
102 array('short'=>'ns', 'long' => 'number_of_students',
103 'help' => 'The number of students to generate. Default=250',
104 'type'=>'NUMBER', 'default' => 250),
105 array('short'=>'sc', 'long' => 'students_per_course',
106 'help' => 'The number of students to enrol in each course. Default=20',
107 'type'=>'NUMBER', 'default' => 20),
108 array('short'=>'nsec', 'long' => 'number_of_sections',
109 'help' => 'The number of sections to generate in each course. Default=10',
110 'type'=>'NUMBER', 'default' => 10),
111 array('short'=>'nmod', 'long' => 'number_of_modules',
112 'help' => 'The number of modules to generate in each section. Default=10',
113 'type'=>'NUMBER', 'default' => 10),
114 array('short'=>'mods', 'long' => 'modules_list',
115 'help' => 'The list of modules you want to generate', 'default' => $this->modules_list
,
116 'type' => 'mod1,mod2...'),
117 array('short'=>'rt', 'long' => 'resource_type',
118 'help' => 'The specific type of resource you want to generate. Defaults to all',
119 'default' => $this->resource_types
,
121 array('short'=>'at', 'long' => 'assignment_type',
122 'help' => 'The specific type of assignment you want to generate. Defaults to all',
123 'default' => $this->assignment_types
,
125 array('short'=>'ft', 'long' => 'forum_type',
126 'help' => 'The specific type of forum you want to generate. Defaults to all',
127 'default' => $this->forum_types
,
129 array('short'=>'gf', 'long' => 'glossary_format',
130 'help' => 'The specific format of glossary you want to generate. Defaults to all',
131 'default' => $this->glossary_formats
,
133 array('short'=>'ag', 'long' => 'assignment_grades',
134 'help' => 'Generate random grades for each student/assignment tuple', 'default' => true),
135 array('short'=>'qg', 'long' => 'quiz_grades',
136 'help' => 'Generate random grades for each student/quiz tuple', 'default' => true),
137 array('short'=>'eg', 'long' => 'entries_per_glossary',
138 'help' => 'The number of definitions to generate per glossary. Default=0',
139 'type'=>'NUMBER', 'default' => 1),
140 array('short'=>'nq', 'long' => 'questions_per_course',
141 'help' => 'The number of questions to generate per course. Default=20',
142 'type'=>'NUMBER', 'default' => 20),
143 array('short'=>'qq', 'long' => 'questions_per_quiz',
144 'help' => 'The number of questions to assign to each quiz. Default=5',
145 'type'=>'NUMBER', 'default' => 5),
146 array('short'=>'df', 'long' => 'discussions_per_forum',
147 'help' => 'The number of discussions to generate for each forum. Default=5',
148 'type'=>'NUMBER', 'default' => 5),
149 array('short'=>'pd', 'long' => 'posts_per_discussion',
150 'help' => 'The number of posts to generate for each forum discussion. Default=15',
151 'type'=>'NUMBER', 'default' => 15),
152 array('short'=>'fd', 'long' => 'fields_per_database',
153 'help' => 'The number of fields to generate for each database. Default=4',
154 'type'=>'NUMBER', 'default' => 4),
155 array('short'=>'drs', 'long' => 'database_records_per_student',
156 'help' => 'The number of records to generate for each student/database tuple. Default=1',
157 'type'=>'NUMBER', 'default' => 1),
158 array('short'=>'mc', 'long' => 'messages_per_chat',
159 'help' => 'The number of messages to generate for each chat module. Default=10',
160 'type'=>'NUMBER', 'default' => 10),
163 foreach ($arguments as $args_array) {
164 $this->settings
[$args_array['long']] = new generator_argument($args_array);
167 foreach ($settings as $setting => $value) {
168 $this->settings
[$setting]->value
= $value;
172 $this->generate_data();
176 public function connect() {
178 $this->original_db
= $DB;
180 $class = get_class($DB);
182 $DB->connect($CFG->dbhost
, $CFG->dbuser
, $CFG->dbpass
, $CFG->dbname
, $this->get('database_prefix'));
185 public function dispose() {
188 $DB = $this->original_db
;
191 public function generate_users() {
197 $this->verbose("Generating ".$this->get('number_of_students')." students...");
198 $lastnames = array('SMITH','JOHNSON','WILLIAMS','JONES','BROWN','DAVIS','MILLER','WILSON',
199 'MOORE','TAYLOR','ANDERSON','THOMAS','JACKSON','WHITE','HARRIS','MARTIN','THOMPSON',
200 'GARCIA','MARTINEZ','ROBINSON','CLARK','RODRIGUEZ','LEWIS','LEE','WALKER','HALL',
201 'ALLEN','YOUNG','HERNANDEZ','KING','WRIGHT','LOPEZ','HILL','SCOTT','GREEN','ADAMS',
202 'BAKER','GONZALEZ','NELSON','CARTER','MITCHELL','PEREZ','ROBERTS','TURNER','PHILLIPS',
203 'CAMPBELL','PARKER','EVANS','EDWARDS','COLLINS','STEWART','SANCHEZ','MORRIS','ROGERS',
204 'REED','COOK','MORGAN','BELL','MURPHY','BAILEY','RIVERA','COOPER','RICHARDSON','COX',
205 'HOWARD','WARD','TORRES','PETERSON','GRAY','RAMIREZ','JAMES','WATSON','BROOKS','KELLY',
206 'SANDERS','PRICE','BENNETT','WOOD','BARNES','ROSS','HENDERSON','COLEMAN','JENKINS','PERRY',
207 'POWELL','LONG','PATTERSON','HUGHES','FLORES','WASHINGTON','BUTLER','SIMMONS','FOSTER',
208 'GONZALES','BRYANT','ALEXANDER','RUSSELL','GRIFFIN','DIAZ','HAYES','MYERS','FORD','HAMILTON',
209 'GRAHAM','SULLIVAN','WALLACE','WOODS','COLE','WEST','JORDAN','OWENS','REYNOLDS','FISHER',
210 'ELLIS','HARRISON','GIBSON','MCDONALD','CRUZ','MARSHALL','ORTIZ','GOMEZ','MURRAY','FREEMAN',
211 'WELLS','WEBB','SIMPSON','STEVENS','TUCKER','PORTER','HUNTER','HICKS','CRAWFORD','HENRY',
212 'BOYD','MASON','MORALES','KENNEDY','WARREN','DIXON','RAMOS','REYES','BURNS','GORDON','SHAW',
213 'HOLMES','RICE','ROBERTSON','HUNT','BLACK','DANIELS','PALMER','MILLS','NICHOLS','GRANT',
214 'KNIGHT','FERGUSON','ROSE','STONE','HAWKINS','DUNN','PERKINS','HUDSON','SPENCER','GARDNER',
215 'STEPHENS','PAYNE','PIERCE','BERRY','MATTHEWS','ARNOLD','WAGNER','WILLIS','RAY','WATKINS',
216 'OLSON','CARROLL','DUNCAN','SNYDER','HART','CUNNINGHAM','BRADLEY','LANE','ANDREWS','RUIZ',
217 'HARPER','FOX','RILEY','ARMSTRONG','CARPENTER','WEAVER','GREENE','LAWRENCE','ELLIOTT','CHAVEZ',
218 'SIMS','AUSTIN','PETERS','KELLEY','FRANKLIN','LAWSON','FIELDS','GUTIERREZ','RYAN','SCHMIDT',
219 'CARR','VASQUEZ','CASTILLO','WHEELER','CHAPMAN','OLIVER','MONTGOMERY','RICHARDS','WILLIAMSON',
220 'JOHNSTON','BANKS','MEYER','BISHOP','MCCOY','HOWELL','ALVAREZ','MORRISON','HANSEN','FERNANDEZ',
221 'GARZA','HARVEY','LITTLE','BURTON','STANLEY','NGUYEN','GEORGE','JACOBS','REID','KIM','FULLER',
222 'LYNCH','DEAN','GILBERT','GARRETT','ROMERO','WELCH','LARSON','FRAZIER','BURKE','HANSON','DAY',
223 'MENDOZA','MORENO','BOWMAN','MEDINA','FOWLER');
224 $firstnames = array( 'JAMES','JOHN','ROBERT','MARY','MICHAEL','WILLIAM','DAVID','RICHARD',
225 'CHARLES','JOSEPH','THOMAS','PATRICIA','LINDA','CHRISTOPHER','BARBARA','DANIEL','PAUL',
226 'MARK','ELIZABETH','JENNIFER','DONALD','GEORGE','MARIA','KENNETH','SUSAN','STEVEN','EDWARD',
227 'MARGARET','BRIAN','DOROTHY','RONALD','ANTHONY','LISA','KEVIN','NANCY','KAREN','BETTY',
228 'HELEN','JASON','MATTHEW','GARY','TIMOTHY','SANDRA','JOSE','LARRY','JEFFREY','DONNA',
229 'FRANK','CAROL','RUTH','SCOTT','ERIC','STEPHEN','ANDREW','SHARON','MICHELLE','LAURA',
230 'SARAH','KIMBERLY','DEBORAH','JESSICA','RAYMOND','SHIRLEY','CYNTHIA','ANGELA','MELISSA',
231 'BRENDA','AMY','GREGORY','ANNA','JOSHUA','JERRY','REBECCA','VIRGINIA','KATHLEEN','PAMELA',
232 'DENNIS','MARTHA','DEBRA','AMANDA','STEPHANIE','WALTER','PATRICK','CAROLYN','CHRISTINE',
233 'PETER','MARIE','JANET','CATHERINE','HAROLD','FRANCES','DOUGLAS','HENRY','ANN','JOYCE',
234 'DIANE','ALICE','JULIE','CARL','HEATHER');
239 shuffle($firstnames);
241 $next_user_id = $DB->get_field_sql("SELECT MAX(id) FROM {user}") +
1;
243 for ($i = 0; $i < $this->get('number_of_students'); $i++
) {
245 $lastname = trim(ucfirst(strtolower($lastnames[rand(0, count($lastnames) - 1)])));
246 $firstname = $firstnames[rand(0, count($firstnames) - 1)];
248 $user = new stdClass();
249 $user->firstname
= trim(ucfirst(strtolower($firstname)));
250 $user->username
= strtolower(substr($firstname, 0, 7) . substr($lastname, 0, 7)) . $next_user_id++
;
251 $user->lastname
= $lastname;
252 $user->email
= $user->username
. '@example.com';
253 $user->mnethostid
= 1;
254 $user->city
= 'Test City';
255 $user->country
= 'AU';
256 $user->password
= md5('password');
257 $user->auth
= 'manual';
258 $user->confirmed
= 1;
259 $user->lang
= $CFG->lang
;
260 $user->timemodified
= time();
262 $user->id
= $DB->insert_record("user", $user);
264 $users[] = $user->id
;
265 $next_user_id = $user->id +
1;
266 $this->verbose("Inserted $user->firstname $user->lastname into DB "
267 ."(username=$user->username, password=password).");
270 if (!$this->get('quiet')) {
271 echo "$users_count users correctly inserted in the database.{$this->eolchar}";
276 public function generate_data() {
277 if (!$this->do_generation
) {
281 set_time_limit($this->get('time_limit'));
283 // Process tiny data set
284 $tiny = $this->get('tiny');
286 $this->verbose("Generating a tiny data set: 1 student in 1 course with 1 module in 1 section...");
287 $this->set('number_of_courses',1);
288 $this->set('number_of_students',1);
289 $this->set('number_of_modules',1);
290 $this->set('number_of_sections',1);
291 $this->set('assignment_grades',false);
292 $this->set('quiz_grades',false);
293 $this->set('students_per_course',1);
294 $this->set('questions_per_course',1);
295 $this->set('questions_per_quiz',1);
298 if ($this->get('pre_cleanup')) {
299 $this->verbose("Deleting previous test data...");
300 $this->data_cleanup();
302 if (!$this->get('quiet')) {
303 echo "Previous test data has been deleted.{$this->eolchar}";
308 if (!$this->get('no_data')) {
309 $users = $this->generate_users();
310 $courses = $this->generate_courses();
311 $modules = $this->generate_modules($courses);
312 $questions = $this->generate_questions($courses, $modules);
313 $course_users = $this->generate_role_assignments($users, $courses);
314 $this->generate_forum_posts($course_users, $modules);
315 $this->generate_grades($course_users, $courses, $modules);
316 $this->generate_module_content($course_users, $courses, $modules);
319 if ($this->get('post_cleanup')) {
320 if (!$this->get('quiet')) {
321 echo "Removing generated data..." . $this->eolchar
;
323 $this->data_cleanup();
324 if (!$this->get('quiet')) {
325 echo "Generated data has been deleted." . $this->eolchar
;
332 $stoptimer = time()+
microtime();
333 $timer = round($stoptimer-$this->starttime
,4);
334 if (!$this->get('quiet')) {
335 echo "End of script! ($timer seconds taken){$this->eolchar}";
340 public function generate_courses() {
343 $this->verbose("Generating " . $this->get('number_of_courses')." courses...");
344 $base_course = new stdClass();
345 $next_course_id = $DB->get_field_sql("SELECT MAX(id) FROM {course}") +
1;
347 $base_course->MAX_FILE_SIZE
= '2097152';
348 $base_course->category
= '1';
349 $base_course->summary
= 'Blah Blah';
350 $base_course->format
= 'weeks';
351 $base_course->numsections
= '10';
352 $base_course->startdate
= mktime();
353 $base_course->id
= '0';
357 for ($i = 1; $i <= $this->get('number_of_courses'); $i++
) {
358 $newcourse = fullclone($base_course);
359 $newcourse->fullname
= "Test course $next_course_id";
360 $newcourse->shortname
= "Test $next_course_id";
361 $newcourse->idnumber
= $next_course_id;
362 if (!$course = create_course($newcourse)) {
363 $this->verbose("Error inserting a new course in the database!");
364 if (!$this->get('ignore_errors')) {
370 $courses[] = $course->id
;
371 $next_course_id = $course->id +
1;
372 $this->verbose("Inserted $course->fullname into DB (idnumber=$course->idnumber).");
376 if (!$this->get('quiet')) {
377 echo "$courses_count test courses correctly inserted into the database.{$this->eolchar}";
382 public function generate_modules($courses) {
384 // Parse the modules-list variable
386 $this->verbose("Generating " . $this->get('number_of_sections')." sections with "
387 .$this->get('number_of_modules')." modules in each section, for each course...");
389 list($modules_list_sql, $modules_params) =
390 $DB->get_in_or_equal($this->get('modules_list'), SQL_PARAMS_NAMED
, 'mod', true);
392 list($modules_ignored_sql, $ignore_params) =
393 $DB->get_in_or_equal($this->modules_to_ignore
, SQL_PARAMS_NAMED
, 'ignore', false);
395 $wheresql = "name $modules_list_sql AND name $modules_ignored_sql";
396 $modules = $DB->get_records_select('modules', $wheresql, array_merge($modules_params, $ignore_params));
398 foreach ($modules as $key => $module) {
401 // Scorm, lams and hotpot are too complex to set up, remove them
402 if (in_array($module->name
, $this->modules_to_ignore
) ||
403 !in_array($module->name
, $this->modules_list
)) {
404 unset($modules[$key]);
408 // Dirty hack for renumbering the modules array's keys
409 $first_module = reset($modules);
410 array_shift($modules);
411 array_unshift($modules, $first_module);
413 $modules_array = array();
415 if (count($courses) > 0) {
416 $libraries = array();
417 foreach ($courses as $courseid) {
420 for ($i = 1; $i <= $this->get('number_of_sections'); $i++
) {
421 for ($j = 0; $j < $this->get('number_of_modules'); $j++
) {
423 $module = new stdClass();
425 // If only one module is created, and we also need to add a question to a quiz, create only a quiz
426 if ($this->get('number_of_modules') == 1
427 && $this->get('questions_per_quiz') > 0
428 && !empty($modules[8])) {
429 $moduledata = $modules[8];
431 $moduledata = $modules[array_rand($modules)];
434 $libfile = "$CFG->dirroot/mod/$moduledata->name/lib.php";
435 if (file_exists($libfile)) {
436 if (!in_array($libfile, $libraries)) {
437 $this->verbose("Including library for $moduledata->name...");
438 $libraries[] = $libfile;
439 require_once($libfile);
442 $this->verbose("Could not load lib file for module $moduledata->name!");
443 if (!$this->get('ignore_errors')) {
448 // Basically 2 types of text fields: description and content
449 $description = "This $moduledata->name has been randomly generated by a very useful script, "
450 . "for the purpose of testing "
451 . "the boundaries of Moodle in various contexts. Moodle should be able to scale to "
452 . "any size without "
453 . "its speed and ease of use being affected dramatically.";
454 $content = 'Very useful content, I am sure you would agree';
456 $module_type_index = 0;
457 $module->introformat
= FORMAT_MOODLE
;
458 $module->messageformat
= FORMAT_MOODLE
;
460 // Special module-specific config
461 switch ($moduledata->name
) {
463 $module->intro
= $description;
464 $module->assignmenttype
= $this->get_module_type('assignment');
465 $module->timedue
= mktime() +
89487321;
466 $module->grade
= rand(50,100);
469 $module->intro
= $description;
470 $module->schedule
= 1;
471 $module->chattime
= 60 * 60 * 4;
474 $module->intro
= $description;
475 $module->name
= 'test';
478 $module->intro
= $description;
479 $module->text
= $content;
480 $module->option
= array('Good choice', 'Bad choice', 'No choice');
481 $module->limit
= array(1, 5, 0);
484 $module->intro
= $description;
485 $module->comments
= $content;
488 $module->intro
= $description;
489 $module->page_after_submit
= $description;
490 $module->comments
= $content;
493 $module->intro
= $description;
494 $module->type
= $this->get_module_type('forum');
495 $module->forcesubscribe
= rand(0, 1);
499 $module->intro
= $description;
500 $module->displayformat
= $this->glossary_formats
[rand(0, count($this->glossary_formats
) - 1)];
501 $module->cmidnumber
= rand(0,999999);
504 $module->content
= $content;
505 $module->intro
= $description;
508 $module->lessondefault
= 1;
509 $module->available
= mktime();
510 $module->deadline
= mktime() +
719891987;
511 $module->grade
= 100;
514 $module->intro
= $description;
515 $module->feedbacktext
= 'blah';
516 $module->feedback
= 1;
517 $module->feedbackboundaries
= array(2, 1);
519 $module->timeopen
= time();
520 $module->timeclose
= time() +
68854;
521 $module->shufflequestions
= true;
522 $module->shuffleanswers
= true;
523 $module->quizpassword
= '';
526 $module->type
= $this->get_module_type('resource');
527 $module->alltext
= $content;
528 $module->summary
= $description;
529 $module->windowpopup
= rand(0,1);
530 $module->display
= rand(0,1);
531 $module->resizable
= rand(0,1);
532 $module->scrollbars
= rand(0,1);
533 $module->directories
= rand(0,1);
534 $module->location
= 'file.txt';
535 $module->menubar
= rand(0,1);
536 $module->toolbar
= rand(0,1);
537 $module->status
= rand(0,1);
538 $module->width
= rand(200,600);
539 $module->height
= rand(200,600);
540 $module->directories
= rand(0,1);
541 $module->files
= false;
542 $module->param_navigationmenu
= rand(0,1);
543 $module->param_navigationbuttons
= rand(0,1);
544 $module->reference
= 1;
545 $module->forcedownload
= 1;
548 $module->template
= rand(1,5);
549 $module->intro
= $description;
552 $module->intro
= $description;
553 $module->summary
= $description;
557 $module->name
= ucfirst($moduledata->name
) . ' ' . $moduledata->count++
;
559 $module->course
= $courseid;
560 $module->section
= $i;
561 $module->module
= $moduledata->id
;
562 $module->modulename
= $moduledata->name
;
563 $module->add
= $moduledata->name
;
564 $module->cmidnumber
= '';
565 $module->coursemodule
= '';
566 $add_instance_function = $moduledata->name
. '_add_instance';
568 $section = get_course_section($i, $courseid);
569 $module->section
= $section->id
;
570 $module->coursemodule
= add_course_module($module);
571 $module->section
= $i;
573 if (function_exists($add_instance_function)) {
574 $this->verbose("Calling module function $add_instance_function");
575 $module->instance
= $add_instance_function($module, '');
576 $DB->set_field('course_modules', 'instance', $module->instance
, array('id'=>$module->coursemodule
));
578 $this->verbose("Function $add_instance_function does not exist!");
579 if (!$this->get('ignore_errors')) {
584 add_mod_to_section($module);
586 $module->cmidnumber
= set_coursemodule_idnumber($module->coursemodule
, '');
588 $this->verbose("A $moduledata->name module was added to section $i (id $module->section) "
589 ."of course $courseid.");
590 rebuild_course_cache($courseid);
592 $module_instance = $DB->get_field('course_modules', 'instance', array('id' => $module->coursemodule
));
593 $module_record = $DB->get_record($moduledata->name
, array('id' => $module_instance));
594 $module_record->instance
= $module_instance;
596 if (empty($modules_array[$moduledata->name
])) {
597 $modules_array[$moduledata->name
] = array();
600 // TODO Find out why some $module_record end up empty here... (particularly quizzes)
601 if (!empty($module_record->instance
)) {
602 $modules_array[$moduledata->name
][] = $module_record;
608 if (!$this->get('quiet')) {
609 echo "Successfully generated " . $this->get('number_of_modules') * $this->get('number_of_sections')
610 . " modules in each course!{$this->eolchar}";
613 return $modules_array;
618 public function generate_questions($courses, $modules) {
621 if (!is_null($this->get('questions_per_course')) && count($courses) > 0 && is_array($courses)) {
622 require_once($CFG->libdir
.'/questionlib.php');
623 require_once($CFG->dirroot
.'/mod/quiz/editlib.php');
624 $questions = array();
625 $questionsmenu = question_type_menu();
626 $questiontypes = array();
627 foreach ($questionsmenu as $qtype => $qname) {
628 $questiontypes[] = $qtype;
632 foreach ($courses as $courseid) {
633 $questions[$courseid] = array();
634 for ($i = 0; $i < $this->get('questions_per_course'); $i++
) {
635 $qtype = $questiontypes[array_rand($questiontypes)];
637 // Only the following types are supported right now. Hang around for more!
638 $supported_types = array('match', 'essay', 'multianswer', 'multichoice', 'shortanswer',
639 'numerical', 'truefalse', 'calculated');
640 $qtype = $supported_types[array_rand($supported_types)];
642 if ($qtype == 'calculated') {
645 $classname = "question_{$qtype}_qtype";
646 if ($qtype == 'multianswer') {
647 $classname = "embedded_cloze_qtype";
650 $question = new $classname();
651 $question->qtype
= $qtype;
652 $questions[$courseid][] = $question->generate_test("question$qtype-$i", $courseid);
653 $this->verbose("Generated a question of type $qtype for course id $courseid.");
657 // Assign questions to quizzes, if such exist
658 if (!empty($modules['quiz']) && !empty($questions) && !is_null($this->get('questions_per_quiz'))) {
659 $quizzes = $modules['quiz'];
661 // Cannot assign more questions per quiz than are available, so determine which is the largest
662 $questions_per_quiz = max(count($questions), $this->get('questions_per_quiz'));
664 foreach ($quizzes as $quiz) {
665 $questions_added = array();
666 for ($i = 0; $i < $questions_per_quiz; $i++
) {
668 // Add a random question to the quiz
670 if (empty($quiz->course
)) {
671 print_object($quizzes);die();
673 $random = rand(0, count($questions[$quiz->course
]));
674 } while (in_array($random, $questions_added) ||
!array_key_exists($random, $questions[$quiz->course
]));
676 if (!quiz_add_quiz_question($questions[$quiz->course
][$random]->id
, $quiz)) {
678 // Could not add question to quiz!! report error
679 if (!$this->get('quiet')) {
680 echo "WARNING: Could not add question id $random to quiz id $quiz->id{$this->eolchar}";
683 $this->verbose("Adding question id $random to quiz id $quiz->id.");
684 $questions_added[] = $random;
694 public function generate_role_assignments($users, $courses) {
696 $course_users = array();
698 if (count($courses) > 0) {
699 $this->verbose("Inserting student->course role assignments...");
701 $assigned_users = array();
703 foreach ($courses as $courseid) {
704 $course_users[$courseid] = array();
706 // Select $students_per_course for assignment to course
708 $users_to_assign = array_slice($users, 0, $this->get('students_per_course'));
710 $context = get_context_instance(CONTEXT_COURSE
, $courseid);
711 foreach ($users_to_assign as $random_user) {
712 role_assign(5, $random_user, $context->id
);
715 $course_users[$courseid][] = $random_user;
716 if (!isset($assigned_users[$random_user])) {
717 $assigned_users[$random_user] = 1;
719 $assigned_users[$random_user]++
;
721 $this->verbose("Student $random_user was assigned to course $courseid.");
725 if (!$this->get('quiet')) {
726 echo "$assigned_count user => course role assignments have been correctly performed.{$this->eolchar}";
728 return $course_users;
733 public function generate_forum_posts($course_users, $modules) {
734 global $CFG, $DB, $USER;
736 if (in_array('forum', $this->modules_list
) &&
737 $this->get('discussions_per_forum') &&
738 $this->get('posts_per_discussion') &&
739 isset($modules['forum'])) {
741 $discussions_count = 0;
744 foreach ($modules['forum'] as $forum) {
745 $forum_users = $course_users[$forum->course
];
747 for ($i = 0; $i < $this->get('discussions_per_forum'); $i++
) {
748 $mform = new fake_form();
750 require_once($CFG->dirroot
.'/mod/forum/lib.php');
752 $discussion = new stdClass();
753 $discussion->course
= $forum->course
;
754 $discussion->forum
= $forum->id
;
755 $discussion->name
= 'Test discussion';
756 $discussion->intro
= 'This is just a test forum discussion';
757 $discussion->assessed
= 0;
758 $discussion->messageformat
= 1;
759 $discussion->messagetrust
= 0;
760 $discussion->mailnow
= false;
761 $discussion->groupid
= -1;
762 $discussion->attachments
= null;
763 $discussion->itemid
= 752157083;
766 $super_global_user = clone($USER);
767 $user_id = $forum_users[array_rand($forum_users)];
768 $USER = $DB->get_record('user', array('id' => $user_id));
770 if ($discussion_id = forum_add_discussion($discussion, $mform, $message)) {
771 $discussion = $DB->get_record('forum_discussions', array('id' => $discussion_id));
772 $discussions_count++
;
774 // Add posts to this discussion
775 $post_ids = array($discussion->firstpost
);
777 for ($j = 0; $j < $this->get('posts_per_discussion'); $j++
) {
778 $global_user = clone($USER);
779 $user_id = $forum_users[array_rand($forum_users)];
780 $USER = $DB->get_record('user', array('id' => $user_id));
781 $post = new stdClass();
782 $post->discussion
= $discussion_id;
783 $post->subject
= 'Re: test discussion';
784 $post->message
= '<p>Nothing much to say, since this is just a test...</p>';
786 $post->attachments
= null;
787 $post->itemid
= 752157083;
788 $post->parent
= $post_ids[array_rand($post_ids)];
790 if ($post_ids[] = forum_add_new_post($post, $mform, $message)) {
793 $USER = $global_user;
797 $USER = $super_global_user;
799 if ($forum->type
== 'single') {
804 if ($discussions_count > 0 && !$this->get('quiet')) {
805 echo "$discussions_count forum discussions have been generated.{$this->eolchar}";
807 if ($posts_count > 0 && !$this->get('quiet')) {
808 echo "$posts_count forum posts have been generated.{$this->eolchar}";
817 public function generate_grades($course_users, $courses, $modules) {
818 global $CFG, $DB, $USER;
821 * ASSIGNMENT GRADES GENERATION
823 if ($this->get('assignment_grades') && isset($modules['assignment'])) {
825 foreach ($course_users as $courseid => $userid_array) {
826 foreach ($userid_array as $userid) {
827 foreach ($modules['assignment'] as $assignment) {
828 if (in_array($assignment->course
, $courses)) {
829 $maxgrade = $assignment->grade
;
830 $random_grade = rand(0, $maxgrade);
831 $grade = new stdClass();
832 $grade->assignment
= $assignment->id
;
833 $grade->userid
= $userid;
834 $grade->grade
= $random_grade;
835 $grade->rawgrade
= $random_grade;
836 $grade->teacher
= $USER->id
;
837 $grade->submissioncomment
= 'comment';
838 $DB->insert_record('assignment_submissions', $grade);
839 grade_update('mod/assignment', $assignment->course
, 'mod', 'assignment', $assignment->id
, 0, $grade);
840 $this->verbose("A grade ($random_grade) has been given to user $userid "
841 . "for assignment $assignment->id");
847 if ($grades_count > 0) {
848 $this->verbose("$grades_count assignment grades have been generated.{$this->eolchar}");
853 * QUIZ GRADES GENERATION
855 if ($this->get('quiz_grades') && isset($modules['quiz'])) {
857 foreach ($course_users as $userid => $courses) {
858 foreach ($modules['quiz'] as $quiz) {
859 if (in_array($quiz->course
, $courses)) {
860 $maxgrade = $quiz->grade
;
861 $random_grade = rand(0, $maxgrade);
862 $grade = new stdClass();
863 $grade->quiz
= $quiz->id
;
864 $grade->userid
= $userid;
865 $grade->grade
= $random_grade;
866 $grade->rawgrade
= $random_grade;
867 $DB->insert_record('quiz_grades', $grade);
868 grade_update('mod/quiz', $courseid, 'mod', 'quiz', $quiz->id
, 0, $grade);
869 $this->verbose("A grade ($random_grade) has been given to user $userid for quiz $quiz->id");
874 if ($grades_count > 0 && !$this->get('quiet')) {
875 echo "$grades_count quiz grades have been generated.{$this->eolchar}";
881 public function generate_module_content($course_users, $courses, $modules) {
882 global $USER, $DB, $CFG;
886 if ($this->get('entries_per_glossary') && !empty($modules['glossary'])) {
887 foreach ($modules['glossary'] as $glossary) {
888 for ($i = 0; $i < $this->get('entries_per_glossary'); $i++
) {
889 $entry = new stdClass();
890 $entry->glossaryid
= $glossary->id
;
891 $entry->userid
= $USER->id
;
892 $entry->concept
= "Test concept";
893 $entry->definition
= "A test concept is nothing to write home about: just a test concept.";
895 $entry->timecreated
= time();
896 $entry->timemodified
= time();
897 $entry->teacherentry
= 0;
898 $entry->approved
= 1;
899 $DB->insert_record('glossary_entries', $entry);
903 if ($entries_count > 0 && !$this->get('quiet')) {
904 echo "$entries_count glossary definitions have been generated.{$this->eolchar}";
910 if (!empty($modules['data']) && $this->get('fields_per_database') && $this->get('database_records_per_student')) {
911 $database_field_types = array('checkbox',
927 foreach ($modules['data'] as $data) {
929 for ($i = 0; $i < $this->get('fields_per_database'); $i++
) {
930 $type = $database_field_types[array_rand($database_field_types)];
931 require_once($CFG->dirroot
.'/mod/data/field/'.$type.'/field.class.php');
932 $newfield = 'data_field_'.$type;
933 $cm = get_coursemodule_from_instance('data', $data->id
);
934 $newfield = new $newfield(0, $data, $cm);
935 $fields[$data->id
][] = $newfield;
936 $newfield->insert_field();
939 // Generate fields for each database (same fields for all, no arguing)
940 for ($i = 0; $i < $this->get('fields_per_database'); $i++
) {
944 // Generate database records for each student, if needed
945 for ($i = 0; $i < $this->get('database_records_per_student'); $i++
) {
949 if ($fields_count > 0 && !$this->get('quiet')) {
950 $datacount = count($modules['data']);
951 echo "$fields_count database fields have been generated for each of the "
952 . "$datacount generated databases.{$this->eolchar}";
958 if (!empty($modules['chat']) && $this->get('messages_per_chat')) {
960 // Insert all users into chat_users table, then a message from each user
961 foreach ($modules['chat'] as $chat) {
963 foreach ($course_users as $courseid => $users_array) {
965 foreach ($users_array as $userid) {
966 if ($messages_count < $this->get('messages_per_chat')) {
967 $chat_user = new stdClass();
968 $chat_user->chatid
= $chat->id
;
969 $chat_user->userid
= $userid;
970 $chat_user->course
= $courseid;
971 $DB->insert_record('chat_users', $chat_user);
973 $chat_message = new stdClass();
974 $chat_message->chatid
= $chat->id
;
975 $chat_message->userid
= $userid;
976 $chat_message->message
= "Hi, everyone!";
977 $DB->insert_record('chat_messages', $chat_message);
985 if ($messages_count > 0 && !$this->get('quiet')) {
986 $datacount = count($modules['chat']);
987 echo "$messages_count messages have been generated for each of the "
988 . "$datacount generated chats.{$this->eolchar}";
998 * If verbose is switched on, prints a string terminated by the global eolchar string.
999 * @param string $string The string to STDOUT
1001 public function verbose($string) {
1002 if ($this->get('verbose') && !$this->get('quiet')) {
1003 echo $string . $this->eolchar
;
1009 * Attempts to delete all generated test data.
1010 * WARNING: THIS WILL COMPLETELY MESS UP A "REAL" SITE, AND IS INTENDED ONLY FOR DEVELOPMENT PURPOSES
1012 function data_cleanup() {
1015 if ($this->get('quiet')) {
1019 // TODO Cleanup code
1021 if ($this->get('quiet')) {
1026 public function get($setting) {
1027 if (isset($this->settings
[$setting])) {
1028 return $this->settings
[$setting]->value
;
1034 public function set($setting, $value) {
1035 if (isset($this->settings
[$setting])) {
1036 $this->settings
[$setting]->value
= $value;
1042 public function get_module_type($modulename) {
1043 $return_val = false;
1045 $type = $this->get($modulename.'_type');
1047 if (is_object($type) && isset($type->type
) && isset($type->options
)) {
1049 if ($type->type
== GENERATOR_RANDOM
) {
1050 $return_val = $type->options
[array_rand($type->options
)];
1052 } elseif ($type->type
== GENERATOR_SEQUENCE
) {
1053 $return_val = $type->options
[$this->{$modulename.'_type_counter'}];
1054 $this->{$modulename.'_type_counter'}++
;
1056 if ($this->{$modulename.'_type_counter'} == count($type->options
)) {
1057 $this->{$modulename.'_type_counter'} = 0;
1061 } elseif (is_array($type)) {
1062 $return_val = $type[array_rand($type)];
1064 } elseif (is_string($type)) {
1065 $return_val = $type;
1072 class generator_argument
{
1077 public $default = null;
1080 public function __construct($params) {
1081 foreach ($params as $key => $val) {
1084 $this->value
= $this->default;
1088 class generator_cli
extends generator
{
1089 public $eolchar = "\n";
1091 public function __construct($settings, $argc) {
1092 parent
::__construct();
1094 // Building the USAGE output of the command line version
1095 $help = "Moodle Data Generator. Generates Data for Moodle sites. Good for benchmarking and other tests.\n\n"
1096 . "FOR DEVELOPMENT PURPOSES ONLY! DO NOT USE ON A PRODUCTION SITE!\n\n"
1097 . "Note: By default the script attempts to fill DB tables prefixed with tst_\n"
1098 . "To override the prefix, use the -P (--database_prefix) setting.\n\n"
1099 . "Usage: {$settings[0]}; [OPTION] ...\n"
1101 . " -h, -?, -help, --help This output\n";
1103 foreach ($this->settings
as $argument) {
1105 if (!empty($argument->type
)) {
1106 $equal = "={$argument->type}";
1109 $padding1 = 5 - strlen($argument->short
);
1110 $padding2 = 30 - (strlen($argument->long
) +
strlen($equal));
1112 for ($i = 0; $i < $padding1; $i++
) {
1113 $paddingstr1 .= ' ';
1116 for ($i = 0; $i < $padding2; $i++
) {
1117 $paddingstr2 .= ' ';
1120 $help .= " -{$argument->short},$paddingstr1--{$argument->long}$equal$paddingstr2{$argument->help}\n";
1123 $help .= "\nEmail nicolas@moodle.com for any suggestions or bug reports.\n";
1125 if ($argc == 1 ||
in_array($settings[1], array('--help', '-help', '-h', '-?'))) {
1130 $this->do_generation
= true;
1131 $settings = $this->_arguments($settings);
1134 foreach ($this->settings
as $argument) {
1137 if (in_array($argument->short
, array_keys($settings))) {
1138 $value = $settings[$argument->short
];
1139 unset($settings[$argument->short
]);
1141 } elseif (in_array($argument->long
, array_keys($settings))) {
1142 $value = $settings[$argument->long
];
1143 unset($settings[$argument->long
]);
1146 if (!is_null($value)) {
1148 if (!empty($argument->type
) && ($argument->type
== 'mod1,mod2...' ||
$argument->type
== 'SELECT')) {
1149 $value = explode(',', $value);
1152 $this->set($argument->long
, $value);
1157 // If some params are left in argv, it means they are not supported
1158 if ($argscount == 0 ||
count($settings) > 0) {
1167 public function generate_data() {
1168 if (is_null($this->get('username')) ||
$this->get('username') == '') {
1169 echo "You must enter a valid username for a moodle administrator account on this site.{$this->eolchar}";
1171 } elseif (is_null($this->get('password')) ||
$this->get('password') == '') {
1172 echo "You must enter a valid password for a moodle administrator account on this site.{$this->eolchar}";
1175 if (!$user = authenticate_user_login($this->get('username'), $this->get('password'))) {
1176 echo "Invalid username or password!{$this->eolchar}";
1179 complete_user_login($user);
1180 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
1182 if (!is_siteadmin($user->id
)) {//TODO: add some proper access control check here!!
1183 echo "You do not have administration privileges on this Moodle site. "
1184 ."These are required for running the generation script.{$this->eolchar}";
1189 parent
::generate_data();
1193 * Converts the standard $argv into an associative array taking var=val arguments into account
1194 * @param array $argv
1195 * @return array $_ARG
1197 private function _arguments($argv) {
1199 foreach ($argv as $arg) {
1200 if (preg_match('/--?([^=]+)=(.*)/',$arg,$reg)) {
1201 $_ARG[$reg[1]] = $reg[2];
1202 } elseif(preg_match('/-([a-zA-Z0-9]+)/',$arg,$reg)) {
1203 $_ARG[$reg[1]] = 'true';
1210 class generator_web
extends generator
{
1211 public $eolchar = '<br />';
1214 public function setup() {
1216 $this->mform
= new generator_form();
1218 $this->do_generation
= optional_param('do_generation', false, PARAM_BOOL
);
1220 if ($data = $this->mform
->get_data(false)) {
1221 foreach ($this->settings
as $setting) {
1222 if (isset($data->{$setting->long
})) {
1223 $this->set($setting->long
, $data->{$setting->long
});
1229 public function display() {
1230 global $OUTPUT, $PAGE;
1231 $PAGE->set_title("Data generator");
1232 echo $OUTPUT->header();
1233 echo $OUTPUT->heading("Data generator: web interface");
1234 echo $OUTPUT->heading("FOR DEVELOPMENT PURPOSES ONLY. DO NOT USE ON A PRODUCTION SITE!", 3);
1235 echo $OUTPUT->heading("Your database contents will probably be massacred. You have been warned", 5);
1237 $this->mform
->display();
1241 public function complete() {
1244 echo $OUTPUT->footer();
1248 class generator_silent
extends generator
{
1252 function generator_generate_data($settings) {
1253 $generator = new generator($settings);
1254 $generator->do_generation
= true;
1255 $generator->generate_data();
1259 function get_new_filename($string) {
1263 function save_stored_file() {
1267 function get_data() {
1272 class generator_form
extends moodleform
{
1273 function definition() {
1274 global $generator, $CFG; //TODO: sloppy coding style!!
1276 $mform =& $this->_form
;
1277 $mform->addElement('hidden', 'do_generation', 1);
1278 $mform->setType('do_generation', PARAM_INT
);
1280 foreach ($generator->settings
as $setting) {
1281 $type = 'advcheckbox';
1283 $htmloptions = null;
1285 $label = ucfirst(str_replace('_', ' ', $setting->long
));
1286 if (!empty($setting->type
) && $setting->type
== 'mod1,mod2...') {
1288 $options = $generator->modules_list
;
1289 $htmloptions = array('multiple' => 'multiple');
1290 } elseif (!empty($setting->type
) && $setting->type
== 'SELECT') {
1293 foreach ($setting->default as $option) {
1294 $options[$option] = $option;
1296 $htmloptions = array('multiple' => 'multiple');
1297 } elseif (!empty($setting->type
)) {
1301 if ($setting->long
== 'password' ||
$setting->long
== 'username') {
1305 $mform->addElement($type, $setting->long
, $label, $options, $htmloptions);
1307 if (isset($setting->default)) {
1308 $mform->setDefault($setting->long
, $setting->default);
1311 $this->add_action_buttons(false, 'Generate data!');
1314 function definition_after_data() {
1320 $generator = new generator_cli($argv, $argc);
1321 $generator->generate_data();
1322 } elseif (strstr($_SERVER['REQUEST_URI'], 'generator.php')) {
1324 $systemcontext = get_context_instance(CONTEXT_SYSTEM
);
1325 require_capability('moodle/site:config', $systemcontext);
1327 $PAGE->set_url('/admin/generator.php');
1328 $PAGE->set_pagelayout('base');
1329 $generator = new generator_web();
1330 $generator->setup();
1331 $generator->display();
1332 $generator->generate_data();
1333 $generator->complete();
1335 $generator = new generator_silent();