view.php 212 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 806 807
        if (is_null($this->owner)) {
            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