collection.php 51.2 KB
Newer Older
1
2
3
4
5
6
<?php
/**
 *
 * @package    mahara
 * @subpackage core
 * @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.
9
10
11
12
13
 *
 */

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

14
15
16
17
18
19
class Collection {

    private $id;
    private $name;
    private $description;
    private $owner;
20
21
    private $group;
    private $institution;
22
23
    private $mtime;
    private $ctime;
24
    private $navigation;
25
26
27
    private $submittedgroup;
    private $submittedhost;
    private $submittedtime;
28
    private $submittedstatus;
29
    private $views;
30
    private $tags;
31
    private $framework;
32

33
34
35
36
    const UNSUBMITTED = 0;
    const SUBMITTED = 1;
    const PENDING_RELEASE = 2;

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    public function __construct($id=0, $data=null) {

        if (!empty($id)) {
            $tempdata = get_record('collection','id',$id);
            if (empty($tempdata)) {
                throw new CollectionNotFoundException("Collection with id $id not found");
            }
            if (!empty($data)) {
                $data = array_merge((array)$tempdata, $data);
            }
            else {
                $data = $tempdata; // use what the database has
            }
            $this->id = $id;
        }
        else {
            $this->ctime = time();
            $this->mtime = time();
        }
56

57
58
59
60
61
62
63
        if (empty($data)) {
            $data = array();
        }
        foreach ((array)$data as $field => $value) {
            if (property_exists($this, $field)) {
                $this->{$field} = $value;
            }
64
        }
65
66
67
68
        if (empty($this->group) && empty($this->institution) && empty($this->owner)) {
            global $USER;
            $this->owner = $USER->get('id');
        }
69
70
    }

71
72
73
74
    public function get($field) {
        if (!property_exists($this, $field)) {
            throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
        }
75
76
77
        if ($field == 'tags') {
            return $this->get_tags();
        }
78
79
80
        if ($field == 'views') {
            return $this->views();
        }
81
82
        return $this->{$field};
    }
83

84
85
86
87
88
89
90
91
    public function set($field, $value) {
        if (property_exists($this, $field)) {
            $this->{$field} = $value;
            $this->mtime = time();
            return true;
        }
        throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
    }
92

93
    /**
94
     * Helper function to create or update a Collection from the supplied data.
95
96
     *
     * @param array $data
97
     * @return collection           The newly created/updated collection
98
99
     */
    public static function save($data) {
100
101
        if (array_key_exists('id', $data)) {
            $id = $data['id'];
102
            $state = 'updatecollection';
103
104
105
        }
        else {
            $id = 0;
106
            $state = 'createcollection';
107
108
        }
        $collection = new Collection($id, $data);
109
        $collection->set('mtime', time());
110
        $collection->commit();
111
112
        $views = $collection->get('views');
        $viewids = array();
113
114
115
116
        if (!empty($views)) {
            foreach ($views['views'] as $view) {
                $viewids[] = $view->view;
            }
117
118
119
        }
        $eventdata = array('id' => $collection->get('id'),
                           'name' => $collection->get('name'),
120
                           'eventfor' => 'collection',
121
122
                           'viewids' => $viewids);
        handle_event($state, $eventdata);
123
        return $collection; // return newly created Collections id
124
    }
125

126
127
128
129
130
    /**
     * Deletes a Collection
     *
     */
    public function delete() {
131
        $viewids = get_column('collection_view', 'view', 'collection', $this->id);
132
        db_begin();
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153

        // Delete navigation blocks within the collection's views which point at this collection.
        if ($viewids) {
            $values = $viewids;
            $values[] = 'navigation';
            $navigationblocks = get_records_select_assoc(
                'block_instance', 'view IN (' . join(',', array_fill(0, count($viewids), '?')) . ') AND blocktype = ?',
                $values
            );
            if ($navigationblocks) {
                safe_require('blocktype', 'navigation');
                foreach ($navigationblocks as $b) {
                    $bi = new BlockInstance($b->id, $b);
                    $configdata = $bi->get('configdata');
                    if (isset($configdata['collection']) && $configdata['collection'] == $this->id) {
                        $bi->delete();
                    }
                }
            }
        }

Stacey Walker's avatar
Stacey Walker committed
154
        delete_records('collection_view','collection',$this->id);
155
        delete_records('tag', 'resourcetype', 'collection', 'resourceid', $this->id);
156
        delete_records('collection','id',$this->id);
157
        delete_records('existingcopy', 'collection', $this->id);
158
159
160
161
162
163

        // Secret url records belong to the collection, so remove them from the view.
        // @todo: add user message to whatever calls this.
        if ($viewids) {
            delete_records_select('view_access', 'view IN (' . join(',', $viewids) . ') AND token IS NOT NULL');
        }
164
165
        $data = array('id' => $this->id,
                      'name' => $this->name,
166
                      'eventfor' => 'collection',
167
168
                      'viewids' => $viewids);
        handle_event('deletecollection', $data);
169
170
171
        db_commit();
    }

172
173
174
175
    /**
     * This method updates the contents of the collection table only.
     */
    public function commit() {
176
        global $USER;
177
178
179
180

        $fordb = new StdClass;
        foreach (get_object_vars($this) as $k => $v) {
            $fordb->{$k} = $v;
181
            if (in_array($k, array('mtime', 'ctime', 'submittedtime')) && !empty($v)) {
182
183
184
                $fordb->{$k} = db_format_timestamp($v);
            }
        }
185

186
        db_begin();
187

188
189
190
191
192
        // if id is not empty we are editing an existing collection
        if (!empty($this->id)) {
            update_record('collection', $fordb, 'id');
        }
        else {
193
194
            $id = insert_record('collection', $fordb, 'id', true);
            if ($id) {
195
                $this->set('id', $id);
196
            }
197
        }
198

199
        if (isset($this->tags)) {
200
201
202
203
204
205
206
207
208
209
210
211
212
213
            if ($this->group) {
                $ownertype = 'group';
                $ownerid = $this->group;
            }
            else if ($this->institution) {
                $ownertype = 'institution';
                $ownerid = $this->institution;
            }
            else {
                $ownertype = 'user';
                $ownerid = $this->owner;
            }
            delete_records('tag', 'resourcetype', 'collection', 'resourceid', $this->get('id'));
            $tags = check_case_sensitive($this->get_tags(), 'tag');
214
            foreach ($tags as $tag) {
215
216
                //truncate the tag before insert it into the database
                $tag = substr($tag, 0, 128);
217
                $tag = check_if_institution_tag($tag);
218
219
220
221
222
223
224
225
226
227
228
                insert_record('tag',
                    (object)array(
                        'resourcetype' => 'collection',
                        'resourceid' => $this->get('id'),
                        'ownertype' => $ownertype,
                        'ownerid' => $ownerid,
                        'tag' => $tag,
                        'ctime' => db_format_timestamp(time()),
                        'editedby' => $USER->get('id'),
                    )
                );
229
230
231
            }
        }

232
233
        db_commit();
    }
234

235
236
237
238
    /**
     * Generates a name for a newly created Collection
     */
    private static function new_name($name, $ownerdata) {
239
240
241
242
        $extText = get_string('version.', 'mahara');
        $tempname = preg_split('/ '. $extText . '[0-9]$/', $name);
        $name = $tempname[0];

243
244
245
246
247
        $taken = get_column_sql('
            SELECT name
            FROM {collection}
            WHERE ' . self::owner_sql($ownerdata) . "
                AND name LIKE ? || '%'", array($name));
248
249
250

        $ext = '';
        $i = 1;
251
252
        if ($taken) {
            while (in_array($name . $ext, $taken)) {
253
                $ext = ' ' . $extText . ++$i;
254
255
256
257
258
259
260
261
262
            }
        }
        return $name . $ext;
    }

    /**
     * Creates a Collection for the given user, based off a given template and other
     * Collection information supplied.
     *
263
264
     * Will set a default name of 'Copy of $collectiontitle' if name is not
     * specified in $collectiondata and $titlefromtemplate == false.
265
266
267
268
269
270
     *
     * @param array $collectiondata Contains information of the old collection, submitted in form
     * @param int $templateid The ID of the Collection to copy
     * @param int $userid     The user who has issued the command to create the
     *                        collection.
     * @param int $checkaccess Whether to check that the user can see the collection before copying it
271
272
     * @param boolean $titlefromtemplate  Title of new collection or view will be exactly copied from the template
     *
273
274
275
276
277
278
     * @return array A list consisting of the new collection, the template collection 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
     */
279
    public static function create_from_template($collectiondata, $templateid, $userid=null, $checkaccess=true, $titlefromtemplate=false) {
280
281
282
283
284
285
286
287
288
289
290
291
292
        require_once(get_config('libroot') . 'view.php');
        global $SESSION;

        if (is_null($userid)) {
            global $USER;
            $userid = $USER->get('id');
        }

        db_begin();

        $colltemplate = new Collection($templateid);

        $data = new StdClass;
293
294
295
296
297
298
299
300
301
302
303
304
305
        // Set a default name if one wasn't set in $collectiondata
        if ($titlefromtemplate) {
            $data->name = $colltemplate->get('name');
        }
        else if (!isset($collectiondata['name'])) {
            $desiredname = $colltemplate->get('name');
            if (get_config('renamecopies')) {
                $desiredname = get_string('Copyof', 'mahara', $desiredname);
            }
            $data->name = self::new_name($desiredname, (object)$collectiondata);
        }
        else {
            $data->name = $collectiondata['name'];
306
        }
307
        $data->description = $colltemplate->get('description');
308
        $data->tags = $colltemplate->get('tags');
309
        $data->navigation = $colltemplate->get('navigation');
310
311
312
313
314
315
316
317
318
319
320
321
        if (!empty($collectiondata['group'])) {
            $data->group = $collectiondata['group'];
        }
        else if (!empty($collectiondata['institution'])) {
            $data->institution = $collectiondata['institution'];
        }
        else if (!empty($collectiondata['owner'])) {
            $data->owner = $collectiondata['owner'];
        }
        else {
            $data->owner = $userid;
        }
Robert Lyon's avatar
Robert Lyon committed
322
        $data->framework = $colltemplate->get('framework');
323
        $data->submittedstatus = 0;
324
325
326
327
328
329
330

        $collection = self::save($data);

        $numcopied = array('pages' => 0, 'blocks' => 0, 'artefacts' => 0);

        $views = $colltemplate->get('views');
        $copyviews = array();
331
        $evidenceviews = array();
332
        $artefactcopies = array();
333
        foreach ($views['views'] as $v) {
334
335
336
337
338
339
340
            $values = array(
                'new' => true,
                'owner' => isset($data->owner) ? $data->owner : null,
                'group' => isset($data->group) ? $data->group : null,
                'institution' => isset($data->institution) ? $data->institution : null,
                'usetemplate' => $v->view
            );
341
            list($view, $template, $copystatus) = View::create_from_template($values, $v->view, $userid, $checkaccess, $titlefromtemplate, $artefactcopies);
342
343
344
345
            // Check to see if we need to re-map any framework evidence
            if (!empty($data->framework) && $userid == $data->owner && count_records('framework_evidence', 'view', $v->view)) {
                $evidenceviews[$v->view] = $view->get('id');
            }
346
347
348
349
350
351
352
353
354
355
356
357
            if (isset($copystatus['quotaexceeded'])) {
                $SESSION->clear('messages');
                return array(null, $colltemplate, array('quotaexceeded' => true));
            }
            $copyviews['view_' . $view->get('id')] = true;
            $numcopied['blocks'] += $copystatus['blocks'];
            $numcopied['artefacts'] += $copystatus['artefacts'];
        }
        $numcopied['pages'] = count($views['views']);

        $collection->add_views($copyviews);

358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
        // Update all the navigation blocks referring to this collection
        if ($viewids = get_column('collection_view', 'view', 'collection', $collection->get('id'))) {
            $navblocks = get_records_select_array(
                'block_instance',
                'view IN (' . join(',', array_fill(0, count($viewids), '?')) . ") AND blocktype = 'navigation'",
                $viewids
            );

            if ($navblocks) {
                safe_require('blocktype', 'navigation');
                foreach ($navblocks as $b) {
                    $bi = new BlockInstance($b->id, $b);
                    $configdata = $bi->get('configdata');
                    if (isset($configdata['collection']) && $configdata['collection'] == $templateid) {
                        $bi->set('configdata', array('collection' => $collection->get('id')));
                        $bi->commit();
                    }
                }
            }
        }
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
        // If there are views with framework evidence to re-map
        if (!empty($evidenceviews)) {
            // We need to get how the old views/artefacts/blocks/evidence fit together
            $evidences = get_records_sql_array('
                SELECT va.view, va.artefact, va.block, fe.*
                FROM {view} v
                JOIN {view_artefact} va ON va.view = v.id
                JOIN {artefact} a ON a.id = va.artefact
                JOIN {framework_evidence} fe ON fe.view = v.id
                WHERE v.id IN (' . join(',', array_keys($evidenceviews)) . ')
                AND a.id IN (' . join(',', array_keys($artefactcopies)) . ')
                AND fe.annotation = va.block', array());
            $newartefactcopies = array();
            foreach ($artefactcopies as $ac) {
                $newartefactcopies[$ac->newid] = 1;
            }
            // And get how the new views/artefacts/blocks fit together
            $newblocks = get_records_sql_assoc('
                SELECT va.artefact, va.view, va.block
                FROM {view} v
                JOIN {view_artefact} va ON va.view = v.id
                JOIN {artefact} a ON a.id = va.artefact
                WHERE v.id IN (' . join(',', array_values($evidenceviews)) . ')
                AND a.id IN (' . join(',', array_keys($newartefactcopies)) . ')
                AND artefacttype = ?', array('annotation'));

            foreach ($evidences as $evidence) {
                if (key_exists($evidence->artefact, $artefactcopies) && key_exists($artefactcopies[$evidence->artefact]->newid, $newartefactcopies)) {
                    $newartefact = $artefactcopies[$evidence->artefact]->newid;
                    $newevidence = new stdClass();
                    $newevidence->view = $newblocks[$newartefact]->view;
                    $newevidence->artefact = $newartefact;
                    $newevidence->annotation = $newblocks[$newartefact]->block;
                    $newevidence->framework = $evidence->framework;
                    $newevidence->element = $evidence->element;
                    $newevidence->state = 0;
                    $newevidence->reviewer = null;
                    $newevidence->ctime = $evidence->ctime;
                    $newevidence->mtime = $evidence->mtime;
                    insert_record('framework_evidence', $newevidence);
                }
            }
        }
421

422
423
424
425
426
427
428
429
430
        db_commit();

        return array(
            $collection,
            $colltemplate,
            $numcopied,
        );
    }

431
    /**
432
     * Returns a list of the current user, group, or institution collections
433
434
435
     *
     * @param offset current page to display
     * @param limit how many collections to display per page
436
437
     * @param groupid current group ID
     * @param institutionname current institution name
438
439
     * @return array (count: integer, data: array, offset: integer, limit: integer)
     */
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
    public static function get_mycollections_data($offset=0, $limit=10, $owner=null, $groupid=null, $institutionname=null) {
        if (!empty($groupid)) {
            $wherestm = '"group" = ?';
            $values = array($groupid);
            $count  = count_records('collection', 'group', $groupid);
        }
        else if (!empty($institutionname)) {
            $wherestm = 'institution = ?';
            $values = array($institutionname);
            $count  = count_records('collection', 'institution', $institutionname);
        }
        else if (!empty($owner)) {
            $wherestm = 'owner = ?';
            $values = array($owner);
            $count  = count_records('collection', 'owner', $owner);
        }
        else {
            $count = 0;
        }
        $data = array();
        if ($count > 0) {
            $data = get_records_sql_assoc("
462
463
464
465
466
467
468
                SELECT
                    c.id,
                    c.description,
                    c.name,
                    c.submittedgroup,
                    c.submittedhost,
                    c.submittedtime,
469
                    c.framework,
470
                    (SELECT COUNT(*) FROM {collection_view} cv WHERE cv.collection = c.id) AS numviews
471
                FROM {collection} c
472
473
474
475
                WHERE " . $wherestm .
                " ORDER BY c.name, c.ctime, c.id ASC
                ", $values, $offset, $limit);
        }
476

477
        self::add_submission_info($data);
478
        self::add_framework_urls($data);
479

480
        $result = (object) array(
481
482
            'count'  => $count,
            'data'   => $data,
483
484
485
486
487
            'offset' => $offset,
            'limit'  => $limit,
        );
        return $result;
    }
488

489
    private static function add_submission_info(&$data) {
490
491
492
        global $CFG;
        require_once($CFG->docroot . 'lib/group.php');

493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
        if (empty($data)) {
            return;
        }

        $records = get_records_sql_assoc('
            SELECT c.id, c.submittedgroup, c.submittedhost, ' . db_format_tsfield('submittedtime') . ',
                   sg.name AS groupname, sg.urlid, sh.name AS hostname
              FROM {collection} c
         LEFT JOIN {group} sg ON c.submittedgroup = sg.id
         LEFT JOIN {host} sh ON c.submittedhost = sh.wwwroot
             WHERE c.id IN (' . join(',', array_fill(0, count($data), '?')) . ')
               AND (c.submittedgroup IS NOT NULL OR c.submittedhost IS NOT NULL)',
            array_keys($data)
        );

        if (empty($records)) {
            return;
        }

        foreach ($records as $r) {
            if (!empty($r->submittedgroup)) {
                $groupdata = (object) array(
                    'id'    => $r->submittedgroup,
                    'name'  => $r->groupname,
                    'urlid' => $r->urlid,
                    'time'  => $r->submittedtime,
                );
                $groupdata->url = group_homepage_url($groupdata);
                $data[$r->id]->submitinfo = $groupdata;
            }
            else if (!empty($r->submittedhost)) {
                $data[$r->id]->submitinfo = (object) array(
                    'name' => $r->hostname,
                    'url'  => $r->submittedhost,
                    'time'  => $r->submittedtime,
                );
            }
        }
    }

533
534
535
536
    /**
    * Gets the fields for the new/edit collection form
    * - populates the fields with collection data if it is an edit
    *
537
    * @param array $collection
538
539
    * @return array $elements
    */
540
    public function get_collectionform_elements() {
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
        $elements = array(
            'name' => array(
                'type' => 'text',
                'defaultvalue' => null,
                'title' => get_string('name', 'collection'),
                'size' => 30,
                'rules' => array(
                    'required' => true,
                ),
            ),
            'description' => array(
                'type'  => 'textarea',
                'rows' => 10,
                'cols' => 50,
                'resizable' => false,
                'defaultvalue' => null,
                'title' => get_string('description', 'collection'),
            ),
559
560
561
562
563
564
565
            'tags'        => array(
                'type'         => 'tags',
                'title'        => get_string('tags'),
                'description'  => get_string('tagsdescprofile'),
                'defaultvalue' => null,
                'help'         => true,
            ),
566
            'navigation' => array(
567
                'type'  => 'switchbox',
568
569
570
571
                'title' => get_string('viewnavigation','collection'),
                'description' => get_string('viewnavigationdesc','collection'),
                'defaultvalue' => 1,
            ),
572
        );
573
        if ($frameworks = $this->get_available_frameworks()) {
574
575
576
577
578
579
580
581
582
583
584
585
586
            $options = array('' => get_string('noframeworkselected', 'module.framework'));
            foreach ($frameworks as $framework) {
                $options[$framework->id] = $framework->name;
            }
            $elements['framework'] = array(
                'type' => 'select',
                'title' => get_string('Framework', 'module.framework'),
                'options' => $options,
                'defaultvalue' => $this->framework,
                'width' => '280px',
                'description' => get_string('frameworkdesc', 'module.framework'),
            );
        }
587

588
        // populate the fields with the existing values if any
589
        if (!empty($this->id)) {
590
            foreach ($elements as $k => $element) {
591
592
593
594
595
596
                if ($k === 'tags') {
                    $elements[$k]['defaultvalue'] = $this->get_tags();
                }
                else {
                    $elements[$k]['defaultvalue'] = $this->$k;
                }
597
598
599
            }
            $elements['id'] = array(
                'type' => 'hidden',
600
                'value' => $this->id,
601
            );
602
603
604
605
606
607
608
609
610
611
612
613
614
615
        }
        if (!empty($this->group)) {
            $elements['group'] = array(
                'type' => 'hidden',
                'value' => $this->group,
            );
        }
        else if (!empty($this->institution)) {
            $elements['institution'] = array(
                'type' => 'hidden',
                'value' => $this->institution,
            );
        }
        else if (!empty($this->owner)) {
616
617
            $elements['owner'] = array(
                'type' => 'hidden',
618
                'value' => $this->owner,
619
            );
620
621
        }

622
623
        return $elements;
    }
624

625
    /**
626
     * Returns array of views in the current collection
627
     *
Aaron Wells's avatar
Aaron Wells committed
628
     * @return array views
629
     */
630
631
632
    public function views() {

        if (!isset($this->views)) {
633

634
            $sql = "SELECT v.id, cv.*, v.title, v.owner, v.group, v.institution, v.ownerformat, v.urlid
635
636
                FROM {collection_view} cv JOIN {view} v ON cv.view = v.id
                WHERE cv.collection = ?
637
                ORDER BY cv.displayorder, v.title, v.ctime ASC";
638

639
            $result = get_records_sql_assoc($sql, array($this->get('id')));
640

641
            if (!empty($result)) {
642
643
644
645
646
647
648
649
650
                require_once('view.php');
                View::get_extra_view_info($result, false, false);
                $result = array_values($result);
                $max = $min = $result[0]['displayorder'];
                foreach ($result as &$r) {
                    $max = max($max, $r['displayorder']);
                    $min = min($min, $r['displayorder']);
                    $r = (object) $r;
                }
651
                $this->views = array(
652
                    'views'     => array_values($result),
653
                    'count'     => count($result),
654
655
                    'max'       => $max,
                    'min'       => $min,
656
657
658
659
660
                );
            }
            else {
                $this->views = array();
            }
661

662
663
        }

664
        return $this->views;
665
    }
666

667
    /**
668
     * Check that a collection can have a framework
669
670
671
672
673
674
675
676
677
678
679
680
     *
     * @return bool
     */
    public function can_have_framework() {
        return ($this->get_framework_institution()) ? true : false;
    }

    /**
     * Check if any allowed institutions lets a collection have a framework
     * and return first valid one.
     *
     * Checks:
681
     * - The collection is not owned by a group
682
683
     * - The framework plugin is active
     * - The institution has 'SmartEvidence' turned on
684
     * - There are frameworks available for the institutions
685
     *
686
     * @return object $institution or false
687
     */
688
     public function get_framework_institution() {
689
690
691
692
        if (!empty($this->group)) {
            return false;
        }

693
        if (!is_plugin_active('framework', 'module')) {
694
695
            return false;
        }
696
        $allowsmartevidence = false;
697
698
        if ($this->institution) {
            $institution = $this->institution;
699
700
            $institution = new Institution($institution);
            $allowsmartevidence = ($institution->allowinstitutionsmartevidence) ? $institution : false;
701
702
703
704
        }
        else {
            $user = new User();
            $user->find_by_id($this->owner);
705
706
707
708
709
710
711
712
713
714
715
716
717
718
            $institutionids = array_keys($user->get('institutions'));
            if (!empty($institutionids)) {
                foreach ($institutionids as $institution) {
                    $institution = new Institution($institution);
                    if ($institution->allowinstitutionsmartevidence == true) {
                        $allowsmartevidence = $institution;
                        break;
                    }
                }
            }
            else {
                $institution = new Institution('mahara');
                $allowsmartevidence = ($institution->allowinstitutionsmartevidence) ? $institution : false;
            }
719
        }
720
        return $allowsmartevidence;
721
722
723
724
725
726
727
728
    }

    /**
     * Get available frameworks
     *
     * @return array Available frameworks
     */
    public function get_available_frameworks() {
729
730
        $institution = $this->get_framework_institution();
        if (!$institution) {
731
732
733
734
735
736
737
738
739
740
            return array();
        }

        if ($frameworks = Framework::get_frameworks($institution->name, true)) {
            // Inactive frameworks are only allowed if they were added to
            // collection when they were active.
            foreach ($frameworks as $key => $framework) {
                if (empty($framework->active) && $framework->id != $this->framework) {
                    unset ($frameworks[$key]);
                }
741
            }
742
            return $frameworks;
743
        }
744
        return array();
745
746
747
748
749
    }

    /**
     * Check that a collection has a framework
     * - The collection can have a framework
750
751
752
753
754
755
     * - It has a framework id
     * - It has views in the collection
     *
     * @return boolean
     */
    public function has_framework() {
756
        if (!$this->can_have_framework()) {
757
758
759
760
761
762
763
764
            return false;
        }
        if (empty($this->framework)) {
            return false;
        }
        if (!$this->views()) {
            return false;
        }
765
        if (!is_plugin_active('framework', 'module')) {
766
767
            return false;
        }
768
769
770
        return true;
    }

771
772
773
774
775
776
777
778
779
    /**
     * Get collection framework option for collection navigation
     *
     * @return object $option;
     */
    public function collection_nav_framework_option() {
        $option = new StdClass;
        $option->framework = $this->framework;
        $option->id = $this->id;
780
        $option->title = get_field('framework', 'name', 'id', $this->framework);
781
782
        $option->framework = true;

783
        $option->fullurl = self::get_framework_url($option);
784
785
786
787
788

        return $option;
    }

    /**
789
     * Adding the framework frameworkurl / fullurl to collections
790
     *
791
     * @param array  $data    Array of objects
792
     *
793
     * @return $data
794
     */
795
    public static function add_framework_urls(&$data) {
796
797
        if (is_array($data)) {
            foreach ($data as $k => $r) {
798
799
                $r->frameworkurl = self::get_framework_url($r, false);
                $r->fullurl = self::get_framework_url($r, true);
800
801
            }
        }
802
803
804
805
806
807
808
809
810
811
812
813
814
815
    }

    /**
     * Making the framework url
     *
     * @param object $data    Either a collection or standard object
     * @param bool   $fullurl Return full url rather than relative one
     *
     * @return $url
     */
    public static function get_framework_url($data, $fullurl = true) {
        $url = 'module/framework/matrix.php?id=' . $data->id;
        if ($fullurl) {
            return get_config('wwwroot') . $url;
816
        }
817
        return $url;
818
819
    }

820
    /**
821
822
     * Get the available views the current user can choose to add to their collections.
     * Restrictions on this list include:
823
     * - currently dashboard, group and profile views are ignored to solve access issues
824
     * - default pages (with template == 2) are ignored
825
     * - each view can only belong to one collection
826
     * - locked/submitted views can't be added to collections
827
828
829
     *
     * @return array $views
     */
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
    public static function available_views($owner=null, $groupid=null, $institutionname=null) {
        if (!empty($groupid)) {
            $wherestm = '"group" = ?';
            $values = array($groupid);
        }
        else if (!empty($institutionname)) {
            $wherestm = 'institution = ?';
            $values = array($institutionname);
        }
        else if (!empty($owner)) {
            $wherestm = 'owner = ?';
            $values = array($owner);
        }
        else {
            return array();
        }
846
        ($views = get_records_sql_array("SELECT v.id, v.title
847
848
849
850
            FROM {view} v
            LEFT JOIN {collection_view} cv ON cv.view = v.id
            WHERE " . $wherestm .
            "   AND cv.view IS NULL
851
                AND v.type NOT IN ('dashboard','grouphomepage','profile')
852
                AND v.template != 2
853
854
                AND v.submittedgroup IS NULL
                AND v.submittedhost IS NULL
855
856
857
858
            GROUP BY v.id, v.title
            ORDER BY v.title ASC
            ", $values))
            || ($views = array());
859
        return $views;
860
861
    }

862
    /**
863
     * Submits the selected views to the collection
864
865
866
867
868
     *
     * @param array values selected views
     * @return integer count so we know what SESSION message to display
     */
    public function add_views($values) {
869
        require_once(get_config('libroot') . 'view.php');
870

871
        $count = 0; // how many views we are adding
872
873
        db_begin();

874
        // each view was marked with a key of view_<id> in order to identify the correct items
875
        // from the form values
876
877
878
879
880
        foreach ($values as $key => $value) {
            if (substr($key,0,5) === 'view_' AND $value == true) {
                $cv = array();
                $cv['view'] = substr($key,5);
                $cv['collection'] = $this->get('id');
881
882
883
884
885

                // set displayorder value
                $max = get_field('collection_view', 'MAX(displayorder)', 'collection', $this->get('id'));
                $cv['displayorder'] = is_numeric($max) ? $max + 1 : 0;

886
887
888
889
                insert_record('collection_view', (object)$cv);
                $count++;
            }
        }
890

891
        $viewids = get_column('collection_view', 'view', 'collection', $this->id);
892
893

        // Set the most permissive access records on all views
894
        View::combine_access($viewids, true);
895

896
897
898
899
900
901
902
        // Copy the whole view config from the first view to all the others
        if (count($viewids)) {
            $firstview = new View($viewids[0]);
            $viewconfig = array(
                'startdate'       => $firstview->get('startdate'),
                'stopdate'        => $firstview->get('stopdate'),
                'template'        => $firstview->get('template'),
903
                'retainview'      => $firstview->get('retainview'),
904
905
906
907
908
909
910
                'allowcomments'   => $firstview->get('allowcomments'),
                'approvecomments' => (int) ($firstview->get('allowcomments') && $firstview->get('approvecomments')),
                'accesslist'      => $firstview->get_access(),
            );
            View::update_view_access($viewconfig, $viewids);
        }

911
912
913
        // Now that we have added views to the collection we need to update the collection modified date
        $this->mtime = db_format_timestamp(time());
        $this->commit();
914
915
        db_commit();

916
917
        return $count;
    }
918

919
    /**
920
     * Removes the selected views from the collection
921
922
923
924
925
     *
     * @param integer $view the view to remove
     */
    public function remove_view($view) {
        db_begin();
926
927
928
929
930
931
932
933

        $position = get_field_sql('
            SELECT displayorder FROM {collection_view}
                WHERE collection = ?
                AND view = ?',
                array($this->get('id'),$view)
        );

934
        delete_records('collection_view','view',$view,'collection',$this->get('id'));
935

936
        $this->update_display_order($position);
937
938
939
940
        // Secret url records belong to the collection, so remove them from the view.
        // @todo: add user message to whatever calls this.
        delete_records_select('view_access', 'view = ? AND token IS NOT NULL', array($view));

941
942
943
944
        // Now that we have removed views from the collection we need to update the collection modified date
        $this->mtime = db_format_timestamp(time());
        $this->commit();

945
946
947
        db_commit();
    }

948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
    /**
     * Updates the position number of the views in a collection
     *
     * @param integer $start position from where to start updating
     */
    public function update_display_order($start = 0) {
        $start = intval($start);
        $ids = get_column_sql('
                SELECT view FROM {collection_view}
                WHERE collection = ?
                ORDER BY displayorder', array($this->get('id')));
        foreach ($ids as $k => $v) {
            if ($start <= $k) {
                set_field('collection_view', 'displayorder', $k, 'view', $v, 'collection',$this->get('id'));
            }
        }
    }

966
967
968
    /**
     * Sets the displayorder for a view
     *
969
970
971
972
     * @param integer   $id   view id
     * @param mixed  direction - either string consisting 'up' or 'down' to
     *               indicate which way to move $id item, or an array containing
     *               the ids in order you want them saved
973
974
     */
    public function set_viewdisplayorder($id, $direction) {
975
976
977
        if (is_array($direction)) {
            // we already have new sort order
            $neworder = $direction;
978
        }
979
980
981
982
983
984
985
986
987
988
989
990
        else {
            $ids = get_column_sql('
                SELECT view FROM {collection_view}
                WHERE collection = ?
                ORDER BY displayorder', array($this->get('id')));

            foreach ($ids as $k => $v) {
                if ($v == $id) {
                    $oldorder = $k;
                    break;
                }
            }
991

992
993
994
995
996
997
998
999
1000
1001
            if ($direction == 'up' && $oldorder > 0) {
                $neworder = array_merge(array_slice($ids, 0, $oldorder - 1),
                                        array($id, $ids[$oldorder-1]),
                                        array_slice($ids, $oldorder+1));
            }
            else if ($direction == 'down' && ($oldorder + 1 < count($ids))) {
                $neworder = array_merge(array_slice($ids, 0, $oldorder),
                                        array($ids[$oldorder+1], $id),
                                        array_slice($ids, $oldorder+2));
            }
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
        }
        if (isset($neworder)) {
            foreach ($neworder as $k => $v) {
                set_field('collection_view', 'displayorder', $k, 'view', $v, 'collection',$this->get('id'));
            }
            $this->set('mtime', time());
            $this->commit();
        }
    }

1012
1013
1014
    /**
     * after editing the collection, redirect back to the appropriate place
     */
1015
1016
1017
1018
    public function post_edit_redirect($new=false, $copy=false, $urlparams=null) {
        if ($new || $copy) {
            $urlparams['id'] = $this->get('id');
            $redirecturl = '/collection/views.php';
1019
1020
        }
        else {
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
            if ($this->get('group')) {
                // Group owned collection
                $redirecturl = '/view/groupviews.php';
            }
            else if ($this->get('institution')) {
                if ($this->get('institution') == 'mahara') {
                    // Site owned collection
                    $redirecturl = '/admin/site/views.php';
                }
                else {
                    // Institution owned collection
                    $redirecturl = '/view/institutionviews.php';
                }
            }
            else {
                // User owned collection
                $redirecturl = '/view/index.php';
            }
1039
        }
1040
1041
1042
        if ($urlparams) {
            $redirecturl .= '?' . http_build_query($urlparams);
        }
1043
1044
1045
        redirect($redirecturl);
    }

1046
    public static function search_by_view_id($viewid) {
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
        $record = get_record_sql('
            SELECT c.*
            FROM {collection} c JOIN {collection_view} cv ON c.id = cv.collection
            WHERE cv.view = ?',
            array($viewid)
        );
        if ($record) {
            return new Collection(0, $record);
        }
        return false;
    }

1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
    /**
     * Returns an SQL snippet that can be used in a where clause to get views
     * with the given owner.
     *
     * @param object $ownerobj An object that has view ownership information -
     *                         either the institution, group or owner fields set
     * @return string
     */
    private static function owner_sql($ownerobj) {
        if (isset($ownerobj->institution)) {
            return 'institution = ' . db_quote($ownerobj->institution);
        }
        if (isset($ownerobj->group) && is_numeric($ownerobj->group)) {
            return '"group" = ' . (int)$ownerobj->group;
        }
        if (isset($ownerobj->owner) && is_numeric($ownerobj->owner)) {
            return 'owner = ' . (int)$ownerobj->owner;
        }
        throw new SystemException("View::owner_sql: Passed object did not have an institution, group or owner field");
    }
1079
1080
1081
1082
1083
1084
1085
1086
1087

    /**
     * Makes a URL for a collection
     *
     * @param bool $full return a full url
     * @param bool $useid ignore clean url settings and always return a url with an id in it
     *
     * @return string
     */
1088
    public function get_url($full=true, $useid=false, &$firstview=null) {
1089
        global $USER;
1090
        $firstview = null;
1091
1092
1093

        $views = $this->views();
        if (!empty($views)) {
1094
            if ($this->framework) {
1095
1096
                if ($full) {
                    $this->fullurl = Collection::get_framework_url($this);
1097
                    return $this->fullurl;
1098
1099
1100
                }
                else {
                    $this->frameworkurl = Collection::get_framework_url($this, false);
1101
                    return $this->frameworkurl;
1102
                }
1103
1104
            }

1105
1106
            $v = new View(0, $views['views'][0]);
            $v->set('dirty', false);
1107
            $firstview = $v;
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
            return $v->get_url($full, $useid);
        }

        log_warn("Attempting to get url for an empty collection");

        if ($this->owner === $USER->get('id')) {
            $url = 'collection/views.php?id=' . $this->id;
        }
        else {
            $url = '';
        }

        if ($full) {
            $url = get_config('wwwroot') . $url;
        }

        return $url;
    }

1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
    /**
     * Sets released submissions to pending release status and adds
     * the submission item to the export queue ready for archiving.
     *
     * @param object $releaseuser The user releasing the collection
     */
    public function pendingrelease($releaseuser=null) {
        $submitinfo = $this->submitted_to();
        if (!$this->is_submitted()) {
            throw new ParameterException("Collection with id " . $this->id . " has not been submitted");
        }
        $viewids = $this->get_viewids();
        db_begin();
        execute_sql("UPDATE {collection}
                     SET submittedstatus = " . self::PENDING_RELEASE . "
                     WHERE id = ?",
                     array($this->id)
        );
        View::_db_pendingrelease($viewids);
        db_commit();

        require_once(get_config('docroot') . 'export/lib.php');
        add_submission_to_export_queue($this, $releaseuser);
    }

1152
1153
1154
1155
1156
1157
    /**
     * Release a submitted collection
     *
     * @param object $releaseuser The user releasing the collection
     */
    public function release($releaseuser=null) {
1158
1159

        if (!$this->is_submitted()) {
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
            throw new ParameterException("Collection with id " . $this->id . " has not been submitted");
        }

        // One day there might be group and institution collections, so be safe
        if (empty($this->owner)) {
            throw new ParameterException("Collection with id " . $this->id . " has no owner");
        }

        $viewids = $this->get_viewids();

        db_begin();
        execute_sql('
            UPDATE {collection}
1173
1174
1175
1176
            SET submittedgroup = NULL,
                submittedhost = NULL,
                submittedtime = NULL,
                submittedstatus = ' . self::UNSUBMITTED . '
1177
1178
1179
1180
1181
1182
            WHERE id = ?',
            array($this->id)
        );
        View::_db_release($viewids, $this->owner, $this->submittedgroup);
        db_commit();

1183
1184
1185
1186
1187
        handle_event('releasesubmission', array('releaseuser' => $releaseuser,
                                                'id' => $this->get('id'),
                                                'groupname' => $this->submittedgroup,
                                                'eventfor' => 'collection'));

1188
1189
        // We don't send out notifications about the release of remote-submitted Views & Collections
        // (though I'm not sure why)
1190
1191
        // if the method is called in an upgrade and we dont have a release user
        if (!defined('INSTALLER') && $this->submittedgroup) {
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
            $releaseuser = optional_userobj($releaseuser);
            $releaseuserdisplay = display_name($releaseuser, $this->owner);
            $submitinfo = $this->submitted_to();

            require_once('activity.php');
            activity_occurred(
                'maharamessage',
                array(
                    'users' => array($this->get('owner')),
                    'strings' => (object) array(
                        'subject' => (object) array(
1203
                            'key'     => 'collectionreleasedsubject1',
1204
1205
1206
1207
                            'section' => 'group',
                            'args'    => array($this->name, $submitinfo->name, $releaseuserdisplay),
                        ),
                        'message' => (object) array(
1208
                            'key'     => 'collectionreleasedmessage1',
1209
1210
1211
                            'section' => 'group',
                            'args'    => array($this->name, $submitinfo->name, $releaseuserdisplay),
                        ),
1212
                    ),
1213
1214
1215
1216
1217
                    'url' => $this->get_url(false),
                    'urltext' => $this->name,
                )
            );
        }
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
    }

    public function get_viewids() {
        $ids = array();
        $viewdata = $this->views();

        if (!empty($viewdata['views'])) {
            foreach ($viewdata['views'] as $v) {
                $ids[] = $v->id;
            }
        }

        return $ids;
    }
1232
1233
1234
1235
1236

    public function is_submitted() {
        return $this->submittedgroup || $this->submittedhost;
    }

1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
    public function submitted_to() {
        if ($this->submittedgroup) {
            $record = get_record('group', 'id', $this->submittedgroup, null, null, null, null, 'id, name, urlid');
            $record->url = group_homepage_url($record);
        }
        else if ($this->submittedhost) {
            $record = get_record('host', 'wwwroot', $this->submittedhost, null, null, null, null, 'wwwroot, name');
            $record->url = $record->wwwroot;
        }
        else {
            throw new SystemException("Collection with id " . $this->id . " has not been submitted");
        }

        return $record;
    }

1253
1254
1255
1256
1257
1258
1259
1260
    /**
     * Submit this collection to a group or a remote host (but only one or the other!)
     * @param object $group
     * @param string $submittedhost
     * @param int $owner The owner of the collection (if not just $USER)
     * @throws SystemException
     */
    public function submit($group = null, $submittedhost = null, $owner = null) {
1261
1262
1263
        global $USER;

        if ($this->is_submitted()) {
1264
1265
1266
1267
1268
            throw new CollectionSubmissionException(get_string('collectionalreadysubmitted', 'view'));
        }
        // Gotta provide one or the other
        if (!$group && !$submittedhost) {
            return false;
1269
1270
1271
        }

        $viewids = $this->get_viewids();
1272
1273
1274
        if (!$viewids) {
            throw new CollectionSubmissionException(get_string('cantsubmitemptycollection', 'view'));
        }
1275
        $idstr = join(',', array_map('intval', $viewids));
1276
        $owner = ($owner == null) ? $USER->get('id') : $owner;
1277
1278
1279
1280

        // Check that none of the views is submitted to some other group.  This is bound to happen to someone,
        // because collection submission is being introduced at a time when it is still possible to submit
        // individual views in a collection.
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
        $sql = "SELECT title FROM {view} WHERE id IN ({$idstr}) AND (submittedhost IS NOT NULL OR (submittedgroup IS NOT NULL";
        $params = array();
        // To ease the transition, if you've submitted one page of the collection to this group already, you
        // can submit the rest as well
        if ($group) {
            $sql .= ' AND submittedgroup != ?';
            $params[] = $group->id;
        }
        $sql .= '))';
        $submittedtitles = get_column_sql($sql, $params );