lib.php 51.5 KB
Newer Older
1
2
3
4
5
<?php
/**
 *
 * @package    mahara
 * @subpackage artefact-blog
6
 * @author     Catalyst IT Ltd
7
8
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL version 3 or later
 * @copyright  For copyright information on Mahara, please see the README file distributed with this software.
9
10
11
12
13
 *
 */

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

Aaron Wells's avatar
Aaron Wells committed
14
/**
15
16
17
18
19
20
21
22
23
24
 * Users can create blogs and blog posts using this plugin.
 */
class PluginArtefactBlog extends PluginArtefact {

    public static function get_artefact_types() {
        return array(
            'blog',
            'blogpost',
        );
    }
Aaron Wells's avatar
Aaron Wells committed
25

26
27
28
    public static function get_block_types() {
        return array();
    }
29
30
31
32
33

    public static function get_plugin_name() {
        return 'blog';
    }

34
35
36
37
    public static function admin_menu_items() {
        $map['manageinstitutions/blogs'] = array(
            'path'   => 'manageinstitutions/blogs',
            'url'    => 'artefact/blog/index.php?institution=1',
38
            'title'  => get_string('Blogs', 'artefact.blog'),
39
40
41
42
43
            'weight' => 75,
        );
        $map['configsite/blogs'] = array(
            'path'   => 'configsite/blogs',
            'url'    => 'artefact/blog/index.php?institution=mahara',
44
            'title'  => get_string('Blogs', 'artefact.blog'),
45
46
47
48
49
50
51
52
53
54
55
56
57
            'weight' => 65,
        );

        if (defined('MENUITEM') && isset($map[MENUITEM])) {
            $map[MENUITEM]['selected'] = true;
        }
        return $map;
    }

    public static function institution_menu_items() {
        return self::admin_menu_items();
    }

58
    public static function set_blog_nav($institution = false, $institutionname = null, $groupid = null) {
59
60
61
62
63
64
65
66
        if ($institutionname == 'mahara') {
            define('ADMIN', 1);
            define('MENUITEM', 'configsite/blogs');
        }
        else if ($institution) {
            define('INSTITUTIONALADMIN', 1);
            define('MENUITEM', 'manageinstitutions/blogs');
        }
67
68
        else if ($groupid) {
            define('GROUP', $groupid);
69
            define('MENUITEM', 'engage/index');
70
            define('MENUITEM_SUBPAGE', 'blogs');
71
        }
72
        else {
73
            define('MENUITEM', 'create/blogs');
74
75
76
        }
    }

77
78
79
80
    public static function is_active() {
        return get_field('artefact_installed', 'active', 'name', 'blog');
    }

81
    public static function menu_items() {
82
        global $USER;
83
        $tab = array(
84
85
            'path'   => 'create/blogs',
            'weight' => 30,
86
            'url'    => 'artefact/blog/index.php',
87
            'title'  => get_string('Blogs', 'artefact.blog'),
88
        );
89
        return array('create/blogs' => $tab);
90
    }
91

92
    public static function get_cron() {
93
        return array();
94
95
    }

96

97
98
99
100
101
102
103
104
105
106
    public static function get_event_subscriptions() {
        return array(
            (object)array(
                'plugin'       => 'blog',
                'event'        => 'createuser',
                'callfunction' => 'create_default_blog',
            ),
        );
    }

107
    public static function block_advanced_options_element($configdata, $artefacttype) {
108
        $strartefacttype = strtolower(get_string($artefacttype, 'artefact.blog'));
109
110
111
112
113
114
115
116
117
118

        $options = array('nocopy' => get_string('copynocopy', 'artefact.blog'));
        if ($artefacttype == 'taggedposts') {
            $options['tagsonly'] = get_string('copytagsonly', 'artefact.blog', $strartefacttype);
        }
        else {
            $options['reference'] = get_string('copyreference', 'artefact.blog', $strartefacttype);
            $options['full'] = get_string('copyfull', 'artefact.blog', $strartefacttype);
        }

119
120
121
        return array(
            'type' => 'fieldset',
            'name' => 'advanced',
122
            'class' => 'first last',
123
124
            'collapsible' => true,
            'collapsed' => false,
125
            'legend' => get_string('moreoptions', 'artefact.blog'),
126
127
128
129
130
131
            'elements' => array(
                'copytype' => array(
                    'type' => 'select',
                    'title' => get_string('blockcopypermission', 'view'),
                    'description' => get_string('blockcopypermissiondesc', 'view'),
                    'defaultvalue' => isset($configdata['copytype']) ? $configdata['copytype'] : 'nocopy',
132
                    'options' => $options,
133
134
135
136
137
                ),
            ),
        );
    }

138
139
140
141
    public static function create_default_blog($event, $user) {
        $name = display_name($user, null, true);
        $blog = new ArtefactTypeBlog(0, (object) array(
            'title'       => get_string('defaultblogtitle', 'artefact.blog', $name),
142
            'owner'       => is_object($user) ? $user->id : $user['id'],
143
144
145
        ));
        $blog->commit();
    }
146
147
148

    public static function get_artefact_type_content_types() {
        return array(
149
150
            'blog' => array('blog'),
            'blogpost' => array('blogpost'),
151
152
        );
    }
153

154
    public static function progressbar_link($artefacttype) {
155
156
        return 'artefact/blog/view/index.php';
    }
157

158
159
160
161
162
163
164
165
166
167
168
169
170
171
    public static function group_tabs($groupid, $role) {
        if ($role) {
            return array(
                'blogs' => array(
                    'path' => 'groups/blogs',
                    'url' => 'artefact/blog/index.php?group=' . $groupid,
                    'title' => get_string('Blogs', 'artefact.blog'),
                    'weight' => 65,
                ),
            );
        }
        else {
            return array();
        }
172
    }
173
174
175
176
177
178
179
}

/**
 * A Blog artefact is a collection of BlogPost artefacts.
 */
class ArtefactTypeBlog extends ArtefactType {

Alastair Pharo's avatar
Alastair Pharo committed
180
181
182
183
    /**
     * This constant gives the per-page pagination for listing blogs.
     */
    const pagination = 10;
184
185


186
187
188
189
190
191
192
193
    /**
     * We override the constructor to fetch the extra data.
     *
     * @param integer
     * @param object
     */
    public function __construct($id = 0, $data = null) {
        parent::__construct($id, $data);
194
195
196
197
198
199

        if (empty($this->id)) {
            $this->container = 1;
        }
    }

200
201
202
203
    public static function is_allowed_in_progressbar() {
        return false;
    }

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
233
234
235
236
237
238
    public function display_title($maxlen=null) {
        global $USER;
        $title = $this->get('title');
        // Check if we are displaying title to anonymous user
        // And the blog we are showing is the default one named
        // after the user.
        if (!$USER->is_logged_in()) {
            $owner = new User;
            $owner->find_by_id($this->get('owner'));
            if (preg_match('/^' . preg_quote($owner->get('firstname') . ' ' . $owner->get('lastname') . '/'), $title)) {
                $title = get_string('Blog', 'artefact.blog');
            }
        }
        if ($maxlen) {
            return str_shorten_text($title, $maxlen, true);
        }
        return $title;
    }

    public function display_postedby($date, $by) {
        global $USER;

        if (!is_numeric($date)) {
            // convert any formatted dates back to time
            $date = strtotime($date);
        }

        if ($USER->is_logged_in()) {
            return get_string('postedbyon', 'artefact.blog', $by, format_date($date));
        }
        else {
            return get_string('postedon', 'artefact.blog') . ' ' . format_date($date);
        }
    }

Alastair Pharo's avatar
Alastair Pharo committed
239
    /**
240
241
242
     * This function updates or inserts the artefact.  This involves putting
     * some data in the artefact table (handled by parent::commit()), and then
     * some data in the artefact_blog_blog table.
Alastair Pharo's avatar
Alastair Pharo committed
243
     */
244
    public function commit() {
245
246
247
248
        // Just forget the whole thing when we're clean.
        if (empty($this->dirty)) {
            return;
        }
Aaron Wells's avatar
Aaron Wells committed
249

250
251
        // We need to keep track of newness before and after.
        $new = empty($this->id);
Aaron Wells's avatar
Aaron Wells committed
252

253
254
255
256
        // Commit to the artefact table.
        parent::commit();

        $this->dirty = false;
257
258
    }

Alastair Pharo's avatar
Alastair Pharo committed
259
    /**
260
261
     * This function extends ArtefactType::delete() by deleting blog-specific
     * data.
Alastair Pharo's avatar
Alastair Pharo committed
262
     */
263
    public function delete() {
264
265
266
267
        if (empty($this->id)) {
            return;
        }

268
269
270
271
        db_begin();
        // Delete embedded images in the blog description
        require_once('embeddedimage.php');
        EmbeddedImage::delete_embedded_images('blog', $this->id);
272
273
        // Delete the artefact and all children.
        parent::delete();
274
        db_commit();
275
276
    }

277
    /**
278
279
280
281
282
283
     * Checks that the person viewing a personal blog is the owner.
     * Or the person is an institution admin for an institution blog.
     * Or a group member if viewing a group blog.
     * Or a group member with editing permissions if editing a blog.
     * If not, throws an AccessDeniedException.
     * Other people see blogs when they are placed in views.
284
     */
285
    public function check_permission($editing=false) {
286
        global $USER;
287

288
289
290
291
292
293
294
295
        if (!empty($this->institution)) {
            if ($this->institution == 'mahara' && !$USER->get('admin')) {
                throw new AccessDeniedException(get_string('youarenotasiteadmin', 'artefact.blog'));
            }
            else if (!$USER->get('admin') && !$USER->is_institutional_admin($this->institution)) {
                throw new AccessDeniedException(get_string('youarenotanadminof', 'artefact.blog', $this->institution));
            }
        }
296
        else if (!empty($this->group)) {
297
            $group = get_group_by_id($this->group);
298
299
300
301
302
303
304
305
306
            $USER->reset_grouproles();
            if (!isset($USER->grouproles[$this->group])) {
                throw new AccessDeniedException(get_string('youarenotamemberof', 'artefact.blog', $group->name));
            }
            require_once('group.php');
            if ($editing && !group_role_can_edit_views($this->group, $USER->grouproles[$this->group])) {
                throw new AccessDeniedException(get_string('youarenotaneditingmemberof', 'artefact.blog', $group->name));
            }
        }
307
308
309
310
        else {
            if ($USER->get('id') != $this->owner) {
                throw new AccessDeniedException(get_string('youarenottheownerofthisblog', 'artefact.blog'));
            }
311
312
313
        }
    }

314

315
316
    public function describe_size() {
        return $this->count_children() . ' ' . get_string('posts', 'artefact.blog');
317
318
    }

319
    /**
320
     * Renders a blog.
321
322
323
324
325
     *
     * @param  array  Options for rendering
     * @return array  A two key array, 'html' and 'javascript'.
     */
    public function render_self($options) {
326
327
328
329
330
331
332
333
334
335
336
        if (!isset($options['limit'])) {
            $limit = self::pagination;
        }
        else if ($options['limit'] === false) {
            $limit = null;
        }
        else {
            $limit = (int) $options['limit'];
        }
        $offset = isset($options['offset']) ? intval($options['offset']) : 0;

337
338
339
340
341
        if (!isset($options['countcomments'])) {
            // Count comments if this is a view
            $options['countcomments'] = (!empty($options['viewid']));
        }

342
343
344
345
        $posts = ArtefactTypeBlogpost::get_posts($this->id, $limit, $offset, $options);

        $template = 'artefact:blog:viewposts.tpl';

346
        $baseurl = get_config('wwwroot') . 'artefact/artefact.php?artefact=' . $this->id;
347
348
349
        if (!empty($options['viewid'])) {
            $baseurl .= '&view=' . $options['viewid'];
        }
350
351
352
353
354
355
356
357
358
        $pagination = array(
            'baseurl' => $baseurl,
            'id' => 'blogpost_pagination',
            'datatable' => 'postlist',
            'jsonscript' => 'artefact/blog/posts.json.php',
        );

        ArtefactTypeBlogpost::render_posts($posts, $template, $options, $pagination);

359
360
        $smarty = smarty_core();
        if (isset($options['viewid'])) {
361
            $smarty->assign('artefacttitle', '<a href="' . get_config('wwwroot') . 'artefact/artefact.php?artefact='
362
                                             . $this->get('id') . '&view=' . $options['viewid']
Richard Mansfield's avatar
Richard Mansfield committed
363
                                             . '">' . hsc($this->get('title')) . '</a>');
364
            $smarty->assign('view', $options['viewid']);
365
366
        }
        else {
Richard Mansfield's avatar
Richard Mansfield committed
367
            $smarty->assign('artefacttitle', hsc($this->get('title')));
368
            $smarty->assign('view', null);
369
370
        }

371
372
373
374
375
376
377
        if (!empty($options['details']) and get_config('licensemetadata')) {
            $smarty->assign('license', render_license($this));
        }
        else {
            $smarty->assign('license', false);
        }

378
        $options['hidetitle'] = true;
379
        $smarty->assign('options', $options);
380
        $smarty->assign('description', $this->get('description'));
381
382
        $smarty->assign('owner', $this->get('owner'));
        $smarty->assign('tags', $this->get('tags'));
383

384
        $smarty->assign('posts', $posts);
385

386
        return array('html' => $smarty->fetch('artefact:blog:blog.tpl'), 'javascript' => '');
387
388
    }

Aaron Wells's avatar
Aaron Wells committed
389

390
    public static function get_icon($options=null) {
391
        global $THEME;
392
        return false;
393
394
    }

Nigel McNie's avatar
Nigel McNie committed
395
    public static function is_singular() {
Penny Leach's avatar
Penny Leach committed
396
397
398
        return false;
    }

Alastair Pharo's avatar
Alastair Pharo committed
399
    public static function collapse_config() {
400
401
    }

402
403
404
405
    public function can_have_attachments() {
        return true;
    }

Alastair Pharo's avatar
Alastair Pharo committed
406
    /**
407
     * This function returns a list of the given blogs.
Alastair Pharo's avatar
Alastair Pharo committed
408
409
410
411
     *
     * @param User
     * @return array (count: integer, data: array)
     */
412
    public static function get_blog_list($limit, $offset, $institution = null, $group = null) {
413
        global $USER;
414
415
416
417
418
419
420
421
422

        $sql = "SELECT b.id, b.title, b.description, b.locked, COUNT(p.id) AS postcount
                FROM {artefact} b LEFT JOIN {artefact} p ON (p.parent = b.id AND p.artefacttype = 'blogpost')
                WHERE b.artefacttype = 'blog'";
        if ($institution) {
            $sql .= ' AND b.institution = ?';
            $values = array($institution);
            $count = (int)get_field('artefact', 'COUNT(*)', 'institution', $institution, 'artefacttype', 'blog');
        }
423
424
425
426
        else if ($group) {
            $sql .= ' AND b.group = ?';
            $values = array($group);
            $count = (int)get_field('artefact', 'COUNT(*)', 'group', $group, 'artefacttype', 'blog');
427
            $groupdata = get_group_by_id($group, false, true, true);
428
        }
429
430
431
432
433
434
435
        else {
            $sql .= ' AND b.owner = ?';
            $values = array($USER->get('id'));
            $count = (int)get_field('artefact', 'COUNT(*)', 'owner', $USER->get('id'), 'artefacttype', 'blog');
        }
        $sql .= " GROUP BY b.id, b.title, b.description, b.locked ORDER BY b.title";
        ($result = get_records_sql_array($sql, $values, $offset, $limit))
Alastair Pharo's avatar
Alastair Pharo committed
436
437
            || ($result = array());

438
439
        foreach ($result as &$r) {
            if (!$r->locked) {
440
                $r->deleteform = ArtefactTypeBlog::delete_form($r->id, $r->title);
441
            }
442
            $r->canedit = (!empty($groupdata) ? $groupdata->canedit : true);
443
444
        }

Alastair Pharo's avatar
Alastair Pharo committed
445
446
447
        return array($count, $result);
    }

448
449
    public static function build_blog_list_html(&$blogs) {
        $smarty = smarty_core();
450
        $smarty->assign('blogs', $blogs);
451
452
        $blogs->tablerows = $smarty->fetch('artefact:blog:bloglist.tpl');
        $pagination = build_pagination(array(
453
454
455
            'id' => 'bloglist_pagination',
            'class' => 'center',
            'url' => get_config('wwwroot') . 'artefact/blog/index.php',
456
457
            'jsonscript' => 'artefact/blog/index.json.php',
            'datatable' => 'bloglist',
458
459
460
            'count' => $blogs->count,
            'limit' => $blogs->limit,
            'offset' => $blogs->offset,
461
            'setlimit' => true,
462
463
            'jumplinks' => 6,
            'numbersincludeprevnext' => 2,
464
465
466
            'resultcounttextsingular' => get_string('blog', 'artefact.blog'),
            'resultcounttextplural' => get_string('blogs', 'artefact.blog'),
        ));
467
468
        $blogs->pagination = $pagination['html'];
        $blogs->pagination_js = $pagination['javascript'];
469
470
    }

Alastair Pharo's avatar
Alastair Pharo committed
471
472
473
    /**
     * This function creates a new blog.
     *
474
     * @param User or null
Alastair Pharo's avatar
Alastair Pharo committed
475
476
     * @param array
     */
477
    public static function new_blog($user, array $values) {
478
479
        require_once('embeddedimage.php');
        db_begin();
Alastair Pharo's avatar
Alastair Pharo committed
480
481
482
        $artefact = new ArtefactTypeBlog();
        $artefact->set('title', $values['title']);
        $artefact->set('description', $values['description']);
483
484
485
        if (!empty($values['institution'])) {
            $artefact->set('institution', $values['institution']);
        }
486
487
488
        else if (!empty($values['group'])) {
            $artefact->set('group', $values['group']);
        }
489
490
491
        else {
            $artefact->set('owner', $user->get('id'));
        }
492
        $artefact->set('tags', $values['tags']);
493
494
495
496
497
        if (get_config('licensemetadata')) {
            $artefact->set('license', $values['license']);
            $artefact->set('licensor', $values['licensor']);
            $artefact->set('licensorurl', $values['licensorurl']);
        }
Alastair Pharo's avatar
Alastair Pharo committed
498
        $artefact->commit();
499
500
501
502
        $blogid = $artefact->get('id');
        $newdescription = EmbeddedImage::prepare_embedded_images($artefact->get('description'), 'blog', $blogid);
        $artefact->set('description', $newdescription);
        db_commit();
503
        return $blogid;
504
    }
Alastair Pharo's avatar
Alastair Pharo committed
505
506
507
508
509
510
511
512

    /**
     * This function updates an existing blog.
     *
     * @param User
     * @param array
     */
    public static function edit_blog(User $user, array $values) {
513
        require_once('embeddedimage.php');
Alastair Pharo's avatar
Alastair Pharo committed
514
515
516
517
518
        if (empty($values['id']) || !is_numeric($values['id'])) {
            return;
        }

        $artefact = new ArtefactTypeBlog($values['id']);
519
520
521
        $institution = !empty($values['institution']) ? $values['institution'] : null;
        $group = !empty($values['group']) ? $values['group'] : null;
        if (!self::can_edit_blog($artefact, $institution, $group)) {
522
523
            return;
        }
Alastair Pharo's avatar
Alastair Pharo committed
524
        $artefact->set('title', $values['title']);
525
526
        $newdescription = EmbeddedImage::prepare_embedded_images($values['description'], 'blog', $values['id']);
        $artefact->set('description', $newdescription);
527
        $artefact->set('tags', $values['tags']);
528
529
530
531
532
        if (get_config('licensemetadata')) {
            $artefact->set('license', $values['license']);
            $artefact->set('licensor', $values['licensor']);
            $artefact->set('licensorurl', $values['licensorurl']);
        }
Alastair Pharo's avatar
Alastair Pharo committed
533
534
        $artefact->commit();
    }
535

Martyn Smith's avatar
Martyn Smith committed
536
537
    public static function get_links($id) {
        $wwwroot = get_config('wwwroot');
538

Martyn Smith's avatar
Martyn Smith committed
539
        return array(
540
541
            '_default'                                  => $wwwroot . 'artefact/blog/view/index.php?id=' . $id,
            get_string('blogsettings', 'artefact.blog') => $wwwroot . 'artefact/blog/settings/index.php?id=' . $id,
Martyn Smith's avatar
Martyn Smith committed
542
543
        );
    }
544
545
546
547
548

    public function copy_extra($new) {
        $new->set('title', get_string('Copyof', 'mahara', $this->get('title')));
    }

549
550
551
    /**
     * Returns the number of posts in this blog that have been published.
     *
Aaron Wells's avatar
Aaron Wells committed
552
     * The result of this function looked up from the database each time, so
553
554
555
556
557
558
559
560
561
562
563
564
565
     * cache it if you know it's safe to do so.
     *
     * @return int
     */
    public function count_published_posts() {
        return (int)get_field_sql("
            SELECT COUNT(*)
            FROM {artefact} a
            LEFT JOIN {artefact_blog_blogpost} bp ON a.id = bp.blogpost
            WHERE a.parent = ?
            AND bp.published = 1", array($this->get('id')));
    }

566
    public static function delete_form($id, $title = '') {
567
        global $THEME;
568
569

        $confirm = get_string('deleteblog?', 'artefact.blog');
570
        $title = hsc($title);
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
        // Check if this blog has posts.
        $postcnt = count_records_sql("
            SELECT COUNT(*)
            FROM {artefact} a
            INNER JOIN {artefact_blog_blogpost} bp ON a.id = bp.blogpost
            WHERE a.parent = ?
            ", array($id));
        if ($postcnt > 0) {
            $confirm = get_string('deletebloghaspost?', 'artefact.blog', $postcnt);

            // Check if this blog posts used in views.
            $viewscnt = count_records_sql("
                SELECT COUNT(DISTINCT(va.view))
                FROM {artefact} a
                INNER JOIN {view_artefact} va ON a.id = va.artefact
                WHERE a.parent = ? OR a.id = ?
                ", array($id, $id));
            if ($viewscnt > 0) {
                $confirm = get_string('deletebloghasview?', 'artefact.blog', $viewscnt);
            }
        }
592
593
594
        return pieform(array(
            'name' => 'delete_' . $id,
            'successcallback' => 'delete_blog_submit',
Pat Kira's avatar
Pat Kira committed
595
596
            'renderer' => 'div',
            'class' => 'form-as-button pull-left btn-group-item',
597
598
            'elements' => array(
                'submit' => array(
599
600
                    'type' => 'button',
                    'usebuttontag' => true,
Pat Kira's avatar
Pat Kira committed
601
                    'class' => 'btn-default btn-sm last',
602
603
                    'alt' => get_string('deletespecific', 'mahara', $title),
                    'elementtitle' => get_string('delete'),
604
                    'confirm' => $confirm,
605
                    'value' => '<span class="icon icon-trash icon-lg text-danger" role="presentation" aria-hidden="true"></span><span class="sr-only">' . get_string('deletespecific', 'mahara', $title) . '</span>',
Pat Kira's avatar
Pat Kira committed
606
607
608
609
                ),
                'delete' => array(
                    'type' => 'hidden',
                    'value' => $id,
610
611
612
613
                ),
            ),
        ));
    }
614

615
616
617
618
619
620
621
622
623
    public function update_artefact_references(&$view, &$template, &$artefactcopies, $oldid) {
        parent::update_artefact_references($view, $template, $artefactcopies, $oldid);
        // Update <img> tags in the blog description to refer to the new image artefacts.
        $regexp = array();
        $replacetext = array();
        if (isset($artefactcopies[$oldid]->oldembeds)) {
            foreach ($artefactcopies[$oldid]->oldembeds as $a) {
                if (isset($artefactcopies[$a])) {
                    // Change the old image id to the new one
624
625
                    $regexp[] = '#<img([^>]+)src="' . get_config('wwwroot') . 'artefact/file/download.php\?file=' . $a . '([^0-9])#';
                    $replacetext[] = '<img$1src="' . get_config('wwwroot') . 'artefact/file/download.php?file=' . $artefactcopies[$a]->newid . '$2';
626
627
628
629
630
631
632
633
634
635
636
637
638
                }
            }
            require_once('embeddedimage.php');
            $newdescription = EmbeddedImage::prepare_embedded_images(
                preg_replace($regexp, $replacetext, $this->get('description')),
                'blog',
                $this->get('id'),
                $view->get('group')
            );
            $this->set('description', $newdescription);
        }
    }

639
640
641
642
643
644
645
646
647
    /**
     * During the copying of a view, we might be allowed to copy
     * blogs. Users need to have multipleblogs enabled for these
     * to be visible.
     */
    public function default_parent_for_copy(&$view, &$template, $artefactstoignore) {
        global $USER, $SESSION;

        $viewid = $view->get('id');
648
649
650
        $groupid = $view->get('group');
        $institution = $view->get('institution');
        if ($groupid || $institution) {
651
            $SESSION->add_msg_once(get_string('copiedblogpoststonewjournal', 'collection'), 'ok', true, 'messages');
652
        }
653
654
655
656
        else {
            try {
                $user = get_user($view->get('owner'));
                set_account_preference($user->id, 'multipleblogs', 1);
657
                $SESSION->add_msg_once(get_string('copiedblogpoststonewjournal', 'collection'), 'ok', true, 'messages');
658
659
660
661
            }
            catch (Exception $e) {
                $SESSION->add_error_msg(get_string('unabletosetmultipleblogs', 'error', $user->username, $viewid, get_config('wwwroot') . 'account/index.php'), false);
            }
662

663
664
665
666
667
668
            try {
                $USER->accountprefs = load_account_preferences($user->id);
            }
            catch (Exception $e) {
                $SESSION->add_error_msg(get_string('pleaseloginforjournals', 'error'));
            }
669
670
671
672
        }

        return null;
    }
673
674
675
676
677
678
679
680
681

    /**
     * Check to see if the user has permissions to edit the blog
     *
     * @param object $blog         A blog artefact
     * @param string $institution  Institution name (optional)
     *
     * @return boolean
     */
682
    public static function can_edit_blog($blog, $institution = null, $group = null) {
683
        global $USER;
684
685
        require_once('group.php');
        $USER->reset_grouproles();
686
687
688
        if (
            ($institution == 'mahara' && $USER->get('admin'))
            || ($institution && $institution != 'mahara' && ($USER->get('admin') || $USER->is_institutional_admin($institution)))
689
            || ($group && !empty($USER->grouproles[$group]) && group_role_can_edit_views($group, $USER->grouproles[$group]))
690
691
692
693
694
695
            || ($USER->get('id') == $blog->get('owner'))
           ) {
            return true;
        }
        return false;
    }
696
697
698
699
700
701
702
}

/**
 * BlogPost artefacts occur within Blog artefacts
 */
class ArtefactTypeBlogPost extends ArtefactType {

703
    /**
Alastair Pharo's avatar
Alastair Pharo committed
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
     * This defines whether the blogpost is published or not.
     *
     * @var boolean
     */
    protected $published = false;

    /**
     * We override the constructor to fetch the extra data.
     *
     * @param integer
     * @param object
     */
    public function __construct($id = 0, $data = null) {
        parent::__construct($id, $data);

719
720
721
722
723
        if ($this->id) {
            if ($bpdata = get_record('artefact_blog_blogpost', 'blogpost', $this->id)) {
                foreach($bpdata as $name => $value) {
                    if (property_exists($this, $name)) {
                        $this->$name = $value;
724
                    }
Alastair Pharo's avatar
Alastair Pharo committed
725
                }
726
727
728
729
            }
            else {
                // This should never happen unless the user is playing around with blog post IDs in the location bar or similar
                throw new ArtefactNotFoundException(get_string('blogpostdoesnotexist', 'artefact.blog'));
730
731
            }
        }
732
733
734
        else {
            $this->allowcomments = 1; // Turn comments on for new posts
        }
Alastair Pharo's avatar
Alastair Pharo committed
735
736
737
    }

    /**
738
     * This method extends ArtefactType::commit() by adding additional data
Alastair Pharo's avatar
Alastair Pharo committed
739
     * into the artefact_blog_blogpost table.
740
     *
Aaron Wells's avatar
Aaron Wells committed
741
     * This method also works out what blockinstances this blogpost is in, and
742
     * informs them that they should re-check what artefacts they have in them.
Aaron Wells's avatar
Aaron Wells committed
743
     * The post content may now link to different artefacts. See {@link
744
     * PluginBlocktypeBlogPost::get_artefacts for more information}
745
     */
746
    protected function postcommit_hook($new) {
747
748
        require_once(get_config('docroot') . 'blocktype/lib.php');
        require_once(get_config('docroot') . 'artefact/blog/blocktype/taggedposts/lib.php');
Alastair Pharo's avatar
Alastair Pharo committed
749
750
751
752
753
754
755
756
757
758
759
760
        $data = (object)array(
            'blogpost'  => $this->get('id'),
            'published' => ($this->get('published') ? 1 : 0)
        );

        if ($new) {
            insert_record('artefact_blog_blogpost', $data);
        }
        else {
            update_record('artefact_blog_blogpost', $data, 'blogpost');
        }

761
        // We want to get all blockinstances that may contain this blog post. That is currently:
762
763
        // 1) All blogpost blocktypes with this post in it
        // 2) All blog blocktypes with this posts's blog in it
764
765
        // 3) All recentposts blocktypes with this post's blog in it
        // 4) All taggedposts blocktypes with this post's tags
766
        $blocks = (array)get_column_sql('SELECT block
767
768
769
            FROM {view_artefact}
            WHERE artefact = ?
            OR artefact = ?', array($this->get('id'), $this->get('parent')));
770
771
772
773
774
775
776
777
778
779
        if (!$blocks) {
            $blocks = array();
        }

        // Get all "tagged blog entries" blocks that may contain this block
        // (we'll just check for a single matching tag here, and let each block
        // instance further down decide whether or not it matches
        $tags = $this->get('tags');
        if ($tags) {
            $blocks = array_merge($blocks, PluginBlocktypeTaggedposts::find_matching_blocks($tags));
780
781
782
783
        }

        // Now rebuild the list of which artefacts these blocks contain
        // in the view_artefacts table. (This is used for watchlist notifications)
784
785
        if ($blocks) {
            foreach ($blocks as $id) {
786
787
788
789
                $instance = new BlockInstance($id);
                $instance->rebuild_artefact_list();
            }
        }
790
791
    }

792
    /**
Alastair Pharo's avatar
Alastair Pharo committed
793
794
     * This function extends ArtefactType::delete() by also deleting anything
     * that's in blogpost.
795
     */
796
    public function delete() {
Alastair Pharo's avatar
Alastair Pharo committed
797
798
799
        if (empty($this->id)) {
            return;
        }
800

801
        require_once('embeddedimage.php');
802
        db_begin();
803
        $this->detach(); // Detach all file attachments
Alastair Pharo's avatar
Alastair Pharo committed
804
        delete_records('artefact_blog_blogpost', 'blogpost', $this->id);
805
        EmbeddedImage::delete_embedded_images('blogpost', $this->id);
Alastair Pharo's avatar
Alastair Pharo committed
806
        parent::delete();
807
        db_commit();
Alastair Pharo's avatar
Alastair Pharo committed
808
    }
809

810
    public static function bulk_delete($artefactids, $log=false) {
811
812
813
814
815
816
817
818
819
820
821
822
823
        if (empty($artefactids)) {
            return;
        }

        $idstr = join(',', array_map('intval', $artefactids));

        db_begin();
        delete_records_select('artefact_blog_blogpost', 'blogpost IN (' . $idstr . ')');
        parent::bulk_delete($artefactids);
        db_commit();
    }


824
    /**
825
826
827
828
829
830
     * Checks that the person viewing a personal blog is the owner.
     * Or the person is an institution admin for an institution blog.
     * Or a group member if viewing a group blog.
     * Or a group member with editing permissions if editing a blog.
     * If not, throws an AccessDeniedException.
     * Other people see blogs when they are placed in views.
831
     */
832
    public function check_permission($editing=true) {
833
        global $USER;
834
835
836
837
838
839
840
841
        if (!empty($this->institution)) {
            if ($this->institution == 'mahara' && !$USER->get('admin')) {
                throw new AccessDeniedException(get_string('youarenotasiteadmin', 'artefact.blog'));
            }
            else if (!$USER->get('admin') && !$USER->is_institutional_admin($this->institution)) {
                throw new AccessDeniedException(get_string('youarenotanadminof', 'artefact.blog', $this->institution));
            }
        }
842
        else if (!empty($this->group)) {
843
            $group = get_group_by_id($this->group);
844
845
846
847
848
849
850
851
852
            $USER->reset_grouproles();
            if (!isset($USER->grouproles[$this->group])) {
                throw new AccessDeniedException(get_string('youarenotamemberof', 'artefact.blog', $group->name));
            }
            require_once('group.php');
            if ($editing && !group_role_can_edit_views($this->group, $USER->grouproles[$this->group])) {
                throw new AccessDeniedException(get_string('youarenotaneditingmemberof', 'artefact.blog', $group->name));
            }
        }
853
854
855
856
        else {
            if ($USER->get('id') != $this->owner) {
                throw new AccessDeniedException(get_string('youarenottheownerofthisblogpost', 'artefact.blog'));
            }
857
858
        }
    }
Aaron Wells's avatar
Aaron Wells committed
859

860
861
    public function describe_size() {
        return $this->count_attachments() . ' ' . get_string('attachments', 'artefact.blog');
862
863
    }

864
    public function render_self($options) {
865
866
        global $USER;

867
        $smarty = smarty_core();
868
869
870
871
872
873
874
875
        $smarty->assign('published', $this->get('published'));
        if (!$this->get('published')) {
            $notpublishedblogpoststr = get_string('notpublishedblogpost', 'artefact.blog');
            if ($this->get('owner') == $USER->get('id')) {
                $notpublishedblogpoststr .= ' <a href="' . get_config('wwwroot') . 'artefact/blog/post.php?id=' . $this->get('id') . '">' . get_string('publishit', 'artefact.blog') . '</a>';
            }
            $smarty->assign('notpublishedblogpost', $notpublishedblogpoststr);
        }
876
        $artefacturl = get_config('wwwroot') . 'artefact/artefact.php?artefact=' . $this->get('id');
877
878
879
880
        if (isset($options['viewid'])) {
            $artefacturl .= '&view=' . $options['viewid'];
        }
        $smarty->assign('artefacturl', $artefacturl);
881
882
        if (empty($options['hidetitle'])) {
            if (isset($options['viewid'])) {
883
                $smarty->assign('artefacttitle', '<a href="' . $artefacturl . '">' . hsc($this->get('title')) . '</a>');
884
885
            }
            else {
Richard Mansfield's avatar
Richard Mansfield committed
886
                $smarty->assign('artefacttitle', hsc($this->get('title')));
887
888
889
890
            }
        }

        // We need to make sure that the images in the post have the right viewid associated with them
891
        $postcontent = $this->get('description');
892
        if (isset($options['viewid'])) {
893
894
            safe_require('artefact', 'file');
            $postcontent = ArtefactTypeFolder::append_view_url($postcontent, $options['viewid']);
895
896
        }
        $smarty->assign('artefactdescription', $postcontent);
897
898
        $smarty->assign('artefacttags', $this->get('tags'));
        $smarty->assign('artefactowner', $this->get('owner'));
899
        $smarty->assign('artefactview', (isset($options['viewid']) ? $options['viewid'] : null));
900
901
902
903
904
905
906
        if (!empty($options['details']) and get_config('licensemetadata')) {
            $smarty->assign('license', render_license($this));
        }
        else {
            $smarty->assign('license', false);
        }

907
        $attachments = $this->get_attachments();
908
        if ($attachments) {
909
            require_once(get_config('docroot') . 'artefact/lib.php');
910
911
912
            foreach ($attachments as &$attachment) {
                $f = artefact_instance_from_id($attachment->id);
                $attachment->size = $f->describe_size();
913
                $attachment->iconpath = $f->get_icon(array('id' => $attachment->id, 'viewid' => isset($options['viewid']) ? $options['viewid'] : 0));
914
                $attachment->viewpath = get_config('wwwroot') . 'artefact/artefact.php?artefact=' . $attachment->id . '&view=' . (isset($options['viewid']) ? $options['viewid'] : 0);
915
916
                $attachment->downloadpath = get_config('wwwroot') . 'artefact/file/download.php?file=' . $attachment->id;
                if (isset($options['viewid'])) {
917
                    $attachment->downloadpath .= '&view=' . $options['viewid'];
918
919
920
                }
            }
            $smarty->assign('attachments', $attachments);
921
922
923
924
            if (isset($options['blockid'])) {
                $smarty->assign('blockid', $options['blockid']);
            }
            $smarty->assign('postid', $this->get('id'));
925
        }
926
927
        $by = $this->author ? display_default_name($this->author) : $this->authorname;
        $smarty->assign('postedbyon', ArtefactTypeBlog::display_postedby($this->ctime, $by));
928
929
930
        if ($this->ctime != $this->mtime) {
            $smarty->assign('updatedon', get_string('updatedon', 'artefact.blog') . ' ' . format_date($this->mtime));
        }
931
        return array('html' => $smarty->fetch('artefact:blog:render/blogpost_renderfull.tpl'),
932
933
                     'javascript' => '',
                     'attachments' => $attachments);
934
935
936
    }


937
938
    public function can_have_attachments() {
        return true;
Richard Mansfield's avatar
Richard Mansfield committed
939
940
941
    }


942
    public static function get_icon($options=null) {
943
        global $THEME;
944
        return false;
945
946
    }

Nigel McNie's avatar
Nigel McNie committed
947
    public static function is_singular() {
Penny Leach's avatar
Penny Leach committed
948
949
950
        return false;
    }

Alastair Pharo's avatar
Alastair Pharo committed
951
    public static function collapse_config() {
952
953
    }

954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
    /**
     * This function returns the blog id and offset for a given post.
     *
     * @param integer $postid The id of the required blog post
     * @return object An object containing the required data
     */
    public static function get_post_data($postid) {
        $post = new stdClass();

        $post->blogid = get_field('artefact', 'parent', 'id', $postid, 'artefacttype', 'blogpost');

        if (is_postgres()) {
            $rownum = get_field_sql("SELECT rownum
                                    FROM (SELECT id, ROW_NUMBER() OVER (ORDER BY id DESC) AS rownum
                                        FROM {artefact}
                                        WHERE parent = ?
                                        ORDER BY id DESC) AS posts
                                    WHERE id = ?",
                    array($post->blogid, $postid));
        }
        else if (is_mysql()) {
            $initvar = execute_sql("SET @row_num = 0");
            if ($initvar) {
                $rownum = get_field_sql("SELECT rownum
                                        FROM (SELECT id, @row_num := @row_num + 1 AS rownum
                                            FROM {artefact}
                                            WHERE parent = ?
                                            ORDER BY id DESC) AS posts
                                        WHERE id = ?",
                        array($post->blogid, $postid));
            }
        }
        $post->offset = $rownum - 1;

        return $post;
    }

Alastair Pharo's avatar
Alastair Pharo committed
991
    /**
992
     * This function returns a list of posts in a given blog.
Alastair Pharo's avatar
Alastair Pharo committed
993
994
995
     *
     * @param integer
     * @param integer
996
     * @param integer
997
     * @param array
Alastair Pharo's avatar
Alastair Pharo committed
998
     */
999
    public static function get_posts($id, $limit, $offset, $viewoptions=null) {
1000
        global $USER;
Alastair Pharo's avatar
Alastair Pharo committed
1001

1002
1003
1004
1005
        $results = array(
            'limit'  => $limit,
            'offset' => $offset,
        );
1006

1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
        // If viewoptions is null, we're getting posts for the my blogs area,
        // and we should get all posts & show drafts first.  Otherwise it's a
        // blog in a view, and we should only get published posts.

        $from = "
            FROM {artefact} a LEFT JOIN {artefact_blog_blogpost} bp ON a.id = bp.blogpost
            WHERE a.artefacttype = 'blogpost' AND a.parent = ?";

        if (!is_null($viewoptions)) {
            if (isset($viewoptions['before'])) {
                $from .= " AND a.ctime < '{$viewoptions['before']}'";
1018
            }
1019
            $draftentries = count_records_sql('SELECT COUNT(*) ' . $from, array($id));
1020
            $from .= ' AND bp.published = 1';
1021
1022
1023
            if (!empty($viewoptions['existing_artefacts'])) {
                $from .= ' AND bp.blogpost IN (' . join(',', (array)$viewoptions['existing_artefacts']) . ')';
            }
1024
        }
1025

1026
1027
        $results['count'] = count_records_sql('SELECT COUNT(*) ' . $from, array($id));

1028
1029
1030
1031
1032
        //check if all posts are drafts
        if (isset($draftentries) && $draftentries > 0 && $results['count'] == 0) {
            $results['alldraftposts'] = true;
        }

1033
1034
1035
        $data = get_records_sql_assoc('
            SELECT
                a.id, a.title, a.description, a.author, a.authorname, ' .
1036
                db_format_tsfield('a.ctime', 'ctime') . ', ' . db_format_tsfield('a.mtime', 'mtime') . ',
1037
                a.locked, bp.published, a.allowcomments, a.group ' . $from . '
1038
            ORDER BY bp.published ASC, a.ctime DESC, a.id DESC',
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
            array($id),
            $offset, $limit
        );

        if (!$data) {
            $results['data'] = array();
            return $results;
        }

        // Get the attached files.
1049
        $postids = array_map(function ($a) { return $a->id; }, $data);
1050
1051
1052
1053
        $files = ArtefactType::attachments_from_id_list($postids);
        if ($files) {
            safe_require('artefact', 'file');
            foreach ($files as &$file) {
1054
1055
1056
1057
1058
                $params = array('id' => $file->attachment);
                if (!empty($viewoptions['viewid'])) {
                    $params['viewid'] = $viewoptions['viewid'];
                }
                $file->icon = call_static_method(