view.php 310 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 49
    private $dirtyrows; // for when we change stuff
    private $dirtycolumns; // now includes reference to row [row][column]
50
    private $tags;
51
    private $categorydata;
52
    private $template;
53
    private $retainview;
54
    private $copynewuser = 0;
55
    private $copynewgroups;
56
    private $type;
57
    private $visits;
58
    private $allowcomments;
59
    private $approvecomments;
60
    private $collection;
61
    private $locked;
62
    private $urlid;
63
    private $skin;
64
    private $anonymise = 0;
65
    private $lockblocks = 0;
66 67
    private $instructions;
    private $instructionscollapsed=0;
Penny Leach's avatar
Penny Leach committed
68

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

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

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

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

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

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

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

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

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

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

300 301 302 303 304 305 306 307 308 309 310
        // 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
311
        $this->atime = time();
312
        $this->rows = array();
313
        $this->columns = array();
314
        $this->dirtyrows = array();
315
        $this->dirtycolumns = array();
316
        $this->oldcolumnsperrow = $this->get('columnsperrow');
317
        // set only for existing views - _create provides default value
318
        // Ignore if the constructor is called with deleted set to true
319 320
        if (empty($this->deleted)) {
            if ($this->columnsperrow === false || ($this->numrows > 0 && count($this->columnsperrow) != $this->numrows)) {
321 322 323 324 325 326 327 328 329 330
                // 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) {
331 332 333 334 335 336 337 338
                                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);
                                }
339 340 341 342 343 344 345 346 347 348 349 350 351
                                $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
                        $default = array();
                        foreach ($rowscols as $row) {
352 353 354 355 356 357 358 359
                            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);
                            }
360 361 362 363 364
                            $default[$row->row] = $row;
                        }
                }
                else {
                    // Layout not known so make it 1 row / 3 cols
365 366 367 368 369
                    if ($this->get('id')) {
                        insert_record('view_rows_columns', (object) array(
                            'view' => $this->get('id'),
                            'row' => 1, 'columns' => 3));
                    }
370 371 372 373
                    $default = self::default_columnsperrow();
                }
                $this->columnsperrow = $default;
            }
374
        }
Penny Leach's avatar
Penny Leach committed
375 376
    }

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

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

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

457 458 459 460
        $view->urlid = generate_urlid($view->title, get_config('cleanurlviewdefault'), 3, 100);
        $viewdata['owner'] = $userid;
        $view->urlid = self::new_urlid($view->urlid, (object)$viewdata);

461
        try {
462
            $copystatus = $view->copy_contents($template, $artefactcopies);
463 464 465 466 467
        }
        catch (QuotaExceededException $e) {
            db_rollback();
            return array(null, $template, array('quotaexceeded' => true));
        }
468

469 470 471
        // Lockblocks if set on template
        $view->set('lockblocks', $template->get('lockblocks'));

472
        $view->commit();
473

474 475 476 477 478
        // 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) {
479 480 481
                // is the owner of the copy going to be a group or institution or not?
                $group = $view->group;
                $institution = $view->institution;
482 483 484 485 486 487 488 489 490
                $owner = (!empty($institution) || !empty($group)) ? null : $view->owner;
                $data = (object) array(
                    'usr' => $owner,
                    'group' => $group,
                    'institution' => $institution,
                    'layout' =>  $template->get('layout'),
                );
                $where = clone $data;
                ensure_record_exists('usr_custom_layout', $where, $data);
491 492 493
            }
        }

494 495 496
        $blocks = get_records_array('block_instance', 'view', $view->get('id'));
        if ($blocks) {
            foreach ($blocks as $b) {
497 498 499 500
                // 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();
501 502 503 504 505 506 507
                $configdata = unserialize($b->configdata);
                if (!isset($configdata['artefactid'])) {
                    continue;
                }
                if (!isset($configdata['copytype']) || $configdata['copytype'] !== 'reference') {
                    continue;
                }
508
                $va = new stdClass();
509 510 511 512 513 514
                $va->view = $b->view;
                $va->artefact = $configdata['artefactid'];
                $va->block = $b->id;
                insert_record('view_artefact', $va);
            }
        }
515 516

        if ($template->get('retainview') && !$template->get('institution')) {
517
            $obj = new stdClass();
518 519 520 521
            $obj->view  = $view->get('id');
            $obj->ctime = db_format_timestamp(time());
            $obj->usr   = $template->get('owner');
            $obj->group = $template->get('group');
522 523 524 525 526 527 528 529
            $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)
            );
530 531
        }

532 533 534 535 536 537 538 539 540
        db_commit();
        return array(
            $view,
            $template,
            $copystatus,
        );
    }

    /**
541
     * Creates a new View for the given user, based on the given information
542 543
     * about the view.
     *
544
     * Validation of the view data is performed, then the View is created. If
545 546
     * the View is to be owned by a group, that group is given access to it.
     *
547
     * @param array $viewdata Data about the view. You can pass in most fields
548 549
     *                        that appear in the view table.
     *
550 551
     *                        Note that you set who owns the View by setting
     *                        either the owner, group or institution field as
552 553
     *                        approriate.
     *
554 555
     *                        Currently, you cannot pass in access data. Use
     *                        $view->set_access() after retrieving the $view
556 557
     *                        object.
     *
558 559
     * @param int $userid The user who has issued the command to create the
     *                    View (note: this is different from the "owner" of the
560 561 562
     *                    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
563
     * @throws SystemException if the View data is invalid - mostly this is due
564 565 566
     *                         to owner information being specified incorrectly.
     */
    private static function _create(&$viewdata, $userid) {
567
        // If no owner information is provided, assume that the view is being
568 569 570 571 572 573 574 575 576 577 578 579 580 581
        // 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']}");
                }
            }

582
            // Users can only have one view of each non-portfolio type
583
            if (isset($viewdata['type']) && $viewdata['type'] != 'portfolio' && get_record('view', 'owner', $viewdata['owner'], 'type', $viewdata['type'])) {
584 585
                $viewdata['type'] = 'portfolio';
            }
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604
        }

        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(
605
            'numcolumns'    => 2, // 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.
606
            'numrows'       => 1,
607
            'columnsperrow' => self::default_columnsperrow(),
608 609 610
            'template'      => 0,
            'type'          => 'portfolio',
            'title'         => (array_key_exists('title', $viewdata)) ? $viewdata['title'] : self::new_title(get_string('Untitled', 'view'), (object)$viewdata),
611
            'anonymise'     => 0,
612
            'lockblocks'    => 0,
613 614 615 616
        );

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

617 618 619 620 621
        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);
        }

622 623
        $view = new View(0, $data);
        $view->commit();
624

625 626 627
        if (isset($viewdata['group']) &&
            (empty($viewdata['type']) || (!empty($viewdata['type']) && $viewdata['type'] != 'grouphomepage'))
           ) {
628 629 630
            require_once('activity.php');

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

634
            // By default, group views should be visible to the group
635
            $newaccess = (object) array(
636 637
                'view'  => $view->get('id'),
                'group' => $viewdata['group'],
638
                'ctime' => db_format_timestamp(time()),
639
            );
640 641 642 643 644 645 646 647
            $vaid = insert_record('view_access', $newaccess, 'id', true);
            handle_event('updateviewaccess', array(
                'id' => $vaid,
                'eventfor' => 'group',
                'parentid' => $view->get('id'),
                'parenttype' => 'view',
                'rules' => $newaccess)
            );
648
            // Notify group members
649
            $accessdata = new stdClass();
650
            $accessdata->view = $view->get('id');
651 652
            $accessdata->oldusers = $beforeusers;
            activity_occurred('viewaccess', $accessdata);
653 654
        }

655 656 657 658 659 660 661 662 663 664 665
        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));
                }
            }
        }

666
        return new View($view->get('id')); // Reread to ensure defaults are set
667 668
    }

Son Nguyen's avatar
Son Nguyen committed
669
    public static function default_columnsperrow() {
670 671
        $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)) {
672
            throw new SystemException("View::default_columnsperrow: Default columns = 3, widths = '33,33,33' not in view_layout_columns table");
673 674 675 676
        }
        return $default;
    }

Penny Leach's avatar
Penny Leach committed
677 678 679 680
    public function get($field) {
        if (!property_exists($this, $field)) {
            throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
        }
681 682 683
        if ($field == 'tags') { // special case
            return $this->get_tags();
        }
684 685 686
        if ($field == 'categorydata') {
            return $this->get_category_data();
        }
687 688 689
        if ($field == 'collection') {
            return $this->get_collection();
        }
690 691 692
        if ($field == 'columnsperrow') {
            return $this->get_columnsperrow();
        }
Penny Leach's avatar
Penny Leach committed
693 694 695
        return $this->{$field};
    }

696 697 698 699 700 701 702
    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;
703 704 705 706 707
            if ($field != 'atime') {
                // don't bother updating the modified time if we are
                // only wanting to update the accessed time
                $this->mtime = time();
            }
708 709 710 711 712
            return true;
        }
        throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
    }

713 714
    public function get_tags() {
        if (!isset($this->tags)) {
715 716 717 718 719 720 721 722 723 724 725 726
            $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')));
727 728 729 730
        }
        return $this->tags;
    }

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 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
    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);
    }

789 790 791
    public function get_collection() {
        if (!isset($this->collection)) {
            require_once(get_config('libroot') . 'collection.php');
792
            $this->collection = Collection::search_by_view_id($this->id);
793 794 795 796
        }
        return $this->collection;
    }

797 798
    public function get_columnsperrow() {
        if (!isset($this->columnsperrow)) {
799 800 801 802
            $this->columnsperrow = get_records_sql_assoc('SELECT "row", columns
                                                          FROM {view_rows_columns}
                                                          WHERE view = ?
                                                          ORDER BY "row" ASC', array($this->get('id')));
803 804 805 806
        }
        return $this->columnsperrow;
    }

807 808 809 810 811 812 813
    public function collection_id() {
        if ($collection = $this->get_collection()) {
            return $collection->get('id');
        }
        return false;
    }

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

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

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

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

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

863
        if (isset($this->tags)) {
864 865 866 867 868 869 870 871 872 873 874 875 876 877
            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'));
878
            foreach ($this->get_tags() as $tag) {
879 880
                //truncate the tag before insert it into the database
                $tag = substr($tag, 0, 128);
881
                $tag = check_if_institution_tag($tag);
882 883 884 885 886 887 888 889 890 891 892
                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'),
                    )
                );
893
            }
894 895
        }

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

903 904 905 906 907 908 909 910 911 912 913 914
        $columnsperrowchanged = (!empty($this->oldcolumnsperrow)) ? array_udiff($this->oldcolumnsperrow, $this->columnsperrow, function($oa, $ob) {
            $rows = $oa->row - $ob->row;
            $columns = $oa->columns - $ob->columns;
            if ($rows != 0) {
                return $rows;
            }
            else if ($columns != 0) {
                return $columns;
            }
            return 0;
        }) : false;

915
        if (isset($this->columnsperrow) && (!empty($columnsperrowchanged) || $creating)) {
916 917 918 919 920 921
            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));
            }
        }

922 923
        db_commit();

924 925 926 927
        $this->dirty = false;
        $this->deleted = false;
    }

928 929 930 931 932
    /**
     * Returns an array of all the artefacts on this page.
     *
     * @return array
     */
Penny Leach's avatar
Penny Leach committed
933
    public function get_artefact_instances() {
934
        $this->artefact_instances = array();
Penny Leach's avatar
Penny Leach committed
935

936 937 938 939 940 941
        $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
942

943 944 945 946 947 948
        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;
949 950
            }
        }
951
        return $this->artefact_instances;
Penny Leach's avatar
Penny Leach committed
952
    }
Penny Leach's avatar
Penny Leach committed
953 954

    public function get_owner_object() {
955
        if (empty($this->owner)) {
956 957
            return false;
        }
Penny Leach's avatar
Penny Leach committed
958
        if (!isset($this->ownerobj)) {
959 960 961 962
            // $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
963 964 965 966
        }
        return $this->ownerobj;
    }

967 968
    public function get_group_object() {
        if (!isset($this->groupobj)) {
969
            $this->groupobj = get_group_by_id($this->get('group'), true);
970 971 972 973
        }
        return $this->groupobj;
    }

974 975 976 977 978 979 980
    public function get_institution_object() {
        if (!isset($this->institutionobj)) {
            $this->institutionobj = get_record('institution', 'name', $this->get('institution'));
        }
        return $this->institutionobj;
    }

981
    public function delete() {
982
        safe_require('artefact', 'comment');
983
        db_begin();
984
        ArtefactTypeComment::delete_view_comments($this->id);
985
        delete_records('view_access','view',$this->id);
986
        delete_records('view_autocreate_grouptype', 'view', $this->id);
987
        delete_records('tag', 'resourcetype', 'view', 'resourceid', $this->id);
988
        delete_records('view_visit','view',$this->id);
989
        delete_records('view_versioning', 'view', $this->id);
990
        delete_records('existingcopy', 'view', $this->id);