lib.php 50.7 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
69
70
        else if ($groupid) {
            define('GROUP', $groupid);
            define('MENUITEM', 'groups/blogs');
        }
71
72
73
74
75
        else {
            define('MENUITEM', 'content/blogs');
        }
    }

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

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

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

95

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

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

        $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);
        }

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

137
138
139
140
    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),
141
            'owner'       => is_object($user) ? $user->id : $user['id'],
142
143
144
        ));
        $blog->commit();
    }
145
146
147
148
149
150

    public static function get_artefact_type_content_types() {
        return array(
            'blogpost' => array('text'),
        );
    }
151

152
    public static function progressbar_link($artefacttype) {
153
154
        return 'artefact/blog/view/index.php';
    }
155
156
157
158
159
160
161
162
163
164
165

    public static function group_tabs($groupid) {
        return array(
            'blogs' => array(
                'path' => 'groups/blogs',
                'url' => 'artefact/blog/index.php?group=' . $groupid,
                'title' => get_string('Blogs', 'artefact.blog'),
                'weight' => 65,
            ),
        );
    }
166
167
168
169
170
171
172
}

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

Alastair Pharo's avatar
Alastair Pharo committed
173
174
175
176
    /**
     * This constant gives the per-page pagination for listing blogs.
     */
    const pagination = 10;
177
178


179
180
181
182
183
184
185
186
    /**
     * We override the constructor to fetch the extra data.
     *
     * @param integer
     * @param object
     */
    public function __construct($id = 0, $data = null) {
        parent::__construct($id, $data);
187
188
189
190
191
192

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

193
194
195
196
    public static function is_allowed_in_progressbar() {
        return false;
    }

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
    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
232
    /**
233
234
235
     * 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
236
     */
237
    public function commit() {
238
239
240
241
        // Just forget the whole thing when we're clean.
        if (empty($this->dirty)) {
            return;
        }
Aaron Wells's avatar
Aaron Wells committed
242

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

246
247
248
249
        // Commit to the artefact table.
        parent::commit();

        $this->dirty = false;
250
251
    }

Alastair Pharo's avatar
Alastair Pharo committed
252
    /**
253
254
     * This function extends ArtefactType::delete() by deleting blog-specific
     * data.
Alastair Pharo's avatar
Alastair Pharo committed
255
     */
256
    public function delete() {
257
258
259
260
        if (empty($this->id)) {
            return;
        }

261
262
263
264
        db_begin();
        // Delete embedded images in the blog description
        require_once('embeddedimage.php');
        EmbeddedImage::delete_embedded_images('blog', $this->id);
265
266
        // Delete the artefact and all children.
        parent::delete();
267
        db_commit();
268
269
    }

270
    /**
271
272
273
274
275
276
     * 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.
277
     */
278
    public function check_permission($editing=false) {
279
        global $USER;
280

281
282
283
284
285
286
287
288
        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));
            }
        }
289
        else if (!empty($this->group)) {
290
            $group = get_group_by_id($this->group);
291
292
293
294
295
296
297
298
299
            $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));
            }
        }
300
301
302
303
        else {
            if ($USER->get('id') != $this->owner) {
                throw new AccessDeniedException(get_string('youarenottheownerofthisblog', 'artefact.blog'));
            }
304
305
306
        }
    }

307

308
309
    public function describe_size() {
        return $this->count_children() . ' ' . get_string('posts', 'artefact.blog');
310
311
    }

312
    /**
313
     * Renders a blog.
314
315
316
317
318
     *
     * @param  array  Options for rendering
     * @return array  A two key array, 'html' and 'javascript'.
     */
    public function render_self($options) {
319
320
321
322
323
324
325
326
327
328
329
        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;

330
331
332
333
334
        if (!isset($options['countcomments'])) {
            // Count comments if this is a view
            $options['countcomments'] = (!empty($options['viewid']));
        }

335
336
337
338
        $posts = ArtefactTypeBlogpost::get_posts($this->id, $limit, $offset, $options);

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

339
        $baseurl = get_config('wwwroot') . 'artefact/artefact.php?artefact=' . $this->id;
340
341
342
        if (!empty($options['viewid'])) {
            $baseurl .= '&view=' . $options['viewid'];
        }
343
344
345
346
347
348
349
350
351
        $pagination = array(
            'baseurl' => $baseurl,
            'id' => 'blogpost_pagination',
            'datatable' => 'postlist',
            'jsonscript' => 'artefact/blog/posts.json.php',
        );

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

352
353
        $smarty = smarty_core();
        if (isset($options['viewid'])) {
354
            $smarty->assign('artefacttitle', '<a href="' . get_config('wwwroot') . 'artefact/artefact.php?artefact='
355
                                             . $this->get('id') . '&view=' . $options['viewid']
Richard Mansfield's avatar
Richard Mansfield committed
356
                                             . '">' . hsc($this->get('title')) . '</a>');
357
358
        }
        else {
Richard Mansfield's avatar
Richard Mansfield committed
359
            $smarty->assign('artefacttitle', hsc($this->get('title')));
360
361
        }

362
363
364
365
366
367
368
        if (!empty($options['details']) and get_config('licensemetadata')) {
            $smarty->assign('license', render_license($this));
        }
        else {
            $smarty->assign('license', false);
        }

369
        $options['hidetitle'] = true;
370
        $smarty->assign('options', $options);
371
        $smarty->assign('description', $this->get('description'));
372
373
        $smarty->assign('owner', $this->get('owner'));
        $smarty->assign('tags', $this->get('tags'));
374

375
        $smarty->assign('posts', $posts);
376

377
        return array('html' => $smarty->fetch('artefact:blog:blog.tpl'), 'javascript' => '');
378
379
    }

Aaron Wells's avatar
Aaron Wells committed
380

381
    public static function get_icon($options=null) {
382
        global $THEME;
383
        return false;
384
385
    }

Nigel McNie's avatar
Nigel McNie committed
386
    public static function is_singular() {
Penny Leach's avatar
Penny Leach committed
387
388
389
        return false;
    }

Alastair Pharo's avatar
Alastair Pharo committed
390
    public static function collapse_config() {
391
392
    }

393
394
395
396
    public function can_have_attachments() {
        return true;
    }

Alastair Pharo's avatar
Alastair Pharo committed
397
    /**
398
     * This function returns a list of the given blogs.
Alastair Pharo's avatar
Alastair Pharo committed
399
400
401
402
     *
     * @param User
     * @return array (count: integer, data: array)
     */
403
    public static function get_blog_list($limit, $offset, $institution = null, $group = null) {
404
        global $USER;
405
406
407
408
409
410
411
412
413

        $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');
        }
414
415
416
417
        else if ($group) {
            $sql .= ' AND b.group = ?';
            $values = array($group);
            $count = (int)get_field('artefact', 'COUNT(*)', 'group', $group, 'artefacttype', 'blog');
418
            $groupdata = get_group_by_id($group, false, true, true);
419
        }
420
421
422
423
424
425
426
        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
427
428
            || ($result = array());

429
430
        foreach ($result as &$r) {
            if (!$r->locked) {
431
                $r->deleteform = ArtefactTypeBlog::delete_form($r->id, $r->title);
432
            }
433
            $r->canedit = (!empty($groupdata) ? $groupdata->canedit : true);
434
435
        }

Alastair Pharo's avatar
Alastair Pharo committed
436
437
438
        return array($count, $result);
    }

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

Alastair Pharo's avatar
Alastair Pharo committed
462
463
464
    /**
     * This function creates a new blog.
     *
465
     * @param User or null
Alastair Pharo's avatar
Alastair Pharo committed
466
467
     * @param array
     */
468
    public static function new_blog($user, array $values) {
469
470
        require_once('embeddedimage.php');
        db_begin();
Alastair Pharo's avatar
Alastair Pharo committed
471
472
473
        $artefact = new ArtefactTypeBlog();
        $artefact->set('title', $values['title']);
        $artefact->set('description', $values['description']);
474
475
476
        if (!empty($values['institution'])) {
            $artefact->set('institution', $values['institution']);
        }
477
478
479
        else if (!empty($values['group'])) {
            $artefact->set('group', $values['group']);
        }
480
481
482
        else {
            $artefact->set('owner', $user->get('id'));
        }
483
        $artefact->set('tags', $values['tags']);
484
485
486
487
488
        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
489
        $artefact->commit();
490
491
492
493
        $blogid = $artefact->get('id');
        $newdescription = EmbeddedImage::prepare_embedded_images($artefact->get('description'), 'blog', $blogid);
        $artefact->set('description', $newdescription);
        db_commit();
494
    }
Alastair Pharo's avatar
Alastair Pharo committed
495
496
497
498
499
500
501
502

    /**
     * This function updates an existing blog.
     *
     * @param User
     * @param array
     */
    public static function edit_blog(User $user, array $values) {
503
        require_once('embeddedimage.php');
Alastair Pharo's avatar
Alastair Pharo committed
504
505
506
507
508
        if (empty($values['id']) || !is_numeric($values['id'])) {
            return;
        }

        $artefact = new ArtefactTypeBlog($values['id']);
509
510
511
        $institution = !empty($values['institution']) ? $values['institution'] : null;
        $group = !empty($values['group']) ? $values['group'] : null;
        if (!self::can_edit_blog($artefact, $institution, $group)) {
512
513
            return;
        }
Alastair Pharo's avatar
Alastair Pharo committed
514
        $artefact->set('title', $values['title']);
515
516
        $newdescription = EmbeddedImage::prepare_embedded_images($values['description'], 'blog', $values['id']);
        $artefact->set('description', $newdescription);
517
        $artefact->set('tags', $values['tags']);
518
519
520
521
522
        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
523
524
        $artefact->commit();
    }
525

Martyn Smith's avatar
Martyn Smith committed
526
527
    public static function get_links($id) {
        $wwwroot = get_config('wwwroot');
528

Martyn Smith's avatar
Martyn Smith committed
529
        return array(
530
531
            '_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
532
533
        );
    }
534
535
536
537
538

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

539
540
541
    /**
     * Returns the number of posts in this blog that have been published.
     *
Aaron Wells's avatar
Aaron Wells committed
542
     * The result of this function looked up from the database each time, so
543
544
545
546
547
548
549
550
551
552
553
554
555
     * 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')));
    }

556
    public static function delete_form($id, $title = '') {
557
        global $THEME;
558
559

        $confirm = get_string('deleteblog?', 'artefact.blog');
560
        $title = hsc($title);
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
        // 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);
            }
        }
582
583
584
        return pieform(array(
            'name' => 'delete_' . $id,
            'successcallback' => 'delete_blog_submit',
Pat Kira's avatar
Pat Kira committed
585
586
            'renderer' => 'div',
            'class' => 'form-as-button pull-left btn-group-item',
587
588
            'elements' => array(
                'submit' => array(
589
590
                    'type' => 'button',
                    'usebuttontag' => true,
Pat Kira's avatar
Pat Kira committed
591
                    'class' => 'btn-default btn-sm last',
592
593
                    'alt' => get_string('deletespecific', 'mahara', $title),
                    'elementtitle' => get_string('delete'),
594
                    'confirm' => $confirm,
595
                    '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
596
597
598
599
                ),
                'delete' => array(
                    'type' => 'hidden',
                    'value' => $id,
600
601
602
603
                ),
            ),
        ));
    }
604

605
606
607
608
609
610
611
612
613
    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
614
615
                    $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';
616
617
618
619
620
621
622
623
624
625
626
627
628
                }
            }
            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);
        }
    }

629
630
631
632
633
634
635
636
637
    /**
     * 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');
638
639
640
        $groupid = $view->get('group');
        $institution = $view->get('institution');
        if ($groupid || $institution) {
641
            $SESSION->add_msg_once(get_string('copiedblogpoststonewjournal', 'collection'), 'ok', true, 'messages');
642
        }
643
644
645
646
        else {
            try {
                $user = get_user($view->get('owner'));
                set_account_preference($user->id, 'multipleblogs', 1);
647
                $SESSION->add_msg_once(get_string('copiedblogpoststonewjournal', 'collection'), 'ok', true, 'messages');
648
649
650
651
            }
            catch (Exception $e) {
                $SESSION->add_error_msg(get_string('unabletosetmultipleblogs', 'error', $user->username, $viewid, get_config('wwwroot') . 'account/index.php'), false);
            }
652

653
654
655
656
657
658
            try {
                $USER->accountprefs = load_account_preferences($user->id);
            }
            catch (Exception $e) {
                $SESSION->add_error_msg(get_string('pleaseloginforjournals', 'error'));
            }
659
660
661
662
        }

        return null;
    }
663
664
665
666
667
668
669
670
671

    /**
     * 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
     */
672
    public static function can_edit_blog($blog, $institution = null, $group = null) {
673
        global $USER;
674
675
        require_once('group.php');
        $USER->reset_grouproles();
676
677
678
        if (
            ($institution == 'mahara' && $USER->get('admin'))
            || ($institution && $institution != 'mahara' && ($USER->get('admin') || $USER->is_institutional_admin($institution)))
679
            || ($group && !empty($USER->grouproles[$group]) && group_role_can_edit_views($group, $USER->grouproles[$group]))
680
681
682
683
684
685
            || ($USER->get('id') == $blog->get('owner'))
           ) {
            return true;
        }
        return false;
    }
686
687
688
689
690
691
692
}

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

693
    /**
Alastair Pharo's avatar
Alastair Pharo committed
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
     * 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);

709
710
711
712
713
        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;
714
                    }
Alastair Pharo's avatar
Alastair Pharo committed
715
                }
716
717
718
719
            }
            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'));
720
721
            }
        }
722
723
724
        else {
            $this->allowcomments = 1; // Turn comments on for new posts
        }
Alastair Pharo's avatar
Alastair Pharo committed
725
726
727
    }

    /**
728
     * This method extends ArtefactType::commit() by adding additional data
Alastair Pharo's avatar
Alastair Pharo committed
729
     * into the artefact_blog_blogpost table.
730
     *
Aaron Wells's avatar
Aaron Wells committed
731
     * This method also works out what blockinstances this blogpost is in, and
732
     * informs them that they should re-check what artefacts they have in them.
Aaron Wells's avatar
Aaron Wells committed
733
     * The post content may now link to different artefacts. See {@link
734
     * PluginBlocktypeBlogPost::get_artefacts for more information}
735
     */
736
    protected function postcommit_hook($new) {
737
738
        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
739
740
741
742
743
744
745
746
747
748
749
750
        $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');
        }

751
        // We want to get all blockinstances that may contain this blog post. That is currently:
752
753
        // 1) All blogpost blocktypes with this post in it
        // 2) All blog blocktypes with this posts's blog in it
754
755
        // 3) All recentposts blocktypes with this post's blog in it
        // 4) All taggedposts blocktypes with this post's tags
756
        $blocks = (array)get_column_sql('SELECT block
757
758
759
            FROM {view_artefact}
            WHERE artefact = ?
            OR artefact = ?', array($this->get('id'), $this->get('parent')));
760
761
762
763
764
765
766
767
768
769
        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));
770
771
772
773
        }

        // Now rebuild the list of which artefacts these blocks contain
        // in the view_artefacts table. (This is used for watchlist notifications)
774
775
        if ($blocks) {
            foreach ($blocks as $id) {
776
777
778
779
                $instance = new BlockInstance($id);
                $instance->rebuild_artefact_list();
            }
        }
780
781
    }

782
    /**
Alastair Pharo's avatar
Alastair Pharo committed
783
784
     * This function extends ArtefactType::delete() by also deleting anything
     * that's in blogpost.
785
     */
786
    public function delete() {
Alastair Pharo's avatar
Alastair Pharo committed
787
788
789
        if (empty($this->id)) {
            return;
        }
790

791
        require_once('embeddedimage.php');
792
        db_begin();
793
        $this->detach(); // Detach all file attachments
Alastair Pharo's avatar
Alastair Pharo committed
794
        delete_records('artefact_blog_blogpost', 'blogpost', $this->id);
795
        EmbeddedImage::delete_embedded_images('blogpost', $this->id);
Alastair Pharo's avatar
Alastair Pharo committed
796
        parent::delete();
797
        db_commit();
Alastair Pharo's avatar
Alastair Pharo committed
798
    }
799

800
    public static function bulk_delete($artefactids, $log=false) {
801
802
803
804
805
806
807
808
809
810
811
812
813
        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();
    }


814
    /**
815
816
817
818
819
820
     * 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.
821
     */
822
    public function check_permission($editing=true) {
823
        global $USER;
824
825
826
827
828
829
830
831
        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));
            }
        }
832
        else if (!empty($this->group)) {
833
            $group = get_group_by_id($this->group);
834
835
836
837
838
839
840
841
842
            $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));
            }
        }
843
844
845
846
        else {
            if ($USER->get('id') != $this->owner) {
                throw new AccessDeniedException(get_string('youarenottheownerofthisblogpost', 'artefact.blog'));
            }
847
848
        }
    }
Aaron Wells's avatar
Aaron Wells committed
849

850
851
    public function describe_size() {
        return $this->count_attachments() . ' ' . get_string('attachments', 'artefact.blog');
852
853
    }

854
    public function render_self($options) {
855
856
        global $USER;

857
        $smarty = smarty_core();
858
859
860
861
862
863
864
865
        $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);
        }
866
        $artefacturl = get_config('wwwroot') . 'artefact/artefact.php?artefact=' . $this->get('id');
867
868
869
870
        if (isset($options['viewid'])) {
            $artefacturl .= '&view=' . $options['viewid'];
        }
        $smarty->assign('artefacturl', $artefacturl);
871
872
        if (empty($options['hidetitle'])) {
            if (isset($options['viewid'])) {
873
                $smarty->assign('artefacttitle', '<a href="' . $artefacturl . '">' . hsc($this->get('title')) . '</a>');
874
875
            }
            else {
Richard Mansfield's avatar
Richard Mansfield committed
876
                $smarty->assign('artefacttitle', hsc($this->get('title')));
877
878
879
880
            }
        }

        // We need to make sure that the images in the post have the right viewid associated with them
881
        $postcontent = $this->get('description');
882
        if (isset($options['viewid'])) {
883
884
            safe_require('artefact', 'file');
            $postcontent = ArtefactTypeFolder::append_view_url($postcontent, $options['viewid']);
885
886
        }
        $smarty->assign('artefactdescription', $postcontent);
887
888
        $smarty->assign('artefacttags', $this->get('tags'));
        $smarty->assign('artefactowner', $this->get('owner'));
889
890
891
892
893
894
895
        if (!empty($options['details']) and get_config('licensemetadata')) {
            $smarty->assign('license', render_license($this));
        }
        else {
            $smarty->assign('license', false);
        }

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


926
927
    public function can_have_attachments() {
        return true;
Richard Mansfield's avatar
Richard Mansfield committed
928
929
930
    }


931
    public static function get_icon($options=null) {
932
        global $THEME;
933
        return false;
934
935
    }

Nigel McNie's avatar
Nigel McNie committed
936
    public static function is_singular() {
Penny Leach's avatar
Penny Leach committed
937
938
939
        return false;
    }

Alastair Pharo's avatar
Alastair Pharo committed
940
    public static function collapse_config() {
941
942
    }

943
944
945
946
947
948
949
950
951
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
    /**
     * 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
980
    /**
981
     * This function returns a list of posts in a given blog.
Alastair Pharo's avatar
Alastair Pharo committed
982
983
984
     *
     * @param integer
     * @param integer
985
     * @param integer
986
     * @param array
Alastair Pharo's avatar
Alastair Pharo committed
987
     */
988
    public static function get_posts($id, $limit, $offset, $viewoptions=null) {
989
        global $USER;
Alastair Pharo's avatar
Alastair Pharo committed
990

991
992
993
994
        $results = array(
            'limit'  => $limit,
            'offset' => $offset,
        );
995

996
997
998
999
1000
1001
1002
1003
1004
1005
1006
        // 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']}'";
1007
            }
1008
            $draftentries = count_records_sql('SELECT COUNT(*) ' . $from, array($id));
1009
1010
            $from .= ' AND bp.published = 1';
        }
1011

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

1014
1015
1016
1017
1018
        //check if all posts are drafts
        if (isset($draftentries) && $draftentries > 0 && $results['count'] == 0) {
            $results['alldraftposts'] = true;
        }

1019
1020
1021
        $data = get_records_sql_assoc('
            SELECT
                a.id, a.title, a.description, a.author, a.authorname, ' .
1022
                db_format_tsfield('a.ctime', 'ctime') . ', ' . db_format_tsfield('a.mtime', 'mtime') . ',
1023
                a.locked, bp.published, a.allowcomments, a.group ' . $from . '
1024
            ORDER BY bp.published ASC, a.ctime DESC, a.id DESC',
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
            array($id),
            $offset, $limit
        );

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

        // Get the attached files.
        $postids = array_map(create_function('$a', 'return $a->id;'), $data);
        $files = ArtefactType::attachments_from_id_list($postids);
        if ($files) {
            safe_require('artefact', 'file');
            foreach ($files as &$file) {
1040
1041
1042
1043
1044
                $params = array('id' => $file->attachment);
                if (!empty($viewoptions['viewid'])) {
                    $params['viewid'] = $viewoptions['viewid'];
                }
                $file->icon = call_static_method(generate_artefact_class_name($file->artefacttype), 'get_icon', $params);
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
                $data[$file->artefact]->files[] = $file;
            }
        }

        if ($tags = ArtefactType::tags_from_id_list($postids)) {
            foreach($tags as &$at) {
                $data[$at->artefact]->tags[] = $at->tag;
            }
        }

        foreach ($data as &$post) {
1056
            // Format dates properly
1057
            if (is_null($viewoptions)) {
1058
                // My Blogs area: create forms for changing post status & deleting posts.
1059
                $post->changepoststatus = ArtefactTypeBlogpost::changepoststatus_form($post->id, $post->published, $post->title);
1060
                $post->delete = ArtefactTypeBlogpost::delete_form($post->id, $post->title);
1061
            }
1062
1063
            else {
                $by = $post->author ? display_default_name($post->author) : $post->authorname;
1064
                $post->postedby = ArtefactTypeBlog::display_postedby($post->ctime, $by);
1065
1066
1067
1068
1069
1070
                // Get comment counts
                if (!empty($viewoptions['countcomments'])) {
                    safe_require('artefact', 'comment');
                    require_once(get_config('docroot') . 'lib/view.php');
                    $view = new View($viewoptions['viewid']);
                    $artefact = artefact_instance_from_id($post->id);
1071
1072
1073
                    list($commentcount, $comments) = ArtefactTypeComment::get_artefact_comments_for_view($artefact, $view, null, false);
                    $post->commentcount = $commentcount;
                    $post->comments = $comments;
1074
                }
1075
            }
1076
1077
1078
            if ($post->ctime != $post->mtime) {
                $post->lastupdated = format_date($post->mtime, 'strftimedaydatetime');
            }
1079
1080
            $post->ctime = format_date($post->ctime, 'strftimedaydatetime');
            $post->mtime = format_date($post->mtime);
1081
1082
1083
1084
1085
1086

            // Ensure images in the post have the right viewid associated with them
            if (!empty($viewoptions['viewid'])) {
                safe_require('artefact', 'file');
                $post->description = ArtefactTypeFolder::append_view_url($post->description, $viewoptions['viewid']);
            }
1087
1088
1089
1090
1091

            if (isset($post->group)) {
                $group = get_group_by_id($post->group, false, true, true);
            }
            $post->canedit = (isset($group) ? $group->canedit : true);
1092
        }
Alastair Pharo's avatar
Alastair Pharo committed
1093

1094
1095
1096
        $results['data'] = array_values($data);

        return $results;
1097
1098
    }

1099
1100
1101
1102
1103
1104
1105
1106
1107
    /**
     * This function renders a list of posts as html
     *
     * @param array posts
     * @param string template
     * @param array options
     * @param array pagination
     */
    public function render_posts(&$posts, $template, $options, $pagination) {
1108
        $smarty = smarty_core();
1109
        $smarty->assign('options', $options);
1110
1111
        $smarty->assign('posts', $posts['data']);

1112
1113
        $posts['tablerows'] = $smarty->fetch($template);