view.php 305 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
 *
 */

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

class View {
15 16 17 18 19
    private $dirty;
    private $deleted;
    private $id;
    private $owner;
    private $ownerformat;
20
    private $group;
21
    private $institution;
22 23 24
    private $ctime;
    private $mtime;
    private $atime;
25 26
    private $startdate;
    private $stopdate;
27 28
    private $submittedgroup;
    private $submittedhost;
29
    private $submittedtime;
30
    private $submittedstatus;
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
    private $numcolumns; // Obsolete - need to leave for upgrade purposes. This can be deleted once we no longer need to support direct upgrades from 15.10 and earlier.
41
    private $columnsperrow; // assoc array of rows set and get using view_rows_columns db table
42
    private $oldcolumnsperrow; // for when we change stuff
43
    private $numrows;
44
    private $layout;
Nigel McNie's avatar
Nigel McNie committed
45
    private $theme;
46
    private $rows;
47
    private $columns;
48
    private $tags;
49
    private $categorydata;
50
    private $template;
51
    private $retainview;
52
    private $copynewuser = 0;
53
    private $copynewgroups;
54
    private $type;
55
    private $visits;
56
    private $allowcomments;
57
    private $approvecomments;
58
    private $collection;
59
    private $locked;
60
    private $urlid;
61
    private $skin;
62
    private $anonymise = 0;
63
    private $lockblocks = 0;
64 65
    private $instructions;
    private $instructionscollapsed=0;
66
    private $newlayout = 1;
67
    private $grid;
68
    private $accessible = 0;
Penny Leach's avatar
Penny Leach committed
69

70 71 72 73
    const UNSUBMITTED = 0;
    const SUBMITTED = 1;
    const PENDING_RELEASE = 2;

74 75 76 77
    // constansts view templates
    const USER_TEMPLATE = 1;
    const SITE_TEMPLATE = 2;

78 79 80 81 82 83 84 85 86 87 88 89
    /**
     * 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',
    );

90
    /**
91 92 93
     * 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.
94 95 96
     *
     * 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
97
     */
98
    public static $basic_column_layouts = array(
99 100 101 102 103 104 105 106 107 108 109
        1 => array(
            '100',
        ),
        2 => array(
            '50,50',
            '67,33',
            '33,67',
        ),
        3 => array(
            '33,33,33',
            '25,50,25',
110 111
            '25,25,50',
            '50,25,25',
112 113 114 115 116 117 118 119 120 121 122
            '15,70,15',
        ),
        4 => array(
            '25,25,25,25',
            '20,30,30,20',
        ),
        5 => array(
            '20,20,20,20,20',
        ),
    );

123 124 125 126 127 128
    /**
     * 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(
129 130 131 132 133
     *              1 => '100',
     *              2 => '50,50',
     *              3 => '100'
     *              'order' => 3
     *  ),
134
     * will insert a record in view_layout with id = 18 and rows = 3
135 136 137 138
     * 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
139
     * And the "order" key indicates that this should be the 3rd option in the layout menu
140 141 142
     */
    public static $defaultlayoutoptions = array(
        1 => array(
143 144
                1 => '100',
                'order' => 1,
145 146
            ),
        2 => array(
147 148
                1 => '50,50',
                'order' => 2,
149 150
            ),
        3 => array(
151 152
                1 => '67,33',
                'order' => 3,
153 154
            ),
        4 => array(
155 156
                1 => '33,67',
                'order' => 4,
157 158
            ),
        5 => array(
159 160
                1 => '33,33,33',
                'order' => 5,
161 162
            ),
        6 => array(
163 164
                1 => '25,50,25',
                'order' => 6,
165 166
            ),
        7 => array(
167
                1 => '25,25,50'
168 169
            ),
        8 => array(
170
                1 => '50,25,25'
171 172
            ),
        9 => array(
173
                1 => '15,70,15'
174 175
            ),
        10 => array(
176
                1 => '25,25,25,25'
177 178
            ),
        11 => array(
179
                1 => '20,30,30,20'
180 181
            ),
        12 => array(
182
                1 => '20,20,20,20,20'
183 184
            ),
        13 => array(
185 186
                1 => '100',
                2 => '25,50,25'
187 188
            ),
        14 => array(
189 190 191
                1 => '100',
                2 => '33,67',
                'order' => 7
192 193
            ),
        15 => array(
194 195
                1 => '100',
                2 => '67,33'
196 197
            ),
        16 => array(
198 199
                1 => '100',
                2 => '50,50'
200 201
            ),
        17 => array(
202 203 204
                1 => '100',
                2 => '33,33,33',
                'order' => 8
205 206
            ),
        18 => array(
207 208 209
                1 => '100',
                2 => '50,50',
                3 => '100'
210 211
            ),
        19 => array(
212 213 214 215
                1 => '100',
                2 => '33,33,33',
                3 => '100',
                'order' => 9
216 217
            ),
        20 => array(
218 219 220
                1 => '100',
                2 => '25,50,25',
                3 => '100'
221 222
            ),
        21 => array(
223 224 225 226
                1 => '100',
                2 => '50,50',
                3 => '33,33,33',
                'order' => 10
227 228 229
            ),
    );

230
    public static $maxlayoutrows = 20;
231 232 233 234 235 236 237

    /**
     * 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;
238

Penny Leach's avatar
Penny Leach committed
239
    public function __construct($id=0, $data=null) {
240
        global $USER;
241 242 243 244 245
        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 = ?',
246 247
                array($id['urlid'], $id['ownerurlid']),
                ERROR_MULTIPLE
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
            );
            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)) {
265 266 267 268 269 270
            $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)
            );
271
            if (empty($tempdata)) {
272 273
                throw new ViewNotFoundException(get_string('viewnotfound', 'error', $id));
            }
274 275
        }
        if (isset($tempdata)) {
276 277 278 279 280 281
            if (!empty($data)) {
                $data = array_merge((array)$tempdata, $data);
            }
            else {
                $data = $tempdata; // use what the database has
            }
282
            $this->id = $tempdata->id;
Penny Leach's avatar
Penny Leach committed
283 284 285
        }
        else {
            $this->ctime = time();
286
            $this->mtime = time();
Penny Leach's avatar
Penny Leach committed
287
            $this->dirty = true;
Penny Leach's avatar
Penny Leach committed
288 289
        }

290 291
        $data = empty($data) ? array() : (array)$data;
        foreach ($data as $field => $value) {
Penny Leach's avatar
Penny Leach committed
292 293 294 295
            if (property_exists($this, $field)) {
                $this->{$field} = $value;
            }
        }
296

297
        if (empty(self::$layoutcolumns) && db_table_exists('view_layout_columns')) {
298 299 300
            self::$layoutcolumns = get_records_assoc('view_layout_columns', '', '', 'columns,id');
        }

301 302 303 304 305 306 307 308 309 310 311
        // 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
312
        $this->atime = time();
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353

        if ($this->newlayout) {
            $this->grid = array();
        }
        else {
            $this->rows = array();
            $this->columns = array();
            $this->oldcolumnsperrow = $this->get('columnsperrow');
            // set only for existing views - _create provides default value
            // Ignore if the constructor is called with deleted set to true
            if (empty($this->deleted)) {
                if ($this->columnsperrow === false || ($this->numrows > 0 && count($this->columnsperrow) != $this->numrows)) {
                    // if we are missing the info for some reason we will give the page it's layout back
                    // this can happen in MySQL when many users are copying the same page
                    if ($this->layout) {
                        if ($rowscols = get_records_sql_array("
                            SELECT vlrc.row, vlc.columns
                            FROM {view_layout_rows_columns} vlrc
                            JOIN {view_layout_columns} vlc ON vlc.id = vlrc.columns
                            WHERE viewlayout = ?", array($this->layout))) {
                                $default = array();
                                foreach ($rowscols as $row) {
                                    if ($this->get('id')) {
                                        $vrc = (object) array(
                                            'view' => $this->get('id'),
                                            'row' => $row->row,
                                            'columns' => $row->columns
                                        );
                                        ensure_record_exists('view_rows_columns', $vrc, $vrc);
                                    }
                                    $default[$row->row] = $row;
                                }
                        }
                    }
                    else if ($rowscols = get_records_sql_array("
                        SELECT vrc.row, vrc.columns
                        FROM {view} v
                        JOIN {view_rows_columns} vrc ON vrc.view = v.id
                        WHERE v.template = ?
                        AND v.type = ?", array(self::SITE_TEMPLATE, $this->type))) {
                            // Layout not specified so use the view type default layout
354 355
                            $default = array();
                            foreach ($rowscols as $row) {
356 357 358 359 360 361 362 363
                                if ($this->get('id')) {
                                    $vrc = (object) array(
                                        'view' => $this->get('id'),
                                        'row' => $row->row,
                                        'columns' => $row->columns
                                    );
                                    ensure_record_exists('view_rows_columns', $vrc, $vrc);
                                }
364 365 366
                                $default[$row->row] = $row;
                            }
                    }
367
                    $this->columnsperrow = $default;
368 369
                }
            }
370
        }
Penny Leach's avatar
Penny Leach committed
371 372
    }

373 374
    /**
     * Creates a new View for the given user/group/institution.
375 376
     *
     * You can specify who the view is being created _by_ with the second
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
     * 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;
    }

    /**
393
     * Creates a View for the given user, based off a given template and other
394 395
     * View information supplied.
     *
396
     * Will set a default title of 'Copy of $viewtitle' if title is not
397
     * specified in $viewdata and $titlefromtemplate == false.
398 399 400
     *
     * @param array $viewdata See View::_create
     * @param int $templateid The ID of the View to copy
401
     * @param int $userid     The user who has issued the command to create the
402
     *                        view. See View::_create
403
     * @param int $checkaccess Whether to check that the user can see the view before copying it
404 405
     * @param bool $titlefromtemplate Use the default title supplied by template
     * @param array $artefactcopies The mapping between old artefact ids and new ones (created in blockinstance copy)
406 407
     * @return array A list consisting of the new view, the template view and
     *               information about the copy - i.e. how many blocks and
408
     *               artefacts were copied
409
     * @throws SystemException under various circumstances, see the source for
410 411
     *                         more information
     */
412
    public static function create_from_template($viewdata, $templateid, $userid=null, $checkaccess=true, $titlefromtemplate=false, &$artefactcopies) {
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
        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");
        }

429
        if ($checkaccess && !$template->get('template') && !$user->can_edit_view($template)) {
430 431
            throw new SystemException("View::create_from_template: Attempting to create a View from another View that is not marked as a template");
        }
432
        else if ($checkaccess && !can_view_view($templateid, $userid)) {
433 434 435 436 437 438
            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
439 440 441
        if ($titlefromtemplate) {
            $view->set('title', $template->get('title'));
        }
442
        else if (!isset($viewdata['title'])
443
                && !($template->get('owner') === 0
444
                    && $template->get('type') == 'portfolio')) {
445 446 447 448 449
            $desiredtitle = $template->get('title');
            if (get_config('renamecopies')) {
                $desiredtitle = get_string('Copyof', 'mahara', $desiredtitle);
            }
            $view->set('title', self::new_title($desiredtitle, (object)$viewdata));
450 451
            $view->set('dirty', true);
        }
452

453 454 455 456
        $view->urlid = generate_urlid($view->title, get_config('cleanurlviewdefault'), 3, 100);
        $viewdata['owner'] = $userid;
        $view->urlid = self::new_urlid($view->urlid, (object)$viewdata);

457
        try {
458
            $copystatus = $view->copy_contents($template, $artefactcopies);
459 460 461 462 463
        }
        catch (QuotaExceededException $e) {
            db_rollback();
            return array(null, $template, array('quotaexceeded' => true));
        }
464

465 466 467
        // Lockblocks if set on template
        $view->set('lockblocks', $template->get('lockblocks'));

468
        $view->commit();
469 470 471 472

        $blocks = get_records_array('block_instance', 'view', $view->get('id'));
        if ($blocks) {
            foreach ($blocks as $b) {
473 474 475 476
                // As some artefact references have been changed, e.g embedded images
                // we need to rebuild the artefact list for each block
                $bi = new BlockInstance($b->id);
                $bi->rebuild_artefact_list();
477 478 479 480 481 482 483
                $configdata = unserialize($b->configdata);
                if (!isset($configdata['artefactid'])) {
                    continue;
                }
                if (!isset($configdata['copytype']) || $configdata['copytype'] !== 'reference') {
                    continue;
                }
484
                $va = new stdClass();
485 486 487 488 489 490
                $va->view = $b->view;
                $va->artefact = $configdata['artefactid'];
                $va->block = $b->id;
                insert_record('view_artefact', $va);
            }
        }
491 492

        if ($template->get('retainview') && !$template->get('institution')) {
493
            $obj = new stdClass();
494 495 496 497
            $obj->view  = $view->get('id');
            $obj->ctime = db_format_timestamp(time());
            $obj->usr   = $template->get('owner');
            $obj->group = $template->get('group');
498 499 500 501 502 503 504 505
            $vaid = insert_record('view_access', $obj, 'id', true);
            handle_event('updateviewaccess', array(
                'id' => $vaid,
                'eventfor' => (!empty($template->get('group')) ? 'group' : 'user'),
                'parentid' => $view->get('id'),
                'parenttype' => 'view',
                'rules' => $obj)
            );
506 507
        }

508 509 510 511 512 513 514 515 516
        db_commit();
        return array(
            $view,
            $template,
            $copystatus,
        );
    }

    /**
517
     * Creates a new View for the given user, based on the given information
518 519
     * about the view.
     *
520
     * Validation of the view data is performed, then the View is created. If
521 522
     * the View is to be owned by a group, that group is given access to it.
     *
523
     * @param array $viewdata Data about the view. You can pass in most fields
524 525
     *                        that appear in the view table.
     *
526 527
     *                        Note that you set who owns the View by setting
     *                        either the owner, group or institution field as
528 529
     *                        approriate.
     *
530 531
     *                        Currently, you cannot pass in access data. Use
     *                        $view->set_access() after retrieving the $view
532 533
     *                        object.
     *
534 535
     * @param int $userid The user who has issued the command to create the
     *                    View (note: this is different from the "owner" of the
536 537 538
     *                    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
539
     * @throws SystemException if the View data is invalid - mostly this is due
540 541 542
     *                         to owner information being specified incorrectly.
     */
    private static function _create(&$viewdata, $userid) {
543
        // If no owner information is provided, assume that the view is being
544 545 546 547 548 549 550 551 552 553 554 555 556 557
        // 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']}");
                }
            }

558
            // Users can only have one view of each non-portfolio type
559
            if (isset($viewdata['type']) && $viewdata['type'] != 'portfolio' && get_record('view', 'owner', $viewdata['owner'], 'type', $viewdata['type'])) {
560 561
                $viewdata['type'] = 'portfolio';
            }
562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
        }

        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(
581 582 583 584
            'numrows'       => 1,
            'template'      => 0,
            'type'          => 'portfolio',
            'title'         => (array_key_exists('title', $viewdata)) ? $viewdata['title'] : self::new_title(get_string('Untitled', 'view'), (object)$viewdata),
585
            'anonymise'     => 0,
586
            'lockblocks'    => 0,
587 588 589 590
        );

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

591 592 593 594 595
        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);
        }

596 597
        $view = new View(0, $data);
        $view->commit();
598

599 600 601
        if (isset($viewdata['group']) &&
            (empty($viewdata['type']) || (!empty($viewdata['type']) && $viewdata['type'] != 'grouphomepage'))
           ) {
602 603 604
            require_once('activity.php');

            // Although group views are owned by the group, the view creator is treated as owner here.
605 606
            // So we need to ignore them from the activity_occured email.
            $beforeusers[$userid] = get_record('usr', 'id', $userid);
607

608
            // By default, group views should be visible to the group
609
            $newaccess = (object) array(
610 611
                'view'  => $view->get('id'),
                'group' => $viewdata['group'],
612
                'ctime' => db_format_timestamp(time()),
613
            );
614 615 616 617 618 619 620 621
            $vaid = insert_record('view_access', $newaccess, 'id', true);
            handle_event('updateviewaccess', array(
                'id' => $vaid,
                'eventfor' => 'group',
                'parentid' => $view->get('id'),
                'parenttype' => 'view',
                'rules' => $newaccess)
            );
622
            // Notify group members
623
            $accessdata = new stdClass();
624
            $accessdata->view = $view->get('id');
625 626
            $accessdata->oldusers = $beforeusers;
            activity_occurred('viewaccess', $accessdata);
627 628
        }

629
        return new View($view->get('id')); // Reread to ensure defaults are set
630 631
    }

632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
    /*
     * Returns the content we used to have in the view_layout_columns table
     * we need it to import Leap2a postfolios with old layout
     */
    public static function get_old_view_layout_columns() {
        $layout = new stdClass();
        $view_layout_columns = array();
        $id = 0;
        foreach (self::$basic_column_layouts as $column => $widths) {
            foreach ($widths as $width) {
                $id++;
                $layout = new stdClass();
                $layout->columns = $column;
                $layout->widths = $width;
                $layout->id = $id;
                $view_layout_columns[] = $layout;
            }
        }
        return $view_layout_columns;
651 652
    }

653

Penny Leach's avatar
Penny Leach committed
654 655 656 657
    public function get($field) {
        if (!property_exists($this, $field)) {
            throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
        }
658 659 660
        if ($field == 'tags') { // special case
            return $this->get_tags();
        }
661 662 663
        if ($field == 'categorydata') {
            return $this->get_category_data();
        }
664 665 666
        if ($field == 'collection') {
            return $this->get_collection();
        }
667 668 669
        if ($field == 'columnsperrow') {
            return $this->get_columnsperrow();
        }
Penny Leach's avatar
Penny Leach committed
670 671 672
        return $this->{$field};
    }

673 674 675 676 677 678 679
    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;
680 681 682 683 684
            if ($field != 'atime') {
                // don't bother updating the modified time if we are
                // only wanting to update the accessed time
                $this->mtime = time();
            }
685 686 687 688 689
            return true;
        }
        throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
    }

690 691
    public function get_tags() {
        if (!isset($this->tags)) {
692 693 694 695 696 697 698 699 700 701 702 703
            $typecast = is_postgres() ? '::varchar' : '';
            $this->tags = get_column_sql("
            SELECT
                (CASE
                    WHEN t.tag LIKE 'tagid_%' THEN CONCAT(i.displayname, ': ', t2.tag)
                    ELSE t.tag
                END) AS tag
            FROM {tag} t
            LEFT JOIN {tag} t2 ON t2.id" . $typecast . " = SUBSTRING(t.tag, 7)
            LEFT JOIN {institution} i ON i.name = t2.ownerid
            WHERE t.resourcetype = ? AND t.resourceid = ?
            ORDER BY tag", array('view', $this->get('id')));
704 705 706 707
        }
        return $this->tags;
    }

708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765
    public function get_all_tags_for_view($limit = null) {
        $count = 0;
        $alltags = array();

        $artefactids = get_column_sql("
            SELECT artefact
            FROM {view_artefact}
            WHERE view = ?
            UNION
            SELECT id AS artefact
            FROM {artefact}
            WHERE parent IN (
                SELECT artefact
                FROM {view_artefact}
                WHERE view = ?)", array($this->id, $this->id));
        $blockids = get_column('block_instance', 'id', 'view', $this->id);
        $typecast = is_postgres() ? '::varchar' : '';
        $alltags = get_column_sql("
            SELECT (
                CASE
                   WHEN t.tag LIKE 'tagid_%' THEN CONCAT(i.displayname, ': ', t2.tag)
                   ELSE t.tag
                END) AS tag
            FROM {tag} t
            LEFT JOIN {tag} t2 ON t2.id" . $typecast . " = SUBSTRING(t.tag, 7)
            LEFT JOIN {institution} i ON i.name = t2.ownerid
            WHERE t.resourcetype = ? AND t.resourceid = ?
            UNION
            SELECT (
                CASE
                   WHEN t.tag LIKE 'tagid_%' THEN CONCAT(i.displayname, ': ', t2.tag)
                   ELSE t.tag
                END) AS tag
            FROM {tag} t
            LEFT JOIN {tag} t2 ON t2.id" . $typecast . " = SUBSTRING(t.tag, 7)
            LEFT JOIN {institution} i ON i.name = t2.ownerid
            WHERE t.resourcetype = ? AND t.resourceid IN ('" . join("','", $blockids) . "')
            GROUP BY 1
            UNION
            SELECT (
                CASE
                   WHEN t.tag LIKE 'tagid_%' THEN CONCAT(i.displayname, ': ', t2.tag)
                   ELSE t.tag
                END) AS tag
            FROM {tag} t
            LEFT JOIN {tag} t2 ON t2.id" . $typecast . " = SUBSTRING(t.tag, 7)
            LEFT JOIN {institution} i ON i.name = t2.ownerid
            WHERE t.resourcetype = ? AND t.resourceid IN ('" . join("','", $artefactids) . "')
            GROUP BY 1
            ORDER BY tag", array('view', $this->id, 'blocktype', 'artefact'));

        $count = sizeof($alltags);
        if ($limit && $count > $limit) {
            $alltags = array_slice($alltags, 0, $limit);
        }
        return array($count, $alltags);
    }

766 767 768
    public function get_collection() {
        if (!isset($this->collection)) {
            require_once(get_config('libroot') . 'collection.php');
769
            $this->collection = Collection::search_by_view_id($this->id);
770 771 772 773
        }
        return $this->collection;
    }

774 775
    public function get_columnsperrow() {
        if (!isset($this->columnsperrow)) {
776 777 778 779
            $this->columnsperrow = get_records_sql_assoc('SELECT "row", columns
                                                          FROM {view_rows_columns}
                                                          WHERE view = ?
                                                          ORDER BY "row" ASC', array($this->get('id')));
780 781 782 783
        }
        return $this->columnsperrow;
    }

784 785 786 787 788 789 790
    public function collection_id() {
        if ($collection = $this->get_collection()) {
            return $collection->get('id');
        }
        return false;
    }

791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
    public function get_group_id_of_corresponding_group_task() {
        $collection = $this->get_collection();
        if ($collection) {
            $portfolioelement = $collection;
        }
        else {
            $portfolioelement = $this;
        }
        $portfolioelementtype = strtolower(get_class($portfolioelement));
        $portfolioelementid = $portfolioelement->get('id');

        $sql = 'SELECT * FROM {artefact} AS a '.
            'INNER JOIN {artefact_plans_task} AS gt ON gt.artefact = a.id '.
            'INNER JOIN {artefact_plans_task} AS ut ON ut.rootgrouptask = gt.artefact '.
            'WHERE ut.outcometype = ? AND ut.outcome = ?';

        $result = get_record_sql($sql, [$portfolioelementtype, $portfolioelementid]);

        if ($result && $result->group) {
            return $result->group;
        }
        return false;
    }

815 816 817 818 819 820 821 822 823 824
    /**
     * 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;
        }
825

826 827 828 829 830
        if (!empty($this->dirty)) {
            return $this->commit();
        }
    }

831
    /**
832 833 834
     * This method updates the contents of the view table only.
     */
    public function commit() {
835 836
        global $USER;

837 838 839
        if (empty($this->dirty)) {
            return;
        }
840
        $fordb = new stdClass();
841 842
        foreach (get_object_vars($this) as $k => $v) {
            $fordb->{$k} = $v;
843
            if (in_array($k, array('mtime', 'ctime', 'atime', 'startdate', 'stopdate', 'submittedtime')) && !empty($v)) {
844 845 846
                $fordb->{$k} = db_format_timestamp($v);
            }
        }
847 848

        db_begin();
849
        if (empty($this->id)) {
850
            // users are only allowed one profile view
851
            if (!$this->template && $this->type == 'profile' && record_exists('view', 'owner', $this->owner, 'type', 'profile')) {
852
                throw new SystemException(get_string('onlonlyyoneprofileviewallowed', 'error'));
853
            }
854
            $this->id = insert_record('view', $fordb, 'id', true);
855
            handle_event('createview', array('id' => $this->id, 'eventfor' => 'view', 'viewtype' => $this->type));
856 857 858
        }
        else {
            update_record('view', $fordb, 'id');
859
            handle_event('saveview', array('id' => $this->id, 'eventfor' => 'view', 'viewtype' => $this->type));
860
        }
861

862
        if (isset($this->tags)) {
863 864 865 866 867 868 869 870 871 872 873 874 875 876
            if ($this->group) {
                $ownertype = 'group';
                $ownerid = $this->group;
            }
            else if ($this->institution) {
                $ownertype = 'institution';
                $ownerid = $this->institution;
            }
            else {
                $ownertype = 'user';
                $ownerid = $this->owner;
            }
            $this->tags = check_case_sensitive($this->tags, 'tag');
            delete_records('tag', 'resourcetype', 'view', 'resourceid', $this->get('id'));
877
            foreach ($this->get_tags() as $tag) {
878 879
                //truncate the tag before insert it into the database
                $tag = substr($tag, 0, 128);
880
                $tag = check_if_institution_tag($tag);
881 882 883 884 885 886 887 888 889 890 891
                insert_record('tag',
                    (object)array(
                        'resourcetype' => 'view',
                        'resourceid' => $this->get('id'),
                        'ownertype' => $ownertype,
                        'ownerid' => $ownerid,
                        'tag' => $tag,
                        'ctime' => db_format_timestamp(time()),
                        'editedby' => $USER->get('id'),
                    )
                );
892
            }
893 894
        }

895 896 897 898 899 900 901
        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));
            }
        }

902 903
        db_commit();

904 905 906 907
        $this->dirty = false;
        $this->deleted = false;
    }

908 909 910 911 912
    /**
     * Returns an array of all the artefacts on this page.
     *
     * @return array
     */
Penny Leach's avatar
Penny Leach committed
913
    public function get_artefact_instances() {
914
        $this->artefact_instances = array();
Penny Leach's avatar
Penny Leach committed
915

916 917 918 919 920 921
        $sql = 'SELECT a.*, i.name, i.plugin, va.block
                FROM {view_artefact} va
                JOIN {artefact} a ON va.artefact = a.id
                JOIN {artefact_installed_type} i ON a.artefacttype = i.name
                WHERE va.view = ?';
        $this->artefact_metadata = get_records_sql_array($sql, array($this->id));
Penny Leach's avatar
Penny Leach committed
922

923 924 925 926 927 928
        if ($instances = $this->artefact_metadata) {
            foreach ($instances as $instance) {
                safe_require('artefact', $instance->plugin);
                $classname = generate_artefact_class_name($instance->artefacttype);
                $i = new $classname($instance->id, $instance);
                $this->artefact_instances[] = $i;
929 930
            }
        }
931
        return $this->artefact_instances;
Penny Leach's avatar
Penny Leach committed
932
    }
Penny Leach's avatar
Penny Leach committed
933 934

    public function get_owner_object() {
935
        if (empty($this->owner)) {
936 937
            return false;
        }
Penny Leach's avatar
Penny Leach committed
938
        if (!isset($this->ownerobj)) {
939 940 941 942
            // $this->ownerobj = get_user_for_display($this->get('owner'));
            $user = new User();
            $user->find_by_id($this->get('owner'));
            $this->ownerobj = $user;
Penny Leach's avatar
Penny Leach committed
943 944 945 946
        }
        return $this->ownerobj;
    }

947 948
    public function get_group_object() {
        if (!isset($this->groupobj)) {
949
            $this->groupobj = get_group_by_id($this->get('group'), true);
950 951 952 953
        }
        return $this->groupobj;
    }

954 955 956 957 958 959 960
    public function get_institution_object() {
        if (!isset($this->institutionobj)) {
            $this->institutionobj = get_record('institution', 'name', $this->get('institution'));
        }
        return $this->institutionobj;
    }

961
    public function delete() {
962
        safe_require('artefact', 'comment');
963
        db_begin();
964
        ArtefactTypeComment::delete_view_comments($this->id);
965
        delete_records('view_access','view',$this->id);
966
        delete_records('view_autocreate_grouptype', 'view', $this->id);
967
        delete_records('tag', 'resourcetype', 'view', 'resourceid', $this->id);
968
        delete_records('view_visit','view',$this->id);
969
        delete_records('view_versioning', 'view', $this->id);
970
        delete_records('existingcopy', 'view', $this->id);
971
        $eventdata = array('id' => $this->id, 'eventfor' => 'view');
972
        if ($collection = $this->get_collection()) {
973
            $eventdata['collection'] = $collection->get('id');
974 975
            $collection->remove_view($this->id);
        }
976
        delete_records('usr_watchlist_view','view',$this->id);
977 978
        //remove lock blocks, if they exist for this page
        set_field('view', 'lockblocks', 0, 'id', $this->id, 'lockblocks', 1);
979
        if ($blockinstanceids = get_column('block_instance', 'id', 'view', $this->id)) {
980
            require_once(get_config('docroot') . 'blocktype/lib.php');
981 982 983 984 985
            foreach ($blockinstanceids as $id) {
                $bi = new BlockInstance($id);
                $bi->delete();
            }
        }
986 987 988 989 990 991 992 993 994 995
        // Check if this view is being used as the custom landing page
        if (get_config('homepageredirect') && !empty(get_config('homepageredirecturl'))) {
            $landing = translate_landingpage_to_tags(array(get_config('homepageredirecturl')));
            foreach ($landing as $land) {
                if ($land->type == 'view' && $land->typeid == $this->id) {
                    set_config('homepageredirecturl', null);
                    notify_landing_removed($land, true);
                }
            }
        }
996 997
        // Delete any submission history
        delete_records('module_assessmentreport_history', 'event', 'view', 'itemid', $this->id);
998

999
        handle_event('deleteview', $eventdata);
1000
        delete_records('view_rows_columns', 'view', $this->id);
1001 1002 1003
        if (is_plugin_active('lti', 'module')) {
            delete_records('lti_assessment_submission', 'viewid', $this->id);
        }
1004
        delete_records('view','id',$this->id);
1005 1006 1007 1008 1009
        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);
        }
1010 1011
        require_once('embeddedimage.php');
        EmbeddedImage::delete_embedded_images('description', $this->id);
1012
        EmbeddedImage::delete_embedded_images('instructions', $this->id);
1013
        $this->deleted = true;
1014
        db_commit();
1015 1016
    }

1017 1018 1019 1020 1021 1022
    /* 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.
     */
1023
    public function get_access($timeformat=null) {
1024
        if ($data = $this->get_access_records()) {
1025
            return $this->process_access_records($data, $timeformat);
1026 1027 1028
        }
        return array();
    }
1029

1030
    public function get_access_records() {
1031
        $data = get_records_sql_array("
1032
            SELECT accesstype, va.group, institution, role, usr, startdate, stopdate, allowcomments, approvecomments
1033
            FROM {view_access} va
1034
            WHERE view = ? AND visible = 1 AND token IS NULL
1035 1036 1037
            ORDER BY
                accesstype IS NULL, accesstype DESC,
                va.group, role IS NOT NULL, role,
1038
                institution, usr,
1039 1040
                startdate IS NOT NULL, startdate, stopdate IS NOT NULL, stopdate,
                allowcomments, approvecomments",
1041 1042
            array($this->id)
        );
1043 1044
        return $data ? $data : array();
    }
1045

1046
    public function process_access_records($data=array(), $timeformat=null) {
1047 1048
        $rolegroups = array();
        foreach ($data as &$item) {
1049
            if (isset($item->group) && $item->role && !isset($roledata[$item->group])) {
1050 1051 1052
                $rolegroups[$item->group] = 1;
            }
        }
1053

1054 1055 1056 1057 1058 1059 1060 1061 1062
        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()
            );
        }
1063

1064 1065
        foreach ($data as &$item) {
            $item = (array)$item;
Eugene Venter's avatar
Eugene Venter committed
1066
            $item['locked'] = false; // Indicate if item is editable
1067 1068 1069 1070 1071 1072 1073 1074
            if ($item['usr']) {
                $item['type'] = 'user';
                $item['id'] = $item['usr'];
            }
            else if ($item['group']) {
                $item['type'] = 'group';
                $item['id'] = $item['group'];
            }
Eugene Venter's avatar