view.php 108 KB
Newer Older
Penny Leach's avatar
Penny Leach committed
1
2
<?php
/**
Francois Marier's avatar
Francois Marier committed
3
 * Mahara: Electronic portfolio, weblog, resume builder and social networking
4
5
 * Copyright (C) 2006-2009 Catalyst IT Ltd and others; see:
 *                         http://wiki.mahara.org/Contributors
Penny Leach's avatar
Penny Leach committed
6
 *
Francois Marier's avatar
Francois Marier committed
7
8
9
10
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
Penny Leach's avatar
Penny Leach committed
11
 *
Francois Marier's avatar
Francois Marier committed
12
13
14
15
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
Penny Leach's avatar
Penny Leach committed
16
 *
Francois Marier's avatar
Francois Marier committed
17
18
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Penny Leach's avatar
Penny Leach committed
19
20
21
 *
 * @package    mahara
 * @subpackage core
22
 * @author     Catalyst IT Ltd
Penny Leach's avatar
Penny Leach committed
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
24
 * @copyright  (C) 2006-2009 Catalyst IT Ltd http://catalyst.net.nz
Penny Leach's avatar
Penny Leach committed
25
26
27
28
29
30
31
 *
 */

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

class View {

32
33
34
35
36
    private $dirty;
    private $deleted;
    private $id;
    private $owner;
    private $ownerformat;
37
    private $group;
38
    private $institution;
39
40
41
    private $ctime;
    private $mtime;
    private $atime;
42
43
    private $startdate;
    private $stopdate;
44
45
    private $submittedgroup;
    private $submittedhost;
46
47
48
49
50
51
52
    private $title;
    private $description;
    private $loggedin;
    private $friendsonly;
    private $artefact_instances;
    private $artefact_metadata;
    private $ownerobj;
53
    private $groupobj;
54
    private $numcolumns;
55
    private $layout;
56
    private $columns;
57
    private $dirtycolumns; // for when we change stuff
58
    private $tags;
59
    private $categorydata;
60
    private $editingroles;
61
    private $template;
62
    private $copynewuser = 0;
63
    private $copynewgroups;
64
    private $type;
Penny Leach's avatar
Penny Leach committed
65
66
67

    public function __construct($id=0, $data=null) {
        if (!empty($id)) {
68
69
70
            $tempdata = get_record('view','id',$id);
            if (empty($tempdata)) {
                throw new ViewNotFoundException("View with id $id not found");
Penny Leach's avatar
Penny Leach committed
71
            }    
72
73
74
75
76
77
            if (!empty($data)) {
                $data = array_merge((array)$tempdata, $data);
            }
            else {
                $data = $tempdata; // use what the database has
            }
Penny Leach's avatar
Penny Leach committed
78
            $this->id = $id;
Penny Leach's avatar
Penny Leach committed
79
80
81
        }
        else {
            $this->ctime = time();
82
            $this->mtime = time();
Penny Leach's avatar
Penny Leach committed
83
            $this->dirty = true;
Penny Leach's avatar
Penny Leach committed
84
85
86
87
88
89
90
91
92
93
94
        }

        if (empty($data)) {
            $data = array();
        }
        foreach ((array)$data as $field => $value) {
            if (property_exists($this, $field)) {
                $this->{$field} = $value;
            }
        }
        $this->atime = time();
95
96
        $this->columns = array();
        $this->dirtycolumns = array();
97
98
        if ($this->group) {
            $group = get_record('group', 'id', $this->group);
99
            safe_require('grouptype', $group->grouptype);
100
            $this->editingroles = call_static_method('GroupType' . ucfirst($group->grouptype), 'get_view_editing_roles');
101
        }
Penny Leach's avatar
Penny Leach committed
102
103
    }

104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
    /**
     * Creates a new View for the given user/group/institution.
     * 
     * You can specify who the view is being created _by_ with the second 
     * 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;
    }

    /**
     * Creates a View for the given user, based off a given template and other 
     * View information supplied.
     *
127
     * Will set a default title of 'Copy of $viewtitle' if title is not 
128
129
130
131
132
133
     * specified in $viewdata.
     *
     * @param array $viewdata See View::_create
     * @param int $templateid The ID of the View to copy
     * @param int $userid     The user who has issued the command to create the 
     *                        view. See View::_create
134
     * @param int $checkaccess Whether to check that the user can see the view before copying it
135
136
137
138
139
140
     * @return array A list consisting of the new view, the template view and 
     *               information about the copy - i.e. how many blocks and 
     *               artefacts were copied
     * @throws SystemException under various circumstances, see the source for 
     *                         more information
     */
141
    public static function create_from_template($viewdata, $templateid, $userid=null, $checkaccess=true) {
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
        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");
        }

        if (!$template->get('template') && !$user->can_edit_view($template)) {
            throw new SystemException("View::create_from_template: Attempting to create a View from another View that is not marked as a template");
        }
161
        else if ($checkaccess && !can_view_view($templateid, $userid)) {
162
163
164
165
166
167
168
169
170
171
            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
        if (!isset($viewdata['title'])) {
            $view->set('title', self::new_title(get_string('Copyof', 'mahara', $template->get('title')), (object)$viewdata));
            $view->set('dirty', true);
        }
172
173
174
175
176
177
178
179

        try {
            $copystatus = $view->copy_contents($template);
        }
        catch (QuotaExceededException $e) {
            db_rollback();
            return array(null, $template, array('quotaexceeded' => true));
        }
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232

        $view->commit();
        db_commit();

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

    /**
     * Creates a new View for the given user, based on the given information 
     * about the view.
     *
     * Validation of the view data is performed, then the View is created. If 
     * the View is to be owned by a group, that group is given access to it.
     *
     * @param array $viewdata Data about the view. You can pass in most fields 
     *                        that appear in the view table.
     *
     *                        Note that you set who owns the View by setting 
     *                        either the owner, group or institution field as 
     *                        approriate.
     *
     *                        Currently, you cannot pass in access data. Use 
     *                        $view->set_access() after retrieving the $view 
     *                        object.
     *
     * @param int $userid The user who has issued the command to create the 
     *                    View (note: this is different from the "owner" of the 
     *                    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
     * @throws SystemException if the View data is invalid - mostly this is due 
     *                         to owner information being specified incorrectly.
     */
    private static function _create(&$viewdata, $userid) {
        // If no owner information is provided, assume that the view is being 
        // 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']}");
                }
            }

233
            // Users can only have one view of each non-portfolio type
234
            if (isset($viewdata['type']) && $viewdata['type'] != 'portfolio' && get_record('view', 'owner', $viewdata['owner'], 'type', $viewdata['type'])) {
235
236
237
                $viewdata['type'] = 'portfolio';
            }

238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
        }

        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(
            'numcolumns'  => 3,
            'template'    => 0,
            'type'        => 'portfolio',
            'title'       => self::new_title(get_string('Untitled', 'view'), (object)$viewdata),
        );

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

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

        if (isset($viewdata['group'])) {
            // By default, group views should be visible to the group
            $view->set_access(array(array(
                'type'      => 'group',
                'id'        => $viewdata['group'],
                'startdate' => null,
                'stopdate'  => null,
                'role'      => null
            )));
        }

        return $view;
    }

Penny Leach's avatar
Penny Leach committed
282
283
284
285
    public function get($field) {
        if (!property_exists($this, $field)) {
            throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
        }
286
287
288
        if ($field == 'tags') { // special case
            return $this->get_tags();
        }
289
290
291
        if ($field == 'categorydata') {
            return $this->get_category_data();
        }
Penny Leach's avatar
Penny Leach committed
292
293
294
        return $this->{$field};
    }

295
296
297
298
299
300
301
302
303
304
305
306
307
    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));
    }

308
309
310
311
312
313
314
    public function get_tags() {
        if (!isset($this->tags)) {
            $this->tags = get_column('view_tag', 'tag', 'view', $this->get('id'));
        }
        return $this->tags;
    }

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
    /**
     * 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;
        }
      
        if (!empty($this->dirty)) {
            return $this->commit();
        }
    }

    /** 
     * 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;
341
            if (in_array($k, array('mtime', 'ctime', 'atime', 'startdate', 'stopdate')) && !empty($v)) {
342
343
344
                $fordb->{$k} = db_format_timestamp($v);
            }
        }
345
346
347

        db_begin();

348
        if (empty($this->id)) {
349
350
            // users are only allowed one profile view
            if ($this->type == 'profile' && record_exists('view', 'owner', $this->owner, 'type', 'profile')) {
351
                throw new SystemException(get_string('onlonlyyoneprofileviewallowed', 'error'));
352
            }
353
354
355
356
357
            $this->id = insert_record('view', $fordb, 'id', true);
        }
        else {
            update_record('view', $fordb, 'id');
        }
358
359
360
361
362
363

        delete_records('view_tag', 'view', $this->get('id'));
        foreach ($this->get_tags() as $tag) {
            insert_record('view_tag', (object)array( 'view' => $this->get('id'), 'tag' => $tag));
        }

364
365
366
367
368
369
370
        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));
            }
        }

371
372
        db_commit();

373
374
375
376
        $this->dirty = false;
        $this->deleted = false;
    }

Penny Leach's avatar
Penny Leach committed
377
378
379
380
381
382
383
384
    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
385
                    $this->childreninstances[] = $i;
Penny Leach's avatar
Penny Leach committed
386
387
388
389
390
391
392
                }
            }
        }
        return $this->artefact_instances;
    }

    public function get_artefact_metadata() {
Penny Leach's avatar
Penny Leach committed
393
        if (!isset($this->artefact_metadata)) {
394
            $sql = 'SELECT a.*, i.name, va.block
395
396
397
                    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
398
                    WHERE va.view = ?';
399
            $this->artefact_metadata = get_records_sql_array($sql, array($this->id));
Penny Leach's avatar
Penny Leach committed
400
401
402
        }
        return $this->artefact_metadata;
    }
Penny Leach's avatar
Penny Leach committed
403

404
    public function find_artefact_children($artefact, $allchildren, &$refs) {
405
406

        $children = array();        
407
408
409
410
411
412
413
414
415
416
        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;
                $children[$child->id]['children'] = $this->find_artefact_children($child, 
                                                            $allchildren, $refs);
417
418
419
420
421
422
            }
        }

        return $children;
    }

Penny Leach's avatar
Penny Leach committed
423

Penny Leach's avatar
Penny Leach committed
424
425
426
427
428
429
    public function has_artefacts() {
        if ($this->get_artefact_metadata()) {
            return true;
        }
        return false;
    }
Penny Leach's avatar
Penny Leach committed
430
431
432
433
434
435
436
437

    public function get_owner_object() {
        if (!isset($this->ownerobj)) {
            $this->ownerobj = get_record('usr', 'id', $this->get('owner'));
        }
        return $this->ownerobj;
    }

438
439
440
441
442
443
444
    public function get_group_object() {
        if (!isset($this->groupobj)) {
            $this->groupobj = get_record('group', 'id', $this->get('group'));
        }
        return $this->groupobj;
    }

Penny Leach's avatar
Penny Leach committed
445
    
446
    public function delete() {
447
        db_begin();
448
449
450
        delete_records('artefact_feedback','view',$this->id);
        delete_records('view_feedback','view',$this->id);
        delete_records('view_access','view',$this->id);
451
        delete_records('view_access_group','view',$this->id);
452
        delete_records('view_access_usr','view',$this->id);
453
        delete_records('view_access_token', 'view', $this->id);
454
        delete_records('view_autocreate_grouptype', 'view', $this->id);
455
        delete_records('view_tag','view',$this->id);
456
        delete_records('usr_watchlist_view','view',$this->id);
457
        if ($blockinstanceids = get_column('block_instance', 'id', 'view', $this->id)) {
458
            require_once(get_config('docroot') . 'blocktype/lib.php');
459
460
461
462
463
            foreach ($blockinstanceids as $id) {
                $bi = new BlockInstance($id);
                $bi->delete();
            }
        }
464
        handle_event('deleteview', $this->id);
465
        delete_records('view','id',$this->id);
466
        $this->deleted = true;
467
        db_commit();
468
469
    }

470
    public function get_access($timeformat=null) {
471

Richard Mansfield's avatar
Richard Mansfield committed
472
473
474
475
476
477
478
479
480
        if (is_mysql()) {
            $uid = 'usr';
            $gid = '"group"';
        }
        else {
            $uid = 'CAST (usr AS TEXT)';
            $gid = 'CAST ("group" AS TEXT)';
        }

481
482
483
484
485
        $data = get_records_sql_array("
            SELECT accesstype AS type, NULL AS id, NULL AS role, NULL AS grouptype, startdate, stopdate
                FROM {view_access}
                WHERE view = ?
        UNION
Richard Mansfield's avatar
Richard Mansfield committed
486
            SELECT 'user' AS type, $uid AS id, NULL AS role, NULL AS grouptype, startdate, stopdate
487
488
489
                FROM {view_access_usr}
                WHERE view = ?
        UNION
Richard Mansfield's avatar
Richard Mansfield committed
490
            SELECT 'group', $gid, role, grouptype, startdate, stopdate FROM {view_access_group}
491
                INNER JOIN {group} g ON (\"group\" = g.id AND g.deleted = ?)
Richard Mansfield's avatar
Richard Mansfield committed
492
493
494
495
                WHERE view = ?
        UNION
            SELECT 'token', token, NULL AS role, NULL AS grouptype, startdate, stopdate
                FROM {view_access_token}
496
                WHERE view = ? AND visible = 1
Richard Mansfield's avatar
Richard Mansfield committed
497
        ", array($this->id, $this->id, 0, $this->id, $this->id));
498
499
500
501
502
        if ($data) {
            foreach ($data as &$item) {
                $item = (array)$item;
                if ($item['role']) {
                    $item['roledisplay'] = get_string($item['role'], 'grouptype.'.$item['grouptype']);
503
                }
504
505
506
507
508
509
510
511
                if ($timeformat) {
                    if ($item['startdate']) {
                        $item['startdate'] = strftime($timeformat, strtotime($item['startdate']));
                    }
                    if ($item['stopdate']) {
                        $item['stopdate'] = strftime($timeformat, strtotime($item['stopdate']));
                    }
                }
512
            }
513
514
515
        }
        else {
            $data = array();
516
517
518
519
520
521
        }
        return $data;
    }

    public function set_access($accessdata) {
        global $USER;
522
        require_once('activity.php');
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579

        // For users who are being removed from having access to this view, they
        // need to have the view and any attached artefacts removed from their
        // watchlist.
        $oldusers = array();
        foreach ($this->get_access() as $item) {
            if ($item['type'] == 'user') {
                $oldusers[] = $item;
            }
        }

        $newusers = array();
        if ($accessdata) {
            foreach ($accessdata as $item) {
                if ($item['type'] == 'user') {
                    $newusers[] = $item;
                }
            }
        }

        $userstodelete = array();
        foreach ($oldusers as $olduser) {
            foreach ($newusers as $newuser) {
                if ($olduser['id'] == $newuser['id']) {
                    continue(2);
                }
            }
            $userstodelete[] = $olduser;
        }

        if ($userstodelete) {
            $userids = array();
            foreach ($userstodelete as $user) {
                $userids[] = intval($user['id']);
            }
            $userids = implode(',', $userids);

            execute_sql('DELETE FROM {usr_watchlist_view}
                WHERE view = ' . $this->get('id') . '
                AND usr IN (' . $userids . ')');
        }

        $beforeusers = activity_get_viewaccess_users($this->get('id'), $USER->get('id'), 'viewaccess');

        // Procedure:
        // get list of current friends - this is available in global $data
        // compare with list of new friends
        // work out which friends are being removed
        // foreach friend
        //     // remove record from usr_watchlist_view where usr = ? and view = ?
        //     // remove records from usr_watchlist_artefact where usr = ? and view = ?
        // endforeach
        //
        db_begin();
        delete_records('view_access', 'view', $this->get('id'));
        delete_records('view_access_usr', 'view', $this->get('id'));
        delete_records('view_access_group', 'view', $this->get('id'));
580
        delete_records('view_access_token', 'view', $this->get('id'), 'visible', 1);
581
582
583
584
        $time = db_format_timestamp(time());

        // View access
        if ($accessdata) {
585
586
587
588
589
590
            /*
             * There should be a cleaner way to do this
             * $accessdata_added ensures that the same access is not granted twice because the profile page
             * gets very grumpy if there are duplicate access rules
             */
            $accessdata_added = array();
591
592
593
            foreach ($accessdata as $item) {
                $accessrecord = new StdClass;
                $accessrecord->view = $this->get('id');
594
595
596
597
598
599
                if (isset($item['startdate'])) {
                    $accessrecord->startdate = db_format_timestamp($item['startdate']);
                }
                if (isset($item['stopdate'])) {
                    $accessrecord->stopdate  = db_format_timestamp($item['stopdate']);
                }
600
601
602
603
604
                switch ($item['type']) {
                    case 'public':
                    case 'loggedin':
                    case 'friends':
                        $accessrecord->accesstype = $item['type'];
605
606
607
608
                        if (array_search($accessrecord, $accessdata_added) === false) {
                            insert_record('view_access', $accessrecord);
                            $accessdata_added[] = $accessrecord;
                        }
609
610
611
                        break;
                    case 'user':
                        $accessrecord->usr = $item['id'];
612
613
614
615
                        if (array_search($accessrecord, $accessdata_added) === false) {
                            insert_record('view_access_usr', $accessrecord);
                            $accessdata_added[] = $accessrecord;
                        }
616
617
618
                        break;
                    case 'group':
                        $accessrecord->group = $item['id'];
619
                        if (isset($item['role']) && strlen($item['role'])) {
620
621
622
623
624
                            // Don't insert a record for a role the group doesn't have
                            $roleinfo = group_get_role_info($item['id']);
                            if (!isset($roleinfo[$item['role']])) {
                                break;
                            }
625
626
                            $accessrecord->role = $item['role'];
                        }
627
628
629
630
631
                        if (array_search($accessrecord, $accessdata_added) === false) {
                            insert_record('view_access_group', $accessrecord);
                            $accessdata_added[] = $accessrecord;
                        }

632
                        break;
Richard Mansfield's avatar
Richard Mansfield committed
633
634
                    case 'token':
                        $accessrecord->token = $item['id'];
635
636
637
638
                        if (array_search($accessrecord, $accessdata_added) === false) {
                            insert_record('view_access_token', $accessrecord);
                            $accessdata_added[] = $accessrecord;
                        }
Richard Mansfield's avatar
Richard Mansfield committed
639
                        break;
640
641
642
643
644
645
646
647
648
649
650
651
652
653
                }
            }
        }

        $data = new StdClass;
        $data->view = $this->get('id');
        $data->owner = $USER->get('id');
        $data->oldusers = $beforeusers;
        activity_occurred('viewaccess', $data);
        handle_event('saveview', $this->get('id'));

        db_commit();
    }

654
655
656
657
658
659
    public function get_autocreate_grouptypes() {
        if (!isset($this->copynewgroups)) {
            $this->copynewgroups = get_column('view_autocreate_grouptype', 'grouptype', 'view', $this->id);
        }
        return $this->copynewgroups;
    }
660

661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
    public function is_submitted() {
        return $this->get('submittedgroup') || $this->get('submittedhost');
    }

    public function submitted_to() {
        if ($group = $this->get('submittedgroup')) {
            return array('type' => 'group', 'id' => $group, 'name' => get_field('group', 'name', 'id', $group));
        }
        if ($host = $this->get('submittedhost')) {
            return array('type' => 'host', 'wwwroot' => $host, 'name' => get_field('host', 'name', 'wwwroot', $host));
        }
        return null;
    }

    public function release($releaseuser=null) {
        $submitinfo = $this->submitted_to();
        if (is_null($submitinfo)) {
            throw new ParameterException("View with id " . $this->get('id') . " has not been submitted");
679
680
        }
        $releaseuser = optional_userobj($releaseuser);
681
682
683
684
685
686
        if ($submitinfo['type'] == 'group') {
            $this->set('submittedgroup', null);
        }
        else if ($submitinfo['type'] == 'host') {
            $this->set('submittedhost', null);
        }
687
        $this->commit();
688
        $ownerlang = get_user_language($this->get('owner'));
689
690
691
        require_once('activity.php');
        activity_occurred('maharamessage', 
                  array('users'   => array($this->get('owner')),
692
                  'subject' => get_string_from_language($ownerlang, 'viewreleasedsubject', 'group'),
693
                  'message' => get_string_from_language($ownerlang, 'viewreleasedmessage', 'group', $submitinfo['name'], 
694
                       display_name($releaseuser, $this->get_owner_object()))));
695
696
    }

697
698
699
    /**
     * Returns HTML for the category list
     *
700
     * @param string $category The currently selected category
701
    */
702
703
    public function build_category_list($category, $new=0) {
        $categories = $this->get_category_data();
704
705
706
707
708
709
710
        $flag = false;
        foreach ($categories as &$cat) {
            $classes = '';
            if (!$flag) {
                $flag = true;
                $classes[] = 'first';
            }
711
            if ($category == $cat['name']) {
712
713
714
715
716
717
718
                $classes[] = 'current';
            }
            if ($classes) {
                $cat['class'] = hsc(implode(' ', $classes)); 
            }
        }

719
720
721
        // Because of the reference in the above loop, $cat refers to the last item
        $cat['class'] = (isset($cat['class'])) ? $cat['class'] . ' last' : 'last';

722
723
        $smarty = smarty_core();
        $smarty->assign('categories', $categories);
724
        $smarty->assign('viewid', $this->get('id'));
725
        $smarty->assign('new', $new);
726
727
728
        return $smarty->fetch('view/blocktypecategorylist.tpl');
    }

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
    /**
     * Gets the name of the first blocktype category for this View.
     *
     * This can change based on what blocktypes allow themselves to be in what 
     * types of View. For example, in a group View, blog blocktypes aren't 
     * allowed (yet), so the first blocktype category shown won't be "blog"
     */
    public function get_default_category() {
        $data = $this->get_category_data();
        return $data[0]['name'];
    }

    /**
     * Gets information about blocktype categories for blocks that can be put 
     * in this View
     *
     * For each category, returns its name, a localised title and the number of 
     * blocktypes in the category that can be put in this View.
     *
     * If a category has no blocktypes that can be put in this View, it is not 
     * returned
     */
    private function get_category_data() {
        if (isset($this->category_data)) {
            return $this->category_data;
        }

        require_once(get_config('docroot') . '/blocktype/lib.php');
        $categories = array();
758
        $sql = 'SELECT bic.* FROM {blocktype_installed_category} bic
759
760
761
            JOIN {blocktype_installed} bi ON (bic.blocktype = bi.name AND bi.active = 1)
            JOIN {blocktype_installed_viewtype} biv ON (bi.name = biv.blocktype AND biv.viewtype = ?)';
        foreach (get_records_sql_array($sql, array($this->get('type'))) as $blocktypecategory) {
762
            safe_require('blocktype', $blocktypecategory->blocktype);
763
            if (call_static_method(generate_class_name("blocktype", $blocktypecategory->blocktype), "allowed_in_view", $this)) {
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
                if (!isset($categories[$blocktypecategory->category])) {
                    $categories[$blocktypecategory->category] = array(
                        'name'  => $blocktypecategory->category,
                        'title' => call_static_method("PluginBlocktype", "category_title_from_name", $blocktypecategory->category),
                        'count' => 0,
                    );
                }
                $categories[$blocktypecategory->category]['count']++;
            }
        }

        foreach ($categories as &$category) {
            $category['title'] .= ' (' . $category['count'] . ')';
            unset($category['count']);
        }

        // The 'internal' plugin is known to the outside world as 'profile', so 
        // we need to sort on the actual name
        usort($categories, create_function('$a, $b', 'return strnatcasecmp($a[\'title\'], $b[\'title\']);'));

        return $this->category_data = $categories;
    }

787
788
789
790
791
792
793
794
    /**
     * Returns HTML for the blocktype list for a particular category
     *
     * @param string $category   The category to build the blocktype list for
     * @param bool   $javascript Set to true if the caller is a json script, 
     *                           meaning that nothing for the standard HTML version 
     *                           alone should be output
     */
795
    public function build_blocktype_list($category, $javascript=false) {
796
        require_once(get_config('docroot') . 'blocktype/lib.php');
797
        $blocktypes = PluginBlockType::get_blocktypes_for_category($category, $this);
798
799
800
801
802
803
804
805
806
807
808

        $smarty = smarty_core();
        $smarty->assign_by_ref('blocktypes', $blocktypes);
        $smarty->assign('javascript', $javascript);
        return $smarty->fetch('view/blocktypelist.tpl');
    }

    /**
     * Process view changes. This function is used both by the json stuff and 
     * by normal posts
     */
809
    public function process_changes($category='', $new=0) {
810
811
812
        global $SESSION, $USER;

        // Security
813
814
        // TODO this might need to be moved below the requestdata check below, to prevent non owners of the view being 
        // rejected
815
        if (!$USER->can_edit_view($this)) {
816
817
818
819
820
821
822
823
824
825
826
            throw new AccessDeniedException(get_string('canteditdontown', 'view'));
        }

        if (!count($_POST) && count($_GET) < 3) {
            return;
        }

        $action = '';
        foreach ($_POST as $key => $value) {
            if (substr($key, 0, 7) == 'action_') {
                $action = substr($key, 7);
827
                break;
828
            }
829
830
831
832
833
            else if (substr($key, 0, 37) == 'cancel_action_configureblockinstance_'
                     && param_integer('removeoncancel', 0)) {
                $action = 'removeblockinstance_' . substr($key, 37);
                break;
            }
834
835
836
837
838
839
840
841
842
843
844
845
        }
        // TODO Scan GET for an action. The only action that is GETted is 
        // confirming deletion of a blockinstance. It _should_ be a POST, but 
        // that can be fixed later.
        if (!$action) {
            foreach ($_GET as $key => $value) {
                if (substr($key, 0, 7) == 'action_') {
                    $action = substr($key, 7);
                }
            }
        }

846
847
848
849
        if (empty($action)) {
            return;
        }
    
850
851
852
        $actionstring = $action;
        $action = substr($action, 0, strpos($action, '_'));
        $actionstring  = substr($actionstring, strlen($action) + 1);
853
854
855
856
857

        // Actions from <input type="image"> buttons send an _x and _y
        if (substr($actionstring, -2) == '_x' || substr($actionstring, -2) == '_y') {
            $actionstring = substr($actionstring, 0, -2);
        }
858
859
860
861
862
863
864
865
866
867
868
869
870
871
        
        $values = self::get_values_for_action($actionstring);

        $result = null;
        switch ($action) {
            // the view class method is the same as the action,
            // but I've left these here in case any additional
            // parameter handling has to be done.
            case 'addblocktype': // requires action_addblocktype  (blocktype in separate parameter)
                $values['blocktype'] = param_alpha('blocktype', null);
            break;
            case 'removeblockinstance': // requires action_removeblockinstance_id_\d
                if (!defined('JSON')) {
                    if (!$sure = param_boolean('sure')) {
872
                        $yeslink = get_config('wwwroot') . '/view/blocks.php?id=' . $this->get('id') . '&c=file&new=' . $new . '&action_' . $action . '_' .  $actionstring . '=1&sure=true';
Penny Leach's avatar
Penny Leach committed
873
                        $baselink = '/view/blocks.php?id=' . $this->get('id') . '&c=' . $category . '&new=' . $new;
874
875
876
877
878
879
880
881
882
                        $SESSION->add_info_msg(get_string('confirmdeleteblockinstance', 'view') 
                            . ' <a href="' . $yeslink . '">' . get_string('yes') . '</a>'
                            . ' <a href="' . $baselink . '">' . get_string('no') . '</a>', false);
                        redirect($baselink);
                        exit;
                    }
                }
            break;
            case 'configureblockinstance': // requires action_configureblockinstance_id_\d_column_\d_order_\d
883
            case 'acsearch': // requires action_acsearch_id_\d
884
885
                if (!defined('JSON')) {
                    $this->blockinstance_currently_being_configured = $values['id'];
886
887
                    // And we're done here for now
                    return;
888
889
890
891
892
893
894
895
                }
            case 'moveblockinstance': // requires action_moveblockinstance_id_\d_column_\d_order_\d
            case 'addcolumn': // requires action_addcolumn_before_\d
            case 'removecolumn': // requires action_removecolumn_column_\d
            break;
            default:
                throw new InvalidArgumentException(get_string('noviewcontrolaction', 'error', $action));
        }
896

897
898
899
900
901
        $message = '';
        $success = false;
        try {
            $values['returndata'] = defined('JSON');
            $returndata = $this->$action($values);
902
903
904
905
906

            // Tell the watchlist that the view changed
            $data = (object)array(
                'view' => $this->get('id'),
            );
907
            require_once('activity.php');
908
909
            activity_occurred('watchlist', $data);

910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
            if (!defined('JSON')) {
                $message = $this->get_viewcontrol_ok_string($action);
            }
            $success = true;
        }
        catch (Exception $e) {
            // if we're in ajax land, just throw it
            // the handler will deal with the message.
            if (defined('JSON')) {
                throw $e;
            }
            $message = $this->get_viewcontrol_err_string($action) . ': ' . $e->getMessage();
        }
        if (!defined('JSON')) {
            // set stuff in the session and redirect
            $fun = 'add_ok_msg';
            if (!$success) {
927
                $fun = 'add_error_msg';
928
929
            }
            $SESSION->{$fun}($message);
930
            redirect('/view/blocks.php?id=' . $this->get('id') . '&c=' . $category . '&new=' . $new);
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
        }
        return array('message' => $message, 'data' => $returndata);
    }

    /** 
     * Parses the string and returns a hash of values
     *
     * @param string $action expects format name_value_name_value
     *                       where values are all numeric
     * @return array associative
    */
    private static function get_values_for_action($action) {
        $values = array();
        $bits = explode('_', $action);
        if ((count($bits) % 2) == 1) {
            throw new ParamOutOfRangeException(get_string('invalidviewaction', 'error', $action));
        }
        $lastkey = null;
        foreach ($bits as $index => $bit) {
            if ($index % 2 == 0) { 
                $lastkey = $bit;
            }
            else {
                $values[$lastkey] = $bit;
            }
        }
        return $values;
    }

960
961
    /**
    * builds up the data structure for  this view
962
963
    * @param boolean $force force a re-read from the database
    *                       use this if a column is dirty
964
965
966
    * @private
    * @return void
    */
967
968
    private function build_column_datastructure($force=false) {
        if (!empty($this->columns) && empty($force)) { // we've already built it up
969
970
971
            return;
        }

Penny Leach's avatar
Penny Leach committed
972
973
974
975
        $sql = 'SELECT bi.*
            FROM {block_instance} bi
            WHERE bi.view = ?
            ORDER BY bi.column, bi.order';
976
977
978
979
980
981
982
983
984
        if (!$data = get_records_sql_array($sql, array($this->get('id')))) {
            $data = array();
        }

        // fill up empty columns array keys
        for ($i = 1; $i <= $this->get('numcolumns'); $i++) {
            $this->columns[$i] = array('blockinstances' => array());
        }

985
986
987
988
989
990
991
992
993
994
995
996
997
        // Set column widths
        if ($this->get('numcolumns') > 1) {
            $layout = $this->get('layout');
            if ($layout) {
                $i = 0;
                // The get_field also verifies the layout is correct for the
                // number of columns in the view
                foreach (explode(',', get_field('view_layout', 'widths', 'id', $layout, 'columns', $this->get('numcolumns'))) as $width) {
                    $this->columns[++$i]['width'] = $width;
                }
            }
        }

998
        foreach ($data as $block) {
999
            require_once(get_config('docroot') . 'blocktype/lib.php');
1000
            $b = new BlockInstance($block->id, (array)$block);
For faster browsing, not all history is shown. View entire blame