view.php 214 KB
Newer Older
Penny Leach's avatar
Penny Leach committed
1
2
3
4
5
<?php
/**
 *
 * @package    mahara
 * @subpackage core
6
 * @author     Catalyst IT Ltd
7
8
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
 * @copyright  For copyright information on Mahara, please see the README file distributed with this software.
Penny Leach's avatar
Penny Leach committed
9
10
11
12
13
14
15
 *
 */

defined('INTERNAL') || die();

class View {

16
17
18
19
20
    private $dirty;
    private $deleted;
    private $id;
    private $owner;
    private $ownerformat;
21
    private $group;
22
    private $institution;
23
24
25
    private $ctime;
    private $mtime;
    private $atime;
26
27
    private $startdate;
    private $stopdate;
28
29
    private $submittedgroup;
    private $submittedhost;
30
    private $submittedtime;
31
32
33
34
35
36
37
    private $title;
    private $description;
    private $loggedin;
    private $friendsonly;
    private $artefact_instances;
    private $artefact_metadata;
    private $ownerobj;
38
    private $groupobj;
39
    private $institutionobj;
40
41
42
    private $numcolumns; // now redundant
    private $columnsperrow; // assoc array of rows set and get using view_rows_columns db table
    private $numrows;
43
    private $layout;
Nigel McNie's avatar
Nigel McNie committed
44
    private $theme;
45
    private $rows;
46
    private $columns;
47
48
    private $dirtyrows; // for when we change stuff
    private $dirtycolumns; // now includes reference to row [row][column]
49
    private $tags;
50
    private $categorydata;
51
    private $template;
52
    private $retainview;
53
    private $copynewuser = 0;
54
    private $copynewgroups;
55
    private $type;
56
    private $visits;
57
    private $allowcomments;
58
    private $approvecomments;
59
    private $collection;
60
    private $accessconf;
61
    private $locked;
62
    private $urlid;
63
    private $skin;
Penny Leach's avatar
Penny Leach committed
64

65
66
67
68
69
70
71
72
73
74
75
76
    /**
     * Which view layout is considered the "default" for views with the given
     * number of columns. Must be present in $layouts of course.
     */
    public static $defaultcolumnlayouts = array(
            1 => '100',
            2 => '50,50',
            3 => '33,33,33',
            4 => '25,25,25,25',
            5 => '20,20,20,20,20',
    );

77
    /**
78
79
80
     * Valid view column layouts. These are read at install time and inserted into
     * view_layout_columns, but not updated afterwards, so if you're changing one
     * you'll need to do that manually.
81
82
83
     *
     * The key represents the number of columns, and the value is an array of all the
     * view_layout_columns records that have that number of columns
84
     */
85
    public static $basic_column_layouts = array(
86
87
88
89
90
91
92
93
94
95
96
        1 => array(
            '100',
        ),
        2 => array(
            '50,50',
            '67,33',
            '33,67',
        ),
        3 => array(
            '33,33,33',
            '25,50,25',
97
98
            '25,25,50',
            '50,25,25',
99
100
101
102
103
104
105
106
107
108
109
            '15,70,15',
        ),
        4 => array(
            '25,25,25,25',
            '20,30,30,20',
        ),
        5 => array(
            '20,20,20,20,20',
        ),
    );

110
111
112
113
114
115
    /**
     * The default layout options to be read at install time.
     * Each view_layout record is based on the array key and the count of its values.
     * Each view_layout_rows_columns record is based on the sub array.
     * For example:
     *  18 => array(
116
117
118
119
120
     *              1 => '100',
     *              2 => '50,50',
     *              3 => '100'
     *              'order' => 3
     *  ),
121
     * will insert a record in view_layout with id = 18 and rows = 3
122
123
124
125
     * and will insert 3 records in view_layout_rows_columns:
     *  - viewlayout = 18, rows = 1, columns = 1
     *  - viewlayout = 18, rows = 2, columns = 2
     *  - viewlayout = 18, rows = 3, columns = 1
126
     * And the "order" key indicates that this should be the 3rd option in the layout menu
127
128
129
     */
    public static $defaultlayoutoptions = array(
        1 => array(
130
131
                1 => '100',
                'order' => 1,
132
133
            ),
        2 => array(
134
135
                1 => '50,50',
                'order' => 2,
136
137
            ),
        3 => array(
138
139
                1 => '67,33',
                'order' => 3,
140
141
            ),
        4 => array(
142
143
                1 => '33,67',
                'order' => 4,
144
145
            ),
        5 => array(
146
147
                1 => '33,33,33',
                'order' => 5,
148
149
            ),
        6 => array(
150
151
                1 => '25,50,25',
                'order' => 6,
152
153
            ),
        7 => array(
154
                1 => '25,25,50'
155
156
            ),
        8 => array(
157
                1 => '50,25,25'
158
159
            ),
        9 => array(
160
                1 => '15,70,15'
161
162
            ),
        10 => array(
163
                1 => '25,25,25,25'
164
165
            ),
        11 => array(
166
                1 => '20,30,30,20'
167
168
            ),
        12 => array(
169
                1 => '20,20,20,20,20'
170
171
            ),
        13 => array(
172
173
                1 => '100',
                2 => '25,50,25'
174
175
            ),
        14 => array(
176
177
178
                1 => '100',
                2 => '33,67',
                'order' => 7
179
180
            ),
        15 => array(
181
182
                1 => '100',
                2 => '67,33'
183
184
            ),
        16 => array(
185
186
                1 => '100',
                2 => '50,50'
187
188
            ),
        17 => array(
189
190
191
                1 => '100',
                2 => '33,33,33',
                'order' => 8
192
193
            ),
        18 => array(
194
195
196
                1 => '100',
                2 => '50,50',
                3 => '100'
197
198
            ),
        19 => array(
199
200
201
202
                1 => '100',
                2 => '33,33,33',
                3 => '100',
                'order' => 9
203
204
            ),
        20 => array(
205
206
207
                1 => '100',
                2 => '25,50,25',
                3 => '100'
208
209
            ),
        21 => array(
210
211
212
213
                1 => '100',
                2 => '50,50',
                3 => '33,33,33',
                'order' => 10
214
215
216
217
218
219
220
221
222
223
224
            ),
    );

    public static $maxlayoutrows = 6; // see number of colours avail in layoutpreview.php

    /**
     * For retrieving and checking numbers of columnns in any given row
     * Initialised in constructor
     * An array of objects which represent each row in view_layout_columns
     */
    public static $layoutcolumns;
225

Penny Leach's avatar
Penny Leach committed
226
    public function __construct($id=0, $data=null) {
227
        global $USER;
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
        if (is_array($id) && isset($id['urlid']) && isset($id['ownerurlid'])) {
            $tempdata = get_record_sql('
                SELECT v.*
                FROM {view} v JOIN {usr} u ON v.owner = u.id
                WHERE v.urlid = ? AND u.urlid = ?',
                array($id['urlid'], $id['ownerurlid'])
            );
            if (empty($tempdata)) {
                throw new ViewNotFoundException(get_string('viewnotfoundbyname', 'error', $id['urlid'], $id['ownerurlid']));
            }
        }
        else if (is_array($id) && isset($id['urlid']) && isset($id['groupurlid'])) {
            $tempdata = get_record_sql('
                SELECT v.*
                FROM {view} v JOIN {group} g ON v.group = g.id
                WHERE v.urlid = ? AND g.urlid = ? AND g.deleted = 0',
                array($id['urlid'], $id['groupurlid'])
            );
            if (empty($tempdata)) {
                throw new ViewNotFoundException(get_string('viewnotfoundbyname', 'error', $id['urlid'], $id['groupurlid']));
            }
        }
        else if (!empty($id) && is_numeric($id)) {
251
252
253
254
255
256
            $tempdata = get_record_sql('
                SELECT v.*
                FROM {view} v LEFT JOIN {group} g ON v.group = g.id
                WHERE v.id = ? AND (v.group IS NULL OR g.deleted = 0)',
                array($id)
            );
257
            if (empty($tempdata)) {
258
259
                throw new ViewNotFoundException(get_string('viewnotfound', 'error', $id));
            }
260
261
        }
        if (isset($tempdata)) {
262
263
264
265
266
267
            if (!empty($data)) {
                $data = array_merge((array)$tempdata, $data);
            }
            else {
                $data = $tempdata; // use what the database has
            }
268
            $this->id = $tempdata->id;
Penny Leach's avatar
Penny Leach committed
269
270
271
        }
        else {
            $this->ctime = time();
272
            $this->mtime = time();
Penny Leach's avatar
Penny Leach committed
273
            $this->dirty = true;
Penny Leach's avatar
Penny Leach committed
274
275
        }

276
277
        $data = empty($data) ? array() : (array)$data;
        foreach ($data as $field => $value) {
Penny Leach's avatar
Penny Leach committed
278
279
280
281
            if (property_exists($this, $field)) {
                $this->{$field} = $value;
            }
        }
282

283
284
285
286
        if (empty(self::$layoutcolumns)) {
            self::$layoutcolumns = get_records_assoc('view_layout_columns', '', '', 'columns,id');
        }

287
288
289
290
291
292
293
294
295
296
297
        // Add in owner and group objects if we already happen to have them from view_search(), etc.
        if (isset($data['user']) && isset($data['user']->id) && $data['user']->id == $this->owner) {
            $this->ownerobj = $data['user'];
        }
        else if (isset($data['groupdata']->id) && $data['groupdata']->id == $this->group) {
            $this->groupobj = $data['groupdata'];
        }
        else if (!isset($data['user']) && !empty($this->owner) && $this->owner == $USER->get('id')) {
            $this->ownerobj = $USER;
        }

Penny Leach's avatar
Penny Leach committed
298
        $this->atime = time();
299
        $this->rows = array();
300
        $this->columns = array();
301
        $this->dirtyrows = array();
302
        $this->dirtycolumns = array();
303
304
305
306
307

        // set only for existing views - _create provides default value
        if (empty($this->columnsperrow)) {
            $this->columnsperrow = get_records_assoc('view_rows_columns', 'view', $this->get('id'), 'row', 'row, columns');
        }
Penny Leach's avatar
Penny Leach committed
308
309
    }

310
311
    /**
     * Creates a new View for the given user/group/institution.
312
313
     *
     * You can specify who the view is being created _by_ with the second
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
     * parameter. This defaults to the current logged in user's ID.
     *
     * @param array $viewdata See View::_create
     * @return View           The newly created View
     */
    public static function create($viewdata, $userid=null) {
        if (is_null($userid)) {
            global $USER;
            $userid = $USER->get('id');
        }

        $view = self::_create($viewdata, $userid);
        return $view;
    }

    /**
330
     * Creates a View for the given user, based off a given template and other
331
332
     * View information supplied.
     *
333
     * Will set a default title of 'Copy of $viewtitle' if title is not
334
     * specified in $viewdata and $titlefromtemplate == false.
335
336
337
     *
     * @param array $viewdata See View::_create
     * @param int $templateid The ID of the View to copy
338
     * @param int $userid     The user who has issued the command to create the
339
     *                        view. See View::_create
340
     * @param int $checkaccess Whether to check that the user can see the view before copying it
341
342
     * @return array A list consisting of the new view, the template view and
     *               information about the copy - i.e. how many blocks and
343
     *               artefacts were copied
344
     * @throws SystemException under various circumstances, see the source for
345
346
     *                         more information
     */
347
    public static function create_from_template($viewdata, $templateid, $userid=null, $checkaccess=true, $titlefromtemplate=false) {
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
        if (is_null($userid)) {
            global $USER;
            $userid = $USER->get('id');
        }

        $user = new User();
        $user->find_by_id($userid);

        db_begin();

        $template = new View($templateid);

        if ($template->get('deleted')) {
            throw new SystemException("View::create_from_template: This template has been deleted");
        }

364
        if ($checkaccess && !$template->get('template') && !$user->can_edit_view($template)) {
365
366
            throw new SystemException("View::create_from_template: Attempting to create a View from another View that is not marked as a template");
        }
367
        else if ($checkaccess && !can_view_view($templateid, $userid)) {
368
369
370
371
372
373
            throw new SystemException("View::create_from_template: User $userid is not permitted to copy View $templateid");
        }

        $view = self::_create($viewdata, $userid);

        // Set a default title if one wasn't set
374
375
376
377
        if ($titlefromtemplate) {
            $view->set('title', $template->get('title'));
        }
        else if (!isset($viewdata['title'])) {
378
379
380
381
382
            $desiredtitle = $template->get('title');
            if (get_config('renamecopies')) {
                $desiredtitle = get_string('Copyof', 'mahara', $desiredtitle);
            }
            $view->set('title', self::new_title($desiredtitle, (object)$viewdata));
383
384
            $view->set('dirty', true);
        }
385

386
387
388
389
        $view->urlid = generate_urlid($view->title, get_config('cleanurlviewdefault'), 3, 100);
        $viewdata['owner'] = $userid;
        $view->urlid = self::new_urlid($view->urlid, (object)$viewdata);

390
391
392
393
394
395
396
        try {
            $copystatus = $view->copy_contents($template);
        }
        catch (QuotaExceededException $e) {
            db_rollback();
            return array(null, $template, array('quotaexceeded' => true));
        }
397
398

        $view->commit();
399

400
401
402
403
404
        // if layout is set, and it's not a default layout
        // add an entry to usr_custom_layout if one does not already exist
        if ($template->get('layout') !== null) {
            $customlayout = get_record('view_layout', 'id', $template->get('layout'), 'iscustom', 1);
            if ($customlayout !== false) {
405
                // is the owner of the copy going to be a group or institution or not?
406
                $owner = $view->owner;
407
408
409
                $group = $view->group;
                $institution = $view->institution;
                $haslayout = false;
410

411
                if (!empty($group)) {
412
413
414
                    $owner = null;
                    $haslayout = get_record('usr_custom_layout', 'layout', $template->get('layout'), 'group', $group);
                }
415
416
417
418
                if (!empty($institution)) {
                    $owner = null;
                    $haslayout = get_record('usr_custom_layout', 'layout', $template->get('layout'), 'institution', $institution);
                }
419
420
421
422
423
                else if (isset($owner)) {
                    $haslayout = get_record('usr_custom_layout', 'layout', $template->get('layout'), 'usr', $owner);
                }

                if (!$haslayout) {
424
                    $newcustomlayout = insert_record('usr_custom_layout', (object) array('usr' => $owner, 'group' => $group, 'institution' => $institution, 'layout' => $template->get('layout')) );
425
426
427
428
                }
            }
        }

429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
        $blocks = get_records_array('block_instance', 'view', $view->get('id'));
        if ($blocks) {
            foreach ($blocks as $b) {
                $configdata = unserialize($b->configdata);
                if (!isset($configdata['artefactid'])) {
                    continue;
                }
                if (!isset($configdata['copytype']) || $configdata['copytype'] !== 'reference') {
                    continue;
                }
                $va = new StdClass;
                $va->view = $b->view;
                $va->artefact = $configdata['artefactid'];
                $va->block = $b->id;
                insert_record('view_artefact', $va);
            }
        }
446
447
448
449
450
451
452
453
454
455

        if ($template->get('retainview') && !$template->get('institution')) {
            $obj = new StdClass;
            $obj->view  = $view->get('id');
            $obj->ctime = db_format_timestamp(time());
            $obj->usr   = $template->get('owner');
            $obj->group = $template->get('group');
            insert_record('view_access', $obj);
        }

456
457
458
459
460
461
462
463
464
465
        db_commit();

        return array(
            $view,
            $template,
            $copystatus,
        );
    }

    /**
466
     * Creates a new View for the given user, based on the given information
467
468
     * about the view.
     *
469
     * Validation of the view data is performed, then the View is created. If
470
471
     * the View is to be owned by a group, that group is given access to it.
     *
472
     * @param array $viewdata Data about the view. You can pass in most fields
473
474
     *                        that appear in the view table.
     *
475
476
     *                        Note that you set who owns the View by setting
     *                        either the owner, group or institution field as
477
478
     *                        approriate.
     *
479
480
     *                        Currently, you cannot pass in access data. Use
     *                        $view->set_access() after retrieving the $view
481
482
     *                        object.
     *
483
484
     * @param int $userid The user who has issued the command to create the
     *                    View (note: this is different from the "owner" of the
485
486
487
     *                    View - a group or institution could be the "owner",
     *                    but it's a _user_ who requests a View is created for it)
     * @return View The created View
488
     * @throws SystemException if the View data is invalid - mostly this is due
489
490
491
     *                         to owner information being specified incorrectly.
     */
    private static function _create(&$viewdata, $userid) {
492
        // If no owner information is provided, assume that the view is being
493
494
495
496
497
498
499
500
501
502
503
504
505
506
        // created by the user for themself
        if (!isset($viewdata['owner']) && !isset($viewdata['group']) && !isset($viewdata['institution'])) {
            $viewdata['owner'] = $userid;
        }

        if (isset($viewdata['owner'])) {
            if ($viewdata['owner'] != $userid) {
                $userobj = new User();
                $userobj->find_by_id($userid);
                if (!$userobj->is_admin_for_user($viewdata['owner'])) {
                    throw new SystemException("View::_create: User $userid is not allowed to create a view for owner {$viewdata['owner']}");
                }
            }

507
            // Users can only have one view of each non-portfolio type
508
            if (isset($viewdata['type']) && $viewdata['type'] != 'portfolio' && get_record('view', 'owner', $viewdata['owner'], 'type', $viewdata['type'])) {
509
510
511
                $viewdata['type'] = 'portfolio';
            }

512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
            // Try to create the view with the owner's default theme if that theme is set by an
            // institution (i.e. if it's different from the site theme)
            //
            // This needs to be modified if users are ever allowed to change their own theme
            // preference.  Currently it's okay because users' themes are forced on them by
            // the site or institution default, but if some users are allowed to change their
            // own theme pref, we should create those users' views without a theme.
            if (!get_config('userscanchooseviewthemes') && !isset($viewdata['theme'])
                && (!isset($viewdata['type']) || $viewdata['type'] != 'dashboard')) {
                global $USER;
                if ($viewdata['owner'] == $USER->get('id')) {
                    $owner = $USER;
                }
                else {
                    $owner = new User();
                    $owner->find_by_id($viewdata['owner']);
                }
529
530
                $ownerthemedata = $owner->get('institutiontheme');
                $ownertheme = isset($ownerthemedata->basename) ? $ownerthemedata->basename : null;
531
                if ($ownertheme && $ownertheme != get_config('theme') && $ownertheme != 'custom') {
532
533
534
                    $viewdata['theme'] = $ownertheme;
                }
            }
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
        }

        if (isset($viewdata['group'])) {
            require_once('group.php');
            if (!group_user_can_edit_views($viewdata['group'], $userid)) {
                throw new SystemException("View::_create: User $userid is not permitted to create a view for group {$viewdata['group']}");
            }
        }

        if (isset($viewdata['institution'])) {
            $user = new User();
            $user->find_by_id($userid);
            if (!$user->can_edit_institution($viewdata['institution'])) {
                throw new SystemException("View::_create: User $userid is not permitted to create a view for institution {$viewdata['institution']}");
            }
        }

        // Create the view
        $defaultdata = array(
554
555
            'numcolumns'    => 2,
            'numrows'       => 1,
556
            'columnsperrow' => self::default_columnsperrow(),
557
558
559
            'template'      => 0,
            'type'          => 'portfolio',
            'title'         => (array_key_exists('title', $viewdata)) ? $viewdata['title'] : self::new_title(get_string('Untitled', 'view'), (object)$viewdata),
560
561
562
563
        );

        $data = (object)array_merge($defaultdata, $viewdata);

564
565
566
567
568
        if ($data->type == 'portfolio' && (!isset($data->url) || is_null($data->url) || !strlen($data->url))) {
            $data->urlid = generate_urlid($data->title, get_config('cleanurlviewdefault'), 3, 100);
            $data->urlid = self::new_urlid($data->urlid, $data);
        }

569
570
571
572
        $view = new View(0, $data);
        $view->commit();

        if (isset($viewdata['group'])) {
573
574
575
576
577
            require_once('activity.php');

            // Although group views are owned by the group, the view creator is treated as owner here.
            $beforeusers = activity_get_viewaccess_users($view->get('id'), $userid, 'viewaccess');

578
            // By default, group views should be visible to the group
579
580
581
            insert_record('view_access', (object) array(
                'view'  => $view->get('id'),
                'group' => $viewdata['group'],
582
                'ctime' => db_format_timestamp(time()),
583
            ));
584
585
586
587
588
589
590

            // Notify group members
            $accessdata = new StdClass;
            $accessdata->view = $view->get('id');
            $accessdata->owner = $userid;
            $accessdata->oldusers = $beforeusers;
            activity_occurred('viewaccess', $accessdata);
591
592
        }

593
594
595
596
597
598
599
600
601
602
603
        if (isset($viewdata['layout'])) {
            // e.g. importing via LEAP2A
            $layoutsrowscols = get_records_select_array('view_layout_rows_columns', 'viewlayout = ?', array($viewdata['layout']));
            if ($layoutsrowscols) {
                delete_records('view_rows_columns', 'view', $view->get('id'));
                foreach ($layoutsrowscols as $layoutrow) {
                    insert_record('view_rows_columns', (object)array( 'view' => $view->get('id'), 'row' => $layoutrow->row, 'columns' =>  self::$layoutcolumns[$layoutrow->columns]->columns));
                }
            }
        }

604
        return new View($view->get('id')); // Reread to ensure defaults are set
605
606
    }

607
608
609
610
611
612
613
614
    public function default_columnsperrow() {
        $default = array(1 => (object)array('row' => 1, 'columns' => 3, 'widths' => '33,33,33'));
        if (!$id = get_field('view_layout_columns', 'id', 'columns', $default[1]->columns, 'widths', $default[1]->widths)) {
            throw new SystemException("View::default_columnsperrow: Default columns = 3, widths = '33,33,33' not in view_layout_columns table");
        }
        return $default;
    }

Penny Leach's avatar
Penny Leach committed
615
616
617
618
    public function get($field) {
        if (!property_exists($this, $field)) {
            throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
        }
619
620
621
        if ($field == 'tags') { // special case
            return $this->get_tags();
        }
622
623
624
        if ($field == 'categorydata') {
            return $this->get_category_data();
        }
625
626
627
        if ($field == 'collection') {
            return $this->get_collection();
        }
628
629
630
        if ($field == 'columnsperrow') {
            return $this->get_columnsperrow();
        }
Penny Leach's avatar
Penny Leach committed
631
632
633
        return $this->{$field};
    }

634
635
636
637
638
639
640
641
642
643
644
645
646
    public function set($field, $value) {
        if (property_exists($this, $field)) {
            if ($this->{$field} != $value) {
                // only set it to dirty if it's changed
                $this->dirty = true;
            }
            $this->{$field} = $value;
            $this->mtime = time();
            return true;
        }
        throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
    }

647
648
649
650
651
652
653
    public function get_tags() {
        if (!isset($this->tags)) {
            $this->tags = get_column('view_tag', 'tag', 'view', $this->get('id'));
        }
        return $this->tags;
    }

654
655
656
    public function get_collection() {
        if (!isset($this->collection)) {
            require_once(get_config('libroot') . 'collection.php');
657
            $this->collection = Collection::search_by_view_id($this->id);
658
659
660
661
        }
        return $this->collection;
    }

662
663
664
665
666
667
668
    public function get_columnsperrow() {
        if (!isset($this->columnsperrow)) {
            $this->columnsperrow = get_records_assoc('view_rows_columns', 'view', $this->get('id'), 'row', 'row, columns');
        }
        return $this->columnsperrow;
    }

669
670
671
672
673
674
675
    public function collection_id() {
        if ($collection = $this->get_collection()) {
            return $collection->get('id');
        }
        return false;
    }

676
677
678
679
680
681
682
683
684
685
    /**
     * View destructor. Calls commit if necessary.
     *
     * A special case is when the object has just been deleted.  In this case,
     * we do nothing.
     */
    public function __destruct() {
        if ($this->deleted) {
            return;
        }
686

687
688
689
690
691
        if (!empty($this->dirty)) {
            return $this->commit();
        }
    }

692
    /**
693
694
695
696
697
698
699
700
701
     * This method updates the contents of the view table only.
     */
    public function commit() {
        if (empty($this->dirty)) {
            return;
        }
        $fordb = new StdClass;
        foreach (get_object_vars($this) as $k => $v) {
            $fordb->{$k} = $v;
702
            if (in_array($k, array('mtime', 'ctime', 'atime', 'startdate', 'stopdate', 'submittedtime')) && !empty($v)) {
703
704
705
                $fordb->{$k} = db_format_timestamp($v);
            }
        }
706
707
708

        db_begin();

709
        if (empty($this->id)) {
710
711
            // users are only allowed one profile view
            if ($this->type == 'profile' && record_exists('view', 'owner', $this->owner, 'type', 'profile')) {
712
                throw new SystemException(get_string('onlonlyyoneprofileviewallowed', 'error'));
713
            }
714
715
716
717
718
            $this->id = insert_record('view', $fordb, 'id', true);
        }
        else {
            update_record('view', $fordb, 'id');
        }
719

720
        if (isset($this->tags)) {
721
            $this->tags = check_case_sensitive($this->tags, 'view_tag');
722
723
            delete_records('view_tag', 'view', $this->get('id'));
            foreach ($this->get_tags() as $tag) {
724
725
                //truncate the tag before insert it into the database
                $tag = substr($tag, 0, 128);
726
727
                insert_record('view_tag', (object)array( 'view' => $this->get('id'), 'tag' => $tag));
            }
728
729
        }

730
731
732
733
734
735
736
        if (isset($this->copynewgroups)) {
            delete_records('view_autocreate_grouptype', 'view', $this->get('id'));
            foreach ($this->copynewgroups as $grouptype) {
                insert_record('view_autocreate_grouptype', (object)array( 'view' => $this->get('id'), 'grouptype' => $grouptype));
            }
        }

737
738
739
740
741
742
743
        if (isset($this->columnsperrow)) {
            delete_records('view_rows_columns', 'view', $this->get('id'));
            foreach ($this->get_columnsperrow() as $viewrow) {
                insert_record('view_rows_columns', (object)array( 'view' => $this->get('id'), 'row' => $viewrow->row, 'columns' => $viewrow->columns));
            }
        }

744
745
        db_commit();

746
747
748
749
        $this->dirty = false;
        $this->deleted = false;
    }

Penny Leach's avatar
Penny Leach committed
750
751
752
753
754
755
756
757
    public function get_artefact_instances() {
        if (!isset($this->artefact_instances)) {
            $this->artefact_instances = false;
            if ($instances = $this->get_artefact_metadata()) {
                foreach ($instances as $instance) {
                    safe_require('artefact', $instance->plugin);
                    $classname = generate_artefact_class_name($instance->artefacttype);
                    $i = new $classname($instance->id, $instance);
Penny Leach's avatar
Penny Leach committed
758
                    $this->childreninstances[] = $i;
Penny Leach's avatar
Penny Leach committed
759
760
761
762
763
764
765
                }
            }
        }
        return $this->artefact_instances;
    }

    public function get_artefact_metadata() {
Penny Leach's avatar
Penny Leach committed
766
        if (!isset($this->artefact_metadata)) {
767
            $sql = 'SELECT a.*, i.name, va.block
768
769
770
                    FROM {view_artefact} va
                    JOIN {artefact} a ON va.artefact = a.id
                    JOIN {artefact_installed_type} i ON a.artefacttype = i.name
Penny Leach's avatar
Penny Leach committed
771
                    WHERE va.view = ?';
772
            $this->artefact_metadata = get_records_sql_array($sql, array($this->id));
Penny Leach's avatar
Penny Leach committed
773
774
775
        }
        return $this->artefact_metadata;
    }
Penny Leach's avatar
Penny Leach committed
776

777
    public function find_artefact_children($artefact, $allchildren, &$refs) {
778

779
        $children = array();
780
781
782
783
784
785
786
787
        if ($allchildren) {
            foreach ($allchildren as $child) {
                if ($child->parent != $artefact->id) {
                    continue;
                }
                $children[$child->id] = array();
                $children[$child->id]['artefact'] = $child;
                $refs[$child->id] = $child;
788
                $children[$child->id]['children'] = $this->find_artefact_children($child,
789
                                                            $allchildren, $refs);
790
791
792
793
794
795
            }
        }

        return $children;
    }

Penny Leach's avatar
Penny Leach committed
796

Penny Leach's avatar
Penny Leach committed
797
798
799
800
801
802
    public function has_artefacts() {
        if ($this->get_artefact_metadata()) {
            return true;
        }
        return false;
    }
Penny Leach's avatar
Penny Leach committed
803
804

    public function get_owner_object() {
805
        if (empty($this->owner)) {
806
807
            return false;
        }
Penny Leach's avatar
Penny Leach committed
808
        if (!isset($this->ownerobj)) {
809
            $this->ownerobj = get_user_for_display($this->get('owner'));
Penny Leach's avatar
Penny Leach committed
810
811
812
813
        }
        return $this->ownerobj;
    }

814
815
816
817
818
819
820
    public function get_group_object() {
        if (!isset($this->groupobj)) {
            $this->groupobj = get_record('group', 'id', $this->get('group'));
        }
        return $this->groupobj;
    }

821
822
823
824
825
826
827
    public function get_institution_object() {
        if (!isset($this->institutionobj)) {
            $this->institutionobj = get_record('institution', 'name', $this->get('institution'));
        }
        return $this->institutionobj;
    }

828
    public function delete() {
829
        safe_require('artefact', 'comment');
830
        db_begin();
831
        ArtefactTypeComment::delete_view_comments($this->id);
832
        delete_records('view_access','view',$this->id);
833
        delete_records('view_autocreate_grouptype', 'view', $this->id);
834
        delete_records('view_tag','view',$this->id);
835
        delete_records('view_visit','view',$this->id);
836
        delete_records('collection_view','view',$this->id);
837
        delete_records('usr_watchlist_view','view',$this->id);
838
        if ($blockinstanceids = get_column('block_instance', 'id', 'view', $this->id)) {
839
            require_once(get_config('docroot') . 'blocktype/lib.php');
840
841
842
843
844
            foreach ($blockinstanceids as $id) {
                $bi = new BlockInstance($id);
                $bi->delete();
            }
        }
845
        handle_event('deleteview', $this->id);
846
        delete_records('view_rows_columns', 'view', $this->id);
847
        delete_records('view','id',$this->id);
848
849
850
851
852
        if (!empty($this->owner) && $this->is_submitted()) {
            // There should be no way to delete a submitted view,
            // but unlock its artefacts just in case.
            ArtefactType::update_locked($this->owner);
        }
853
        $this->deleted = true;
854
        db_commit();
855
856
    }

857
858
859
860
861
862
    /* Only retrieve access records that the owner can edit on the
     * view access page.  Some records are not visible there, such as
     * tutor access records for submitted views and objectionable
     * content access records (visible = 0) and token/secret url
     * records which are managed per-view, on another page.
     */
863
    public function get_access($timeformat=null) {
864
        if ($data = $this->get_access_records()) {
865
            return $this->process_access_records($data, $timeformat);
866
867
868
        }
        return array();
    }
869

870
    public function get_access_records() {
871
        $data = get_records_sql_array("
872
            SELECT accesstype, va.group, institution, role, usr, startdate, stopdate, allowcomments, approvecomments
873
            FROM {view_access} va
874
            WHERE view = ? AND visible = 1 AND token IS NULL
875
876
877
            ORDER BY
                accesstype IS NULL, accesstype DESC,
                va.group, role IS NOT NULL, role,
878
                institution, usr,
879
880
                startdate IS NOT NULL, startdate, stopdate IS NOT NULL, stopdate,
                allowcomments, approvecomments",
881
882
            array($this->id)
        );
883
884
        return $data ? $data : array();
    }
885

886
    public function process_access_records($data=array(), $timeformat=null) {
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
        $rolegroups = array();
        foreach ($data as &$item) {
            if ($item->role && !isset($roledata[$item->group])) {
                $rolegroups[$item->group] = 1;
            }
        }
        if ($rolegroups) {
            $grouptypes = get_records_sql_assoc('
                SELECT id, grouptype
                FROM {group}
                WHERE id IN (' . join(',', array_map('intval', array_keys($rolegroups))) . ')
                AND deleted = 0',
                array()
            );
        }
902

903
904
        foreach ($data as &$item) {
            $item = (array)$item;
Eugene Venter's avatar
Eugene Venter committed
905
            $item['locked'] = false; // Indicate if item is editable
906
907
908
909
910
911
912
913
            if ($item['usr']) {
                $item['type'] = 'user';
                $item['id'] = $item['usr'];
            }
            else if ($item['group']) {
                $item['type'] = 'group';
                $item['id'] = $item['group'];
            }
914
915
916
            else if ($item['institution']) {
                $item['type'] = 'institution';
                $item['id'] = $item['institution'];
917
918
919
920
921
922
923

                if ($this->type == 'profile') {
                    $myinstitutions = array_keys(load_user_institutions($this->owner));
                    if (in_array($item['id'], $myinstitutions) && empty($item['startdate']) && empty($item['stopdate'])) {
                        $item['locked'] = true;
                    }
                }
924
            }
925
926
927
928
929
            else {
                $item['type'] = $item['accesstype'];
                $item['id'] = null;
            }

930
931
932
933
            if ($this->type == 'profile' && $item['type'] == 'loggedin' && get_config('loggedinprofileviewaccess')) {
                $item['locked'] = true;
            }

934
935
936
937
938
939
            if ($item['role']) {
                $item['roledisplay'] = get_string($item['role'], 'grouptype.'.$grouptypes[$item['group']]->grouptype);
            }
            if ($timeformat) {
                if ($item['startdate']) {
                    $item['startdate'] = strftime($timeformat, strtotime($item['startdate']));
940
                }
941
942
                if ($item['stopdate']) {
                    $item['stopdate'] = strftime($timeformat, strtotime($item['stopdate']));
943
                }
944
            }
945
        }
946
947
948
        return $data;
    }

949
950
951
952
953
954
955
956
    /* Attempt to sort two access records in the same order as the
       query in get_access_records */
    public static function cmp_accesslist($a, $b) {
        if (($c = empty($a->accesstype) - empty($b->accesstype))
            || ($c = strcmp($b->accesstype, $a->accesstype))
            || ($c = $a->group - $b->group)
            || ($c = !empty($a->role) - !empty($b->role))
            || ($c = strcmp($a->role, $b->role))
957
958
            || ($c = !empty($a->institution) - !empty($b->institution))
            || ($c = strcmp($a->institution, $b->institution))
959
960
961
962
963
964
965
966
967
968
969
            || ($c = $a->usr - $b->usr)
            || ($c = !empty($a->startdate) - !empty($b->startdate))
            || ($c = strcmp($a->startdate, $b->startdate))
            || ($c = !empty($a->stopdate) - !empty($b->stopdate))
            || ($c = strcmp($a->stopdate, $b->stopdate))
            || ($c = $a->allowcomments - $b->allowcomments)) {
            return $c;
        }
        return $a->approvecomments - $b->approvecomments;
    }

970
971
972
973
974
    public static function update_view_access($config, $viewids) {

        db_begin();

        // Use set_access() on the first view to get a hopefully consistent
975
        // and complete representation of the access list
976
977
        $firstview = new View($viewids[0]);
        $fullaccesslist = $firstview->set_access($config['accesslist']);
978
979

        // Copy the first view's access records to all the other views
980
981
        $firstview->copy_access($viewids);

982
983
984
985
986
        // Sort the full access list in the same order as the list
        // returned by get_access, so that views with the same set of
        // access records get grouped together
        usort($fullaccesslist, array('self', 'cmp_accesslist'));

987
988
989
990
991
992
993
994
995
996
        // Hash the config object so later on we can easily find
        // all the views with the same config/access rights
        $config['accesslist'] = $fullaccesslist;
        $accessconf = substr(md5(serialize($config)), 0, 10);

        foreach ($viewids as $viewid) {
            $v = new View((int) $viewid);
            $v->set('startdate', $config['startdate']);
            $v->set('stopdate', $config['stopdate']);
            $v->set('template', $config['template']);
997
            $v->set('retainview', $config['retainview']);
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
            $v->set('allowcomments', $config['allowcomments']);
            $v->set('approvecomments', $config['approvecomments']);
            if (isset($config['copynewuser'])) {
                $v->set('copynewuser', $config['copynewuser']);
            }
            if (isset($config['copynewgroups'])) {
                $v->set('copynewgroups', $config['copynewgroups']);
            }
            $v->set('accessconf', $accessconf);
            $v->commit();
        }

        db_commit();
    }

1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
    /*Return preview image for creation of custom layout
     */
    public function updatecustomlayoutpreview($values) {
        global $THEME;
        require_once(get_config('libroot') . 'layoutpreviewimage.php');

        $require = array('numrows');
        foreach ($require as $require) {
            if (!array_key_exists($require, $values) || empty($values[$require])) {
                throw new ParamOutOfRangeException(get_string('missingparam' . $require, 'error'));
            }
        }

        $numrows = $values['numrows'];
        $collayouts = array();
        for ($i=0; $i<$numrows; $i++) {
            if (array_key_exists('row'. ($i+1), $values)) {
                $collayouts['row' . ($i+1)] = $values['row' . ($i+1)];
            }
        }

        $previewimage = 'vl-';
        $alttext = '';
        $customlayout = array();
        for ($i=0; $i<$numrows; $i++) {
            $id = $collayouts['row' . ($i+1)];
            $widths = get_field('view_layout_columns', 'widths', 'id', $id);
            $hyphenatedwidths = str_replace(',', '-', $widths);
            $customlayout[$i+1] = $hyphenatedwidths;
            $previewimage .= $hyphenatedwidths;
            $alttext .= $hyphenatedwidths;
            if ($i != $numrows -1) {
                $previewimage .= '_';
                $alttext .= ' / ';
            }
        }

        if (LayoutPreviewImage::preview_exists($previewimage)) {
            $img = get_config('wwwroot') . 'thumb.php?type=customviewlayout&cvl=' . $previewimage;
            $data = array('data' => $img, 'alttext' => $alttext, 'newimage' => 0);
            return $data;
        }
        else {
            // generate thumbnail images with GD
            $data= array();
            $data['layout'] = $customlayout;
            $data['description'] = 'test';
            $data['owner'] = 1;

            $previewlayoutimage = new LayoutPreviewImage($data);
            $newpreviewimage = $previewlayoutimage->create_preview();

            if ($newpreviewimage) {
                $img = get_config('wwwroot') . 'thumb.php?type=customviewlayout&cvl=' . $previewimage;
                $data = array('data' => $img, 'alttext' => $alttext, 'newimage' => 1);
                return $data;
            }
            else {
                $msg = '<p>' . get_string('previewimagegenerationfailed', 'error') . '</p>';
                $data = array('data' => $msg, 'alttext' => $alttext, 'newimage' => 0);
                return $data;
            }
        }
    }

    public function addcustomlayout($values) {
        global $THEME;
        $require = array('numrows');
        foreach ($require as $require) {
            if (!array_key_exists($require, $values) || empty($values[$require])) {
                throw new ParamOutOfRangeException(get_string('missingparam' . $require, 'error'));
            }
        }

        $numrows = $values['numrows'];
        $alttext = '';
        $rowscolssql = '';
        $rowscols = array();
        $resultids = array();
        $layoutid = 0;

        for ($i=0; $i<$numrows; $i++) {
            if (array_key_exists('row'. ($i+1), $values)) {
                $rowscolssql .= '(row = ' . ($i+1) . ' AND columns = ' . $values['row' . ($i+1)] . ')';
                if ($i != $numrows-1) {
                    $rowscolssql .= ' OR ';
                }
                $widths = get_field('view_layout_columns', 'widths', 'id', $values['row' . ($i+1)]);
                $hyphenatedwidths = str_replace(',', '-', $widths);
                $alttext .= $hyphenatedwidths;
                if ($i != $numrows -1) {
                    $alttext .= ' / ';
                }
                $rowscols[$i+1] = $values['row' . ($i+1)];
            }
        }

1110
        $owner = $this->owner;
1111
1112
1113
        $group = $this->group;
        $institution = $this->institution;
        if (!empty($group)) {
1114
1115
            $owner = null;
            $andclause = 'AND ucl.group = ?';
1116
1117
1118
1119
1120
1121
            $andclausevalue = $group;
        }
        else if (!empty($institution)) {
            $owner = null;
            $andclause = 'AND ucl.institution = ?';
            $andclausevalue = $institution;
1122
1123
1124
        }
        else if (isset($owner)) {
            $andclause = 'AND ucl.usr = ?';
1125
            $andclausevalue = $owner;
1126
1127
        }
        else {
1128
1129
1130
            // no group or owner or institution set
            // site pages should have institution set
            throw new SystemException("View::addcustomlayout: No owner, group or institution set for view.");
1131
1132
        }

1133
        // check for existing layout
1134
1135
1136
1137
        $sql = 'SELECT vlrc.viewlayout AS id
                FROM
                {view_layout} vl
                INNER JOIN {view_layout_rows_columns} vlrc
1138
                ON vl.id = vlrc.viewlayout
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
                INNER JOIN (
                    SELECT
                    viewlayout, COUNT(*)
                    FROM {view_layout_rows_columns}
                    GROUP BY viewlayout
                    HAVING COUNT(*) = ?
                    ) vlrc2
                ON vlrc.viewlayout = vlrc2.viewlayout
                INNER JOIN {usr_custom_layout} ucl
                ON ucl.layout = vl.id
                WHERE (' . $rowscolssql . ')
                AND (
                   vl.iscustom = 0
                   OR (
1153
                       vl.iscustom = 1 ' . $andclause . '
1154
1155
1156
1157
1158
                      )
                )
                GROUP BY vlrc.viewlayout
                HAVING count(*) = ?
                LIMIT 1';
1159
        $layoutids = get_records_sql_array($sql, array($numrows, $andclausevalue, $numrows));
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174

        if ($layoutids) {
            $data = array('layoutid' => $layoutids[0]->id, 'newlayout' => 0);
            return $data;
        }
        else {

            db_begin();
            // no existing layout of this kind, create it
            $newlayoutid = insert_record('view_layout', (object) array('rows' => $numrows, 'iscustom' => 1), 'id', true);
            if (!$newlayoutid) {
                db_rollback();
                throw new SystemException("View::addcustomlayout: Couldn't create new layout record.");
            }

1175
            if (isset($owner)) {
1176
                $newcustomlayout = insert_record('usr_custom_layout', (object) array('usr' => $owner, 'group' => null, 'institution' => null, 'layout' => $newlayoutid));
1177
1178
            }
            else if (isset($group)) {
1179
                $newcustomlayout = insert_record('usr_custom_layout', (object) array('usr' => null, 'group' => $group, 'institution' => null, 'layout' => $newlayoutid));
1180
            }
1181
1182
            else if (isset($institution)) {
                $newcustomlayout = insert_record('usr_custom_layout', (object) array('usr' => null, 'group' => null, 'institution' => $institution, 'layout' => $newlayoutid));
1183
            }
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
            if (!$newcustomlayout) {
                db_rollback();
                throw new SystemException("View::addcustomlayout: Couldn't create new usr custom layout record.");
            }

            for ($i=0; $i<$numrows; $i++) {
                if (array_key_exists(($i+1), $rowscols)) {
                    $numcols = get_field('view_layout_columns', 'columns', 'id', $rowscols[$i+1]);
                    $newrec