Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 
25
defined('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
 */
34
class 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 array Number of assignments in course
83
     */
84
    private static $paramactivities = array(1, 10, 100, 500, 1000, 2000);
85
    /**
86
     * @var string Course shortname
87
     */
88
    private $shortname;
89
 
90
    /**
91
     * @var string Course fullname.
92
     */
93
    private $fullname = "";
94
 
95
    /**
96
     * @var string Course summary.
97
     */
98
    private $summary = "";
99
 
100
    /**
101
     * @var string Course summary format, defaults to FORMAT_HTML.
102
     */
103
    private $summaryformat = FORMAT_HTML;
104
 
105
    /**
106
     * @var testing_data_generator Data generator
107
     */
108
    protected $generator;
109
 
110
    /**
111
     * @var stdClass Course object
112
     */
113
    private $course;
114
 
115
    /**
116
     * @var array Array from test user number (1...N) to userid in database
117
     */
118
    private $userids;
119
 
120
    /**
121
     * @var array $additionalmodules
122
     */
123
    private $additionalmodules;
124
    /**
125
     * Constructs object ready to create course.
126
     *
127
     * @param string $shortname Course shortname
128
     * @param int $size Size as numeric index
129
     * @param bool $fixeddataset To use fixed or random data
130
     * @param int|bool $filesizelimit The max number of bytes for a generated file
131
     * @param bool $progress True if progress information should be displayed
132
     * @param array $additionalmodules potential additional modules to be added (quiz, bigbluebutton...)
133
     */
134
    public function __construct(
135
        $shortname,
136
        $size,
137
        $fixeddataset = false,
138
        $filesizelimit = false,
139
        $progress = true,
140
        $fullname = null,
141
        $summary = null,
142
        $summaryformat = FORMAT_HTML,
143
        $additionalmodules = []
144
    ) {
145
 
146
        // Set parameters.
147
        $this->shortname = $shortname;
148
 
149
        // We can't allow fullname to be set to an empty string.
150
        if (empty($fullname)) {
151
            $this->fullname = get_string(
152
                'fullname',
153
                'tool_generator',
154
                array(
155
                    'size' => get_string('shortsize_' . $size, 'tool_generator')
156
                )
157
            );
158
        } else {
159
            $this->fullname = $fullname;
160
        }
161
 
162
        // Summary, on the other hand, should be empty-able.
163
        if (!is_null($summary)) {
164
            $this->summary = $summary;
165
            $this->summaryformat = $summaryformat;
166
        }
167
        $this->additionalmodules = $additionalmodules;
168
        parent::__construct($size, $fixeddataset, $filesizelimit, $progress);
169
    }
170
 
171
    /**
172
     * Returns the relation between users and course sizes.
173
     *
174
     * @return array
175
     */
176
    public static function get_users_per_size() {
177
        return self::$paramusers;
178
    }
179
 
180
    /**
181
     * Gets a list of size choices supported by this backend.
182
     *
183
     * @return array List of size (int) => text description for display
184
     */
185
    public static function get_size_choices() {
186
        $options = array();
187
        for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
188
            $options[$size] = get_string('coursesize_' . $size, 'tool_generator');
189
        }
190
        return $options;
191
    }
192
 
193
    /**
194
     * Checks that a shortname is available (unused).
195
     *
196
     * @param string $shortname Proposed course shortname
197
     * @return string An error message if the name is unavailable or '' if OK
198
     */
199
    public static function check_shortname_available($shortname) {
200
        global $DB;
201
        $fullname = $DB->get_field('course', 'fullname',
202
                array('shortname' => $shortname), IGNORE_MISSING);
203
        if ($fullname !== false) {
204
            // I wanted to throw an exception here but it is not possible to
205
            // use strings from moodle.php in exceptions, and I didn't want
206
            // to duplicate the string in tool_generator, so I changed this to
207
            // not use exceptions.
208
            return get_string('shortnametaken', 'moodle', $fullname);
209
        }
210
        return '';
211
    }
212
 
213
    /**
214
     * Runs the entire 'make' process.
215
     *
216
     * @return int Course id
217
     */
218
    public function make() {
219
        global $DB, $CFG, $USER;
220
        require_once($CFG->dirroot . '/lib/phpunit/classes/util.php');
221
 
222
        raise_memory_limit(MEMORY_EXTRA);
223
 
224
        if ($this->progress && !CLI_SCRIPT) {
225
            echo html_writer::start_tag('ul');
226
        }
227
 
228
        $entirestart = microtime(true);
229
 
230
        // Get generator.
231
        $this->generator = phpunit_util::get_data_generator();
232
 
233
        // Make course.
234
        $this->course = $this->create_course();
235
 
236
        $this->create_assignments();
237
        $this->create_pages();
238
        $this->create_small_files();
239
        $this->create_big_files();
240
 
241
        // Create users as late as possible to reduce regarding in the gradebook.
242
        $this->create_users();
243
        $this->create_forum();
244
 
245
        // Let plugins hook into user settings navigation.
246
        $pluginsfunction = get_plugins_with_function('course_backend_generator_create_activity');
247
        foreach ($pluginsfunction as $plugintype => $plugins) {
248
            foreach ($plugins as $pluginname => $pluginfunction) {
249
                if (in_array($pluginname, $this->additionalmodules)) {
250
                    $pluginfunction($this, $this->generator, $this->course->id, self::$paramactivities[$this->size]);
251
                }
252
            }
253
        }
254
 
255
        // We are checking 'enroladminnewcourse' setting to decide to enrol admins or not.
256
        if (!empty($CFG->creatornewroleid) && !empty($CFG->enroladminnewcourse) && is_siteadmin($USER->id)) {
257
            // Deal with course creators - enrol them internally with default role.
258
            enrol_try_internal_enrol($this->course->id, $USER->id, $CFG->creatornewroleid);
259
        }
260
 
261
        // Log total time.
262
        $this->log('coursecompleted', round(microtime(true) - $entirestart, 1));
263
        $this->end_log();
264
 
265
        if ($this->progress && !CLI_SCRIPT) {
266
            echo html_writer::end_tag('ul');
267
        }
268
 
269
        return $this->course->id;
270
    }
271
 
272
    /**
273
     * Creates the actual course.
274
     *
275
     * @return stdClass Course record
276
     */
277
    private function create_course() {
278
        $this->log('createcourse', $this->shortname);
279
        $courserecord = array(
280
            'shortname' => $this->shortname,
281
            'fullname' => $this->fullname,
282
            'numsections' => self::$paramsections[$this->size],
283
            'startdate' => usergetmidnight(time())
284
        );
285
        if (strlen($this->summary) > 0) {
286
            $courserecord['summary'] = $this->summary;
287
            $courserecord['summary_format'] = $this->summaryformat;
288
        }
289
 
290
        $return = $this->generator->create_course($courserecord, array('createsections' => true));
291
        $this->end_log();
292
        return $return;
293
    }
294
 
295
    /**
296
     * Creates a number of user accounts and enrols them on the course.
297
     * Note: Existing user accounts that were created by this system are
298
     * reused if available.
299
     */
300
    private function create_users() {
301
        global $DB;
302
 
303
        // Work out total number of users.
304
        $count = self::$paramusers[$this->size];
305
 
306
        // Get existing users in order. We will 'fill up holes' in this up to
307
        // the required number.
308
        $this->log('checkaccounts', $count);
309
        $this->end_log();
310
        $nextnumber = 1;
311
        $rs = $DB->get_recordset_select('user', $DB->sql_like('username', '?'),
312
                array('tool_generator_%'), 'username', 'id, username');
313
        foreach ($rs as $rec) {
314
            // Extract number from username.
315
            $matches = array();
316
            if (!preg_match('~^tool_generator_([0-9]{6})$~', $rec->username, $matches)) {
317
                continue;
318
            }
319
            $number = (int)$matches[1];
320
 
321
            // Create missing users in range up to this.
322
            if ($number != $nextnumber) {
323
                $this->create_user_accounts($nextnumber, min($number - 1, $count));
324
            } else {
325
                $this->userids[$number] = (int)$rec->id;
326
            }
327
 
328
            // Stop if we've got enough users.
329
            $nextnumber = $number + 1;
330
            if ($number >= $count) {
331
                break;
332
            }
333
        }
334
        $rs->close();
335
 
336
        // Create users from end of existing range.
337
        if ($nextnumber <= $count) {
338
            $this->create_user_accounts($nextnumber, $count);
339
        }
340
 
341
        // Assign all users to course.
342
        $this->log('enrol', $count, true);
343
 
344
        $enrolplugin = enrol_get_plugin('manual');
345
        $instances = enrol_get_instances($this->course->id, true);
346
        foreach ($instances as $instance) {
347
            if ($instance->enrol === 'manual') {
348
                break;
349
            }
350
        }
351
        if ($instance->enrol !== 'manual') {
352
            throw new coding_exception('No manual enrol plugin in course');
353
        }
354
        $role = $DB->get_record('role', array('shortname' => 'student'), '*', MUST_EXIST);
355
 
356
        for ($number = 1; $number <= $count; $number++) {
357
            // Enrol user.
358
            $enrolplugin->enrol_user($instance, $this->userids[$number], $role->id);
359
            $this->dot($number, $count);
360
        }
361
 
362
        // Sets the pointer at the beginning to be aware of the users we use.
363
        reset($this->userids);
364
 
365
        $this->end_log();
366
    }
367
 
368
    /**
369
     * Creates user accounts with a numeric range.
370
     *
371
     * @param int $first Number of first user
372
     * @param int $last Number of last user
373
     */
374
    private function create_user_accounts($first, $last) {
375
        global $CFG;
376
 
377
        $count = $last - $first + 1;
11 efrain 378
        $this->log('createusers', $count, true);
379
 
1 efrain 380
        $done = 0;
381
        for ($number = $first; $number <= $last; $number++, $done++) {
382
            // Work out username with 6-digit number.
383
            $textnumber = (string)$number;
384
            while (strlen($textnumber) < 6) {
385
                $textnumber = '0' . $textnumber;
386
            }
387
            $username = 'tool_generator_' . $textnumber;
388
 
389
            // Create user account.
390
            $record = array('username' => $username, 'idnumber' => $number);
391
 
392
            // We add a user password if it has been specified.
393
            if (!empty($CFG->tool_generator_users_password)) {
394
                $record['password'] = $CFG->tool_generator_users_password;
395
            }
396
 
397
            $user = $this->generator->create_user($record);
398
            $this->userids[$number] = (int)$user->id;
399
            $this->dot($done, $count);
400
        }
401
        $this->end_log();
402
    }
403
 
404
    /**
405
     * Creates a number of Assignment activities.
406
     */
407
    private function create_assignments() {
408
        // Set up generator.
409
        $assigngenerator = $this->generator->get_plugin_generator('mod_assign');
410
 
411
        // Create assignments.
412
        $number = self::$paramassignments[$this->size];
413
        $this->log('createassignments', $number, true);
414
        for ($i = 0; $i < $number; $i++) {
415
            $record = array('course' => $this->course);
416
            $options = array('section' => $this->get_target_section());
417
            $assigngenerator->create_instance($record, $options);
418
            $this->dot($i, $number);
419
        }
420
 
421
        $this->end_log();
422
    }
423
 
424
    /**
425
     * Creates a number of Page activities.
426
     */
427
    private function create_pages() {
428
        // Set up generator.
429
        $pagegenerator = $this->generator->get_plugin_generator('mod_page');
430
 
431
        // Create pages.
432
        $number = self::$parampages[$this->size];
433
        $this->log('createpages', $number, true);
434
        for ($i = 0; $i < $number; $i++) {
435
            $record = array('course' => $this->course);
436
            $options = array('section' => $this->get_target_section());
437
            $pagegenerator->create_instance($record, $options);
438
            $this->dot($i, $number);
439
        }
440
 
441
        $this->end_log();
442
    }
443
 
444
    /**
445
     * Creates one resource activity with a lot of small files.
446
     */
447
    private function create_small_files() {
448
        $count = self::$paramsmallfilecount[$this->size];
449
        $this->log('createsmallfiles', $count, true);
450
 
451
        // Create resource with default textfile only.
452
        $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
453
        $record = array('course' => $this->course,
454
                'name' => get_string('smallfiles', 'tool_generator'));
455
        $options = array('section' => 0);
456
        $resource = $resourcegenerator->create_instance($record, $options);
457
 
458
        // Add files.
459
        $fs = get_file_storage();
460
        $context = context_module::instance($resource->cmid);
461
        $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
462
                'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/');
463
        for ($i = 0; $i < $count; $i++) {
464
            $filerecord['filename'] = 'smallfile' . $i . '.dat';
465
 
466
            // Generate random binary data (different for each file so it
467
            // doesn't compress unrealistically).
468
            $data = random_bytes($this->limit_filesize(self::$paramsmallfilesize[$this->size]));
469
 
470
            $fs->create_file_from_string($filerecord, $data);
471
            $this->dot($i, $count);
472
        }
473
 
474
        $this->end_log();
475
    }
476
 
477
    /**
478
     * Creates a number of resource activities with one big file each.
479
     */
480
    private function create_big_files() {
481
        // Work out how many files and how many blocks to use (up to 64KB).
482
        $count = self::$parambigfilecount[$this->size];
483
        $filesize = $this->limit_filesize(self::$parambigfilesize[$this->size]);
484
        $blocks = ceil($filesize / 65536);
485
        $blocksize = floor($filesize / $blocks);
486
 
487
        $this->log('createbigfiles', $count, true);
488
 
489
        // Prepare temp area.
490
        $tempfolder = make_temp_directory('tool_generator');
491
        $tempfile = $tempfolder . '/' . rand();
492
 
493
        // Create resources and files.
494
        $fs = get_file_storage();
495
        $resourcegenerator = $this->generator->get_plugin_generator('mod_resource');
496
        for ($i = 0; $i < $count; $i++) {
497
            // Create resource.
498
            $record = array('course' => $this->course,
499
                    'name' => get_string('bigfile', 'tool_generator', $i));
500
            $options = array('section' => $this->get_target_section());
501
            $resource = $resourcegenerator->create_instance($record, $options);
502
 
503
            // Write file.
504
            $handle = fopen($tempfile, 'w');
505
            if (!$handle) {
506
                throw new coding_exception('Failed to open temporary file');
507
            }
508
            for ($j = 0; $j < $blocks; $j++) {
509
                $data = random_bytes($blocksize);
510
                fwrite($handle, $data);
511
                $this->dot($i * $blocks + $j, $count * $blocks);
512
            }
513
            fclose($handle);
514
 
515
            // Add file.
516
            $context = context_module::instance($resource->cmid);
517
            $filerecord = array('component' => 'mod_resource', 'filearea' => 'content',
518
                    'contextid' => $context->id, 'itemid' => 0, 'filepath' => '/',
519
                    'filename' => 'bigfile' . $i . '.dat');
520
            $fs->create_file_from_pathname($filerecord, $tempfile);
521
        }
522
 
523
        unlink($tempfile);
524
        $this->end_log();
525
    }
526
 
527
    /**
528
     * Creates one forum activity with a bunch of posts.
529
     */
530
    private function create_forum() {
531
        global $DB;
532
 
533
        $discussions = self::$paramforumdiscussions[$this->size];
534
        $posts = self::$paramforumposts[$this->size];
535
        $totalposts = $discussions * $posts;
536
 
537
        $this->log('createforum', $totalposts, true);
538
 
539
        // Create empty forum.
540
        $forumgenerator = $this->generator->get_plugin_generator('mod_forum');
541
        $record = array('course' => $this->course,
542
                'name' => get_string('pluginname', 'forum'));
543
        $options = array('section' => 0);
544
        $forum = $forumgenerator->create_instance($record, $options);
545
 
546
        // Add discussions and posts.
547
        $sofar = 0;
548
        for ($i = 0; $i < $discussions; $i++) {
549
            $record = array('forum' => $forum->id, 'course' => $this->course->id,
550
                    'userid' => $this->get_target_user());
551
            $discussion = $forumgenerator->create_discussion($record);
552
            $parentid = $DB->get_field('forum_posts', 'id', array('discussion' => $discussion->id), MUST_EXIST);
553
            $sofar++;
554
            for ($j = 0; $j < $posts - 1; $j++, $sofar++) {
555
                $record = array('discussion' => $discussion->id,
556
                        'userid' => $this->get_target_user(), 'parent' => $parentid);
557
                $forumgenerator->create_post($record);
558
                $this->dot($sofar, $totalposts);
559
            }
560
        }
561
 
562
        $this->end_log();
563
    }
564
 
565
    /**
566
     * Gets a section number.
567
     *
568
     * Depends on $this->fixeddataset.
569
     *
570
     * @return int A section number from 1 to the number of sections
571
     */
572
    public function get_target_section() {
573
 
574
        if (!$this->fixeddataset) {
575
            $key = rand(1, self::$paramsections[$this->size]);
576
        } else {
577
            // Using section 1.
578
            $key = 1;
579
        }
580
 
581
        return $key;
582
    }
583
 
584
    /**
585
     * Gets a user id.
586
     *
587
     * Depends on $this->fixeddataset.
588
     *
589
     * @return int A user id for a random created user
590
     */
591
    private function get_target_user() {
592
 
593
        if (!$this->fixeddataset) {
594
            $userid = $this->userids[rand(1, self::$paramusers[$this->size])];
595
        } else if ($userid = current($this->userids)) {
596
            // Moving pointer to the next user.
597
            next($this->userids);
598
        } else {
599
            // Returning to the beginning if we reached the end.
600
            $userid = reset($this->userids);
601
        }
602
 
603
        return $userid;
604
    }
605
 
606
    /**
607
     * Restricts the binary file size if necessary
608
     *
609
     * @param int $length The total length
610
     * @return int The limited length if a limit was specified.
611
     */
612
    private function limit_filesize($length) {
613
 
614
        // Limit to $this->filesizelimit.
615
        if (is_numeric($this->filesizelimit) && $length > $this->filesizelimit) {
616
            $length = floor($this->filesizelimit);
617
        }
618
 
619
        return $length;
620
    }
621
}