source: moodle/trunk/fuentes/admin/tool/generator/classes/course_backend.php @ 136

Last change on this file since 136 was 136, checked in by mabarracus, 4 years ago

Ported code to xenial

File size: 18.9 KB
Line 
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/>.
16
17/**
18 * tool_generator course backend code.
19 *
20 * @package tool_generator
21 * @copyright 2013 The Open University
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27/**
28 * Backend code for the 'make large course' tool.
29 *
30 * @package tool_generator
31 * @copyright 2013 The Open University
32 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33 */
34class tool_generator_course_backend extends tool_generator_backend {
35    /**
36     * @var array Number of sections in course
37     */
38    private static $paramsections = array(1, 10, 100, 500, 1000, 2000);
39    /**
40     * @var array Number of assignments in course
41     */
42    private static $paramassignments = array(1, 10, 100, 500, 1000, 2000);
43    /**
44     * @var array Number of Page activities in course
45     */
46    private static $parampages = array(1, 50, 200, 1000, 5000, 10000);
47    /**
48     * @var array Number of students enrolled in course
49     */
50    private static $paramusers = array(1, 100, 1000, 10000, 50000, 100000);
51    /**
52     * Total size of small files: 1KB, 1MB, 10MB, 100MB, 1GB, 2GB.
53     *
54     * @var array Number of small files created in a single file activity
55     */
56    private static $paramsmallfilecount = array(1, 64, 128, 1024, 16384, 32768);
57    /**
58     * @var array Size of small files (to make the totals into nice numbers)
59     */
60    private static $paramsmallfilesize = array(1024, 16384, 81920, 102400, 65536, 65536);
61    /**
62     * Total size of big files: 8KB, 8MB, 80MB, 800MB, 8GB, 16GB.
63     *
64     * @var array Number of big files created as individual file activities
65     */
66    private static $parambigfilecount = array(1, 2, 5, 10, 10, 10);
67    /**
68     * @var array Size of each large file
69     */
70    private static $parambigfilesize = array(8192, 4194304, 16777216, 83886080,
71            858993459, 1717986918);
72    /**
73     * @var array Number of forum discussions
74     */
75    private static $paramforumdiscussions = array(1, 10, 100, 500, 1000, 2000);
76    /**
77     * @var array Number of forum posts per discussion
78     */
79    private static $paramforumposts = array(2, 2, 5, 10, 10, 10);
80
81    /**
82     * @var string Course shortname
83     */
84    private $shortname;
85
86    /**
87     * @var testing_data_generator Data generator
88     */
89    protected $generator;
90
91    /**
92     * @var stdClass Course object
93     */
94    private $course;
95
96    /**
97     * @var array Array from test user number (1...N) to userid in database
98     */
99    private $userids;
100
101    /**
102     * Constructs object ready to create course.
103     *
104     * @param string $shortname Course shortname
105     * @param int $size Size as numeric index
106     * @param bool $fixeddataset To use fixed or random data
107     * @param int|bool $filesizelimit The max number of bytes for a generated file
108     * @param bool $progress True if progress information should be displayed
109     */
110    public function __construct($shortname, $size, $fixeddataset = false, $filesizelimit = false, $progress = true) {
111
112        // Set parameters.
113        $this->shortname = $shortname;
114
115        parent::__construct($size, $fixeddataset, $filesizelimit, $progress);
116    }
117
118    /**
119     * Returns the relation between users and course sizes.
120     *
121     * @return array
122     */
123    public static function get_users_per_size() {
124        return self::$paramusers;
125    }
126
127    /**
128     * Gets a list of size choices supported by this backend.
129     *
130     * @return array List of size (int) => text description for display
131     */
132    public static function get_size_choices() {
133        $options = array();
134        for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
135            $options[$size] = get_string('coursesize_' . $size, 'tool_generator');
136        }
137        return $options;
138    }
139
140    /**
141     * Checks that a shortname is available (unused).
142     *
143     * @param string $shortname Proposed course shortname
144     * @return string An error message if the name is unavailable or '' if OK
145     */
146    public static function check_shortname_available($shortname) {
147        global $DB;
148        $fullname = $DB->get_field('course', 'fullname',
149                array('shortname' => $shortname), IGNORE_MISSING);
150        if ($fullname !== false) {
151            // I wanted to throw an exception here but it is not possible to
152            // use strings from moodle.php in exceptions, and I didn't want
153            // to duplicate the string in tool_generator, so I changed this to
154            // not use exceptions.
155            return get_string('shortnametaken', 'moodle', $fullname);
156        }
157        return '';
158    }
159
160    /**
161     * Runs the entire 'make' process.
162     *
163     * @return int Course id
164     */
165    public function make() {
166        global $DB, $CFG;
167        require_once($CFG->dirroot . '/lib/phpunit/classes/util.php');
168
169        raise_memory_limit(MEMORY_EXTRA);
170
171        if ($this->progress && !CLI_SCRIPT) {
172            echo html_writer::start_tag('ul');
173        }
174
175        $entirestart = microtime(true);
176
177        // Start transaction.
178        $transaction = $DB->start_delegated_transaction();
179
180        // Get generator.
181        $this->generator = phpunit_util::get_data_generator();
182
183        // Make course.
184        $this->course = $this->create_course();
185        $this->create_users();
186        $this->create_assignments();
187        $this->create_pages();
188        $this->create_small_files();
189        $this->create_big_files();
190        $this->create_forum();
191
192        // Log total time.
193        $this->log('coursecompleted', round(microtime(true) - $entirestart, 1));
194
195        if ($this->progress && !CLI_SCRIPT) {
196            echo html_writer::end_tag('ul');
197        }
198
199        // Commit transaction and finish.
200        $transaction->allow_commit();
201        return $this->course->id;
202    }
203
204    /**
205     * Creates the actual course.
206     *
207     * @return stdClass Course record
208     */
209    private function create_course() {
210        $this->log('createcourse', $this->shortname);
211        $courserecord = array('shortname' => $this->shortname,
212                'fullname' => get_string('fullname', 'tool_generator',
213                    array('size' => get_string('shortsize_' . $this->size, 'tool_generator'))),
214                'numsections' => self::$paramsections[$this->size]);
215        return $this->generator->create_course($courserecord, array('createsections' => true));
216    }
217
218    /**
219     * Creates a number of user accounts and enrols them on the course.
220     * Note: Existing user accounts that were created by this system are
221     * reused if available.
222     */
223    private function create_users() {
224        global $DB;
225
226        // Work out total number of users.
227        $count = self::$paramusers[$this->size];
228
229        // Get existing users in order. We will 'fill up holes' in this up to
230        // the required number.
231        $this->log('checkaccounts', $count);
232        $nextnumber = 1;
233        $rs = $DB->get_recordset_select('user', $DB->sql_like('username', '?'),
234                array('tool_generator_%'), 'username', 'id, username');
235        foreach ($rs as $rec) {
236            // Extract number from username.
237            $matches = array();
238            if (!preg_match('~^tool_generator_([0-9]{6})$~', $rec->username, $matches)) {
239                continue;
240            }
241            $number = (int)$matches[1];
242
243            // Create missing users in range up to this.
244            if ($number != $nextnumber) {
245                $this->create_user_accounts($nextnumber, min($number - 1, $count));
246            } else {
247                $this->userids[$number] = (int)$rec->id;
248            }
249
250            // Stop if we've got enough users.
251            $nextnumber = $number + 1;
252            if ($number >= $count) {
253                break;
254            }
255        }
256        $rs->close();
257
258        // Create users from end of existing range.
259        if ($nextnumber <= $count) {
260            $this->create_user_accounts($nextnumber, $count);
261        }
262
263        // Assign all users to course.
264        $this->log('enrol', $count, true);
265
266        $enrolplugin = enrol_get_plugin('manual');
267        $instances = enrol_get_instances($this->course->id, true);
268        foreach ($instances as $instance) {
269            if ($instance->enrol === 'manual') {
270                break;
271            }
272        }
273        if ($instance->enrol !== 'manual') {
274            throw new coding_exception('No manual enrol plugin in course');
275        }
276        $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
277
278        for ($number = 1; $number <= $count; $number++) {
279            // Enrol user.
280            $enrolplugin->enrol_user($instance, $this->userids[$number], $role->id);
281            $this->dot($number, $count);
282        }
283
284        // Sets the pointer at the beginning to be aware of the users we use.
285        reset($this->userids);
286
287        $this->end_log();
288    }
289
290    /**
291     * Creates user accounts with a numeric range.
292     *
293     * @param int $first Number of first user
294     * @param int $last Number of last user
295     */
296    private function create_user_accounts($first, $last) {
297        global $CFG;
298
299        $this->log('createaccounts', (object)array('from' => $first, 'to' => $last), true);
300        $count = $last - $first + 1;
301        $done = 0;
302        for ($number = $first; $number <= $last; $number++, $done++) {
303            // Work out username with 6-digit number.
304            $textnumber = (string)$number;
305            while (strlen($textnumber) < 6) {
306                $textnumber = '0' . $textnumber;
307            }
308            $username = 'tool_generator_' . $textnumber;
309
310            // Create user account.
311            $record = array('firstname' => get_string('firstname', 'tool_generator'),
312                    'lastname' => $number, 'username' => $username);
313
314            // We add a user password if it has been specified.
315            if (!empty($CFG->tool_generator_users_password)) {
316                $record['password'] = $CFG->tool_generator_users_password;
317            }
318
319            $user = $this->generator->create_user($record);
320            $this->userids[$number] = (int)$user->id;
321            $this->dot($done, $count);
322        }
323        $this->end_log();
324    }
325
326    /**
327     * Creates a number of Assignment activities.
328     */
329    private function create_assignments() {
330        // Set up generator.
331        $assigngenerator = $this->generator->get_plugin_generator('mod_assign');
332
333        // Create assignments.
334        $number = self::$paramassignments[$this->size];
335        $this->log('createassignments', $number, true);
336        for ($i = 0; $i < $number; $i++) {
337            $record = array('course' => $this->course);
338            $options = array('section' => $this->get_target_section());
339            $assigngenerator->create_instance($record, $options);
340            $this->dot($i, $number);
341        }
342
343        $this->end_log();
344    }
345
346    /**
347     * Creates a number of Page activities.
348     */
349    private function create_pages() {
350        // Set up generator.
351        $pagegenerator = $this->generator->get_plugin_generator('mod_page');
352
353        // Create pages.
354        $number = self::$parampages[$this->size];
355        $this->log('createpages', $number, true);
356        for ($i = 0; $i < $number; $i++) {
357            $record = array('course' => $this->course);
358            $options = array('section' => $this->get_target_section());
359            $pagegenerator->create_instance($record, $options);
360            $this->dot($i, $number);
361        }
362
363        $this->end_log();
364    }
365
366    /**
367     * Creates one resource activity with a lot of small files.
368     */
369    private function create_small_files() {
370        $count = self::$paramsmallfilecount[$this->size];
371        $this->log('createsmallfiles', $count, true);
372
373        // Create resource with default textfile only.
374        $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
375        $record = array('course' => $this->course,
376                'name' => get_string('smallfiles', 'tool_generator'));
377        $options = array('section' => 0);
378        $resource = $resourcegenerator->create_instance($record, $options);
379
380        // Add files.
381        $fs = get_file_storage();
382        $context = context_module::instance($resource->cmid);
383        $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
384                'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/');
385        for ($i = 0; $i < $count; $i++) {
386            $filerecord['filename'] = 'smallfile' . $i . '.dat';
387
388            // Generate random binary data (different for each file so it
389            // doesn't compress unrealistically).
390            $data = self::get_random_binary($this->limit_filesize(self::$paramsmallfilesize[$this->size]));
391
392            $fs->create_file_from_string($filerecord, $data);
393            $this->dot($i, $count);
394        }
395
396        $this->end_log();
397    }
398
399    /**
400     * Creates a string of random binary data. The start of the string includes
401     * the current time, in an attempt to avoid large-scale repetition.
402     *
403     * @param int $length Number of bytes
404     * @return Random data
405     */
406    private static function get_random_binary($length) {
407
408        $data = microtime(true);
409        if (strlen($data) > $length) {
410            // Use last digits of data.
411            return substr($data, -$length);
412        }
413        $length -= strlen($data);
414        for ($j = 0; $j < $length; $j++) {
415            $data .= chr(rand(1, 255));
416        }
417        return $data;
418    }
419
420    /**
421     * Creates a number of resource activities with one big file each.
422     */
423    private function create_big_files() {
424        global $CFG;
425
426        // Work out how many files and how many blocks to use (up to 64KB).
427        $count = self::$parambigfilecount[$this->size];
428        $filesize = $this->limit_filesize(self::$parambigfilesize[$this->size]);
429        $blocks = ceil($filesize / 65536);
430        $blocksize = floor($filesize / $blocks);
431
432        $this->log('createbigfiles', $count, true);
433
434        // Prepare temp area.
435        $tempfolder = make_temp_directory('tool_generator');
436        $tempfile = $tempfolder . '/' . rand();
437
438        // Create resources and files.
439        $fs = get_file_storage();
440        $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
441        for ($i = 0; $i < $count; $i++) {
442            // Create resource.
443            $record = array('course' => $this->course,
444                    'name' => get_string('bigfile', 'tool_generator', $i));
445            $options = array('section' => $this->get_target_section());
446            $resource = $resourcegenerator->create_instance($record, $options);
447
448            // Write file.
449            $handle = fopen($tempfile, 'w');
450            if (!$handle) {
451                throw new coding_exception('Failed to open temporary file');
452            }
453            for ($j = 0; $j < $blocks; $j++) {
454                $data = self::get_random_binary($blocksize);
455                fwrite($handle, $data);
456                $this->dot($i * $blocks + $j, $count * $blocks);
457            }
458            fclose($handle);
459
460            // Add file.
461            $context = context_module::instance($resource->cmid);
462            $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
463                    'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/',
464                    'filename' => 'bigfile' . $i . '.dat');
465            $fs->create_file_from_pathname($filerecord, $tempfile);
466        }
467
468        unlink($tempfile);
469        $this->end_log();
470    }
471
472    /**
473     * Creates one forum activity with a bunch of posts.
474     */
475    private function create_forum() {
476        global $DB;
477
478        $discussions = self::$paramforumdiscussions[$this->size];
479        $posts = self::$paramforumposts[$this->size];
480        $totalposts = $discussions * $posts;
481
482        $this->log('createforum', $totalposts, true);
483
484        // Create empty forum.
485        $forumgenerator = $this->generator->get_plugin_generator('mod_forum');
486        $record = array('course' => $this->course,
487                'name' => get_string('pluginname', 'forum'));
488        $options = array('section' => 0);
489        $forum = $forumgenerator->create_instance($record, $options);
490
491        // Add discussions and posts.
492        $sofar = 0;
493        for ($i = 0; $i < $discussions; $i++) {
494            $record = array('forum' => $forum->id, 'course' => $this->course->id,
495                    'userid' => $this->get_target_user());
496            $discussion = $forumgenerator->create_discussion($record);
497            $parentid = $DB->get_field('forum_posts', 'id', array('discussion' => $discussion->id), MUST_EXIST);
498            $sofar++;
499            for ($j = 0; $j < $posts - 1; $j++, $sofar++) {
500                $record = array('discussion' => $discussion->id,
501                        'userid' => $this->get_target_user(), 'parent' => $parentid);
502                $forumgenerator->create_post($record);
503                $this->dot($sofar, $totalposts);
504            }
505        }
506
507        $this->end_log();
508    }
509
510    /**
511     * Gets a section number.
512     *
513     * Depends on $this->fixeddataset.
514     *
515     * @return int A section number from 1 to the number of sections
516     */
517    private function get_target_section() {
518
519        if (!$this->fixeddataset) {
520            $key = rand(1, self::$paramsections[$this->size]);
521        } else {
522            // Using section 1.
523            $key = 1;
524        }
525
526        return $key;
527    }
528
529    /**
530     * Gets a user id.
531     *
532     * Depends on $this->fixeddataset.
533     *
534     * @return int A user id for a random created user
535     */
536    private function get_target_user() {
537
538        if (!$this->fixeddataset) {
539            $userid = $this->userids[rand(1, self::$paramusers[$this->size])];
540        } else if ($userid = current($this->userids)) {
541            // Moving pointer to the next user.
542            next($this->userids);
543        } else {
544            // Returning to the beginning if we reached the end.
545            $userid = reset($this->userids);
546        }
547
548        return $userid;
549    }
550
551    /**
552     * Restricts the binary file size if necessary
553     *
554     * @param int $length The total length
555     * @return int The limited length if a limit was specified.
556     */
557    private function limit_filesize($length) {
558
559        // Limit to $this->filesizelimit.
560        if (is_numeric($this->filesizelimit) && $length > $this->filesizelimit) {
561            $length = floor($this->filesizelimit);
562        }
563
564        return $length;
565    }
566
567}
Note: See TracBrowser for help on using the repository browser.