lib.php 23.8 KB
Newer Older
1
2
3
4
<?php
/**
 *
 * @package    mahara
5
 * @subpackage blocktype.blog/taggedposts
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();

14
class PluginBlocktypeTaggedposts extends MaharaCoreBlocktype {
15

16
17
18
    const TAGTYPE_INCLUDE = 1;
    const TAGTYPE_EXCLUDE = 0;

19
    public static function get_title() {
20
        return get_string('title', 'blocktype.blog/taggedposts');
21
22
23
    }

    public static function get_description() {
24
        return get_string('description', 'blocktype.blog/taggedposts');
25
26
27
    }

    public static function get_categories() {
28
        return array('blog' => 13000);
29
30
    }

31
32
33
34
35
36
37
38
39
40
    public static function get_instance_javascript(BlockInstance $bi) {
        $blockid = $bi->get('id');
        return array(
            array(
                'file'   => 'js/taggedposts.js',
                'initjs' => "addNewTaggedPostShortcut($blockid);",
            )
        );
    }

41
42
43
44
45
46
47
    /**
     * Given a list of tags, finds blocks by the current user that contain those tags
     * (Used to determine which ones to check for the view_artefact table)
     * @param array $tags
     * @return array
     */
    public static function find_matching_blocks(array $tags) {
48
49
        global $USER;

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
        $taggedblockids = (array)get_column_sql(
                'SELECT bi.id as block
                FROM
                    {blocktype_taggedposts_tags} btt
                    INNER JOIN {block_instance} bi
                        ON btt.block_instance = bi.id
                    INNER JOIN {view} v
                        ON bi.view = v.id
                WHERE
                    v.owner = ?
                    AND btt.tagtype = ?
                    AND btt.tag IN (' . implode(',', db_array_to_ph($tags)) . ')
                ',
                array_merge(
                    array(
                        $USER->id,
                        PluginBlocktypeTaggedposts::TAGTYPE_INCLUDE
                    ),
                    $tags
                )
        );
        if ($taggedblockids) {
            return $taggedblockids;
        }
        else {
            return array();
        }
    }

    /**
     * Returns the blog posts that will be displayed by this block.
     *
     * @param BlockInstance $instance
     * @param array $tagsin Optional reference variable for finding out the "include" tags used by this block
     * @param array $tagsout Optional reference variable for finding out the "extclude" tags used by this block
     * @return array of blogpost records
     */
87
    public static function get_blog_posts_in_block(BlockInstance $instance, &$tagsinreturn = null, &$tagsoutreturn = null, $versioning=false) {
88
        $configdata = $instance->get('configdata');
89
        $results = array();
90

91
        $tagsin = $tagsout = array();
92
93
94
95
96
97
        if ($versioning) {
            $tagrecords = $configdata['tagrecords'];
        }
        else {
            $tagrecords = get_records_array('blocktype_taggedposts_tags', 'block_instance', $instance->get('id'), 'tagtype desc, tag', 'tag, tagtype');
        }
98
99
100
101
102
103
        if ($tagrecords) {

            $view = $instance->get('view');
            $limit = isset($configdata['count']) ? (int) $configdata['count'] : 10;

            foreach ($tagrecords as $tag) {
104
105
                //tag is encoded in the db if it has special characters
                $tag->tag = htmlspecialchars_decode($tag->tag);
106
107
                if ($tag->tagtype == PluginBlocktypeTaggedposts::TAGTYPE_INCLUDE) {
                    $tagsin[] = $tag->tag;
108
109
                }
                else {
110
                    $tagsout[] = $tag->tag;
111
112
113
114
                }
            }
            $tagsout = array_filter($tagsout);
            $sqlvalues = array($view);
115
            $typecast = is_postgres() ? '::varchar' : '';
116
            $sql =
117
118
                "SELECT a.title, p.title AS parenttitle, a.id, a.parent, a.owner, a.author, a.authorname,
                    a.description, a.allowcomments, at.tag, a.ctime, a.mtime
119
120
121
                FROM {artefact} a
                JOIN {artefact} p ON a.parent = p.id
                JOIN {artefact_blog_blogpost} ab ON (ab.blogpost = a.id AND ab.published = 1)
122
123
124
                JOIN {tag} at ON (at.resourcetype = 'artefact' AND at.resourceid = a.id" . $typecast . ")
                WHERE a.artefacttype = 'blogpost'
                AND a.owner = (SELECT \"owner\" from {view} WHERE id = ?)";
125
126
            if (!empty($tagsin)) {
                foreach ($tagsin as $tagin) {
127
128
129
                    $sql .= " AND EXISTS (
                        SELECT * FROM {tag} AS at
                        WHERE at.resourcetype = 'artefact' AND at.resourceid = a.id" . $typecast . "
130
                        AND at.tag = ?
131
                    )";
132
133
134
135
136
                }
                $sqlvalues = array_merge($sqlvalues, $tagsin);
            }
            if (!empty($tagsout)) {
                foreach ($tagsout as $tagout) {
137
138
139
                    $sql .= " AND NOT EXISTS (
                        SELECT * FROM {tag} AS at
                        WHERE at.resourcetype = 'artefact' AND at.resourceid = a.id" . $typecast . "
140
                        AND at.tag = ?
141
                    )";
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
                }
                $sqlvalues = array_merge($sqlvalues, $tagsout);
            }
            $sql .= ' ORDER BY a.ctime DESC, a.id DESC';
            $results = get_records_sql_array($sql, $sqlvalues);
            // We need to filter this down to unique results
            if (!empty($results)) {
                $used = array();
                foreach ($results as $key => $result) {
                    if (array_search($result->id, $used) === false) {
                        $used[] = $result->id;
                    }
                    else {
                        unset($results[$key]);
                    }
                }
                if (!empty($limit)) {
                    $results = array_slice($results, 0, $limit);
                }
            }
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
            else {
                $results = array();
            }
        }

        if ($tagsinreturn !== null) {
            $tagsinreturn = $tagsin;
        }
        if ($tagsoutreturn !== null) {
            $tagsoutreturn = $tagsout;
        }

        return $results;
    }

177
    public static function render_instance(BlockInstance $instance, $editing=false, $versioning=false) {
178
179
180
181
182
183
184
185
186
187
188
189
        global $USER;

        $configdata = $instance->get('configdata');
        $view = $instance->get('view');
        $full = isset($configdata['full']) ? $configdata['full'] : false;
        $results = array();

        $smarty = smarty_core();
        $smarty->assign('view', $view);
        $viewownerdisplay = null;
        // Display all posts, from all blogs, owned by this user
        $tagsin = $tagsout = array();
190
        $results = self::get_blog_posts_in_block($instance, $tagsin, $tagsout, $versioning);
191
192
        if ($tagsin || $tagsout) {

193
194
195
196
197
198
199
200
            $smarty->assign('blockid', $instance->get('id'));
            $smarty->assign('editing', $editing);
            if ($editing) {
                // Get list of blogs owned by this user to create the "Add new post" shortcut while editing
                $viewowner = $instance->get_view()->get('owner');
                if (!$viewowner || !$blogs = get_records_select_array('artefact', 'artefacttype = \'blog\' AND owner = ?', array($viewowner), 'title ASC', 'id, title')) {
                    $blogs = array();
                }
201
                $smarty->assign('tagselect', implode(', ', $tagsin));
202
203
                $smarty->assign('blogs', $blogs);
            }
204

205
206
            // if posts are not found with the selected tag, notify the user
            if (!$results) {
207
208
                $smarty->assign('badtag', implode(', ', $tagsin));
                $smarty->assign('badnotag', implode(', ', $tagsout));
209
210
211
212
213
214
215
216
217
                return $smarty->fetch('blocktype:taggedposts:taggedposts.tpl');
            }

            // update the view_artefact table so journal entries are accessible when this is the only block on the page
            // referencing this journal
            $dataobject = array(
                'view'      => $view,
                'block'     => $instance->get('id'),
            );
218
219
            require_once(get_config('docroot') . 'lib/view.php');
            $viewobj = new View($view);
220
221
222
            require_once(get_config('docroot') . 'artefact/lib.php');
            safe_require('artefact', 'blog');
            safe_require('artefact', 'comment');
223
224
            foreach ($results as $result) {
                $dataobject["artefact"] = $result->parent;
225
                $result->displaydate= format_date(strtotime($result->ctime));
226
227
                $by = $result->author ? display_default_name($result->author) : $result->authorname;
                $result->postedbyon = ArtefactTypeBlog::display_postedby($result->ctime, $by);
228

229
230
231
                if ($result->ctime != $result->mtime) {
                    $result->updateddate= format_date(strtotime($result->mtime));
                }
232

233
                $artefact = new ArtefactTypeBlogpost($result->id);
234
                // get comments for this post
235
                $result->commentcount = count_records_select('artefact_comment_comment', "onartefact = {$result->id} AND private = 0 AND deletedby IS NULL AND hidden=0");
236
237
238
239
240
                $allowcomments = $artefact->get('allowcomments');
                if (empty($result->commentcount) && empty($allowcomments)) {
                    $result->commentcount = null;
                }

241
                list($commentcount, $comments) = ArtefactTypeComment::get_artefact_comments_for_view($artefact, $viewobj, null, false, false, $versioning);
242
                $result->comments = $comments;
243
244

                // get all tags for this post
245
                $taglist = get_records_sql_array("SELECT tag FROM {tag} WHERE resourcetype = 'artefact' AND resourceid = ? ORDER BY tag DESC", array($result->id));
246
247
248
                foreach ($taglist as $t) {
                    $result->taglist[] = $t->tag;
                }
249
                if ($full) {
250
                    $rendered = $artefact->render_self(array('viewid' => $view, 'details' => true, 'blockid' => $instance->get('id')));
251
252
                    $result->html = $rendered['html'];
                    if (!empty($rendered['javascript'])) {
253
                        $result->html .= '<script>' . $rendered['javascript'] . '</script>';
254
                    }
255
256
257
258
259
260
261
262
263
                    $attachments = $rendered['attachments'];
                    if (!empty($attachments)) {
                        $smarty->assign('attachments', $attachments);
                        $smarty->assign('postid', $result->id);
                        $result->attachments = $smarty->fetch('artefact:blog:render/blogpost_renderattachments.tpl');
                    }

                    safe_require('artefact', 'file');
                    $result->description = ArtefactTypeFolder::append_view_url($result->description, $view);
264
                }
265
266
267
268
            }

            // check if the user viewing the page is the owner of the selected tag
            $owner = $results[0]->owner;
269
            if ($USER->is_logged_in() && $USER->id != $owner) {
270
                $viewownerdisplay = get_user_for_display($owner);
271
            }
272
273
274
            $smarty->assign('tagsin', $tagsin);
            $smarty->assign('tagsout', $tagsout);
        }
275
        else if (!self::get_chooseable_tags()) {
276
277
278
            // error if block configuration fails
            $smarty->assign('configerror', get_string('notagsavailableerror', 'blocktype.blog/taggedposts'));
            return $smarty->fetch('blocktype:taggedposts:taggedposts.tpl');
279
280
281
        }
        else {
            // error if block configuration fails
282
            $smarty->assign('configerror', get_string('configerror', 'blocktype.blog/taggedposts'));
283
284
285
            return $smarty->fetch('blocktype:taggedposts:taggedposts.tpl');
        }

286
287
288
289
290
291
        // add any needed links to the tags
        $tagstr = $tagomitstr = '';
        foreach ($tagsin as $key => $tag) {
            if ($key > 0) {
                $tagstr .= ', ';
            }
292
293
294
295
296
297
298
299
300
            if (strpos($tag, 'tagid_') !== false) {
                $tags = get_records_sql_array("
                    SELECT CONCAT(i.displayname, ': ', t.tag) AS tag, t.resourceid
                    FROM {tag} t
                    LEFT JOIN {institution} i ON i.name = t.ownerid
                    WHERE t.id = ?", array(substr($tag, 6, 5))
                );
                $tag = $tags[0]->tag;
            }
301
            $tagstr .= ($USER->id != $owner) ? '"<a href="' . get_config('wwwroot') . 'relatedtags.php?tag=' . urlencode($tag) . '&view=' . $view . '">' . hsc($tag) . '</a>"' : '"<a href="' . get_config('wwwroot') . 'tags.php?tag=' . urlencode($tag) . '&sort=name&type=text">' . hsc($tag) . '</a>"';
302
303
304
305
306
307
        }
        if (!empty($tagsout)) {
            foreach ($tagsout as $key => $tag) {
                if ($key > 0) {
                    $tagomitstr .= ', ';
                }
308
                $tagomitstr .= ($USER->id != $owner) ? '"<a href="' . get_config('wwwroot') . 'relatedtags.php?tag=' . urlencode($tag) . '&view=' . $view . '">' . hsc($tag) . '</a>"' : '"<a href="' . get_config('wwwroot') . 'tags.php?tag=' . urlencode($tag) . '&sort=name&type=text">' . hsc($tag) . '</a>"';
309
310
            }
        }
311
312
313
314
315
316
317
        if (empty($tagsin)) {
            $blockheading = get_string('blockheadingtagsomitonly', 'blocktype.blog/taggedposts', count($tagsout), $tagomitstr);
        }
        else {
            $blockheading = get_string('blockheadingtags', 'blocktype.blog/taggedposts', count($tagsin), $tagstr);
            $blockheading .= (!empty($tagomitstr)) ? get_string('blockheadingtagsomitboth', 'blocktype.blog/taggedposts', count($tagsout), $tagomitstr) : '';
        }
318
        $blockheading .= ($viewownerdisplay) ? ' ' . get_string('by', 'artefact.blog') . ' <a href="' . profile_url($viewownerdisplay) . '">' . display_name($viewownerdisplay) . '</a>' : '';
319
        $smarty->assign('full', $full);
320
        $smarty->assign('results', $results);
321
        $smarty->assign('blockheading', $blockheading);
322
323
324
        return $smarty->fetch('blocktype:taggedposts:taggedposts.tpl');
    }

325
326
327
328
329
330
331
332
333
334
335
    private static function get_selected_tags() {

    }


    /**
     * Get the tags the user can choose from
     * (i.e. tags they use on their blogpost artefacts)
     * @return array
     */
    private static function get_chooseable_tags() {
336
        global $USER;
337

338
        $typecast = is_postgres() ? '::varchar' : '';
339
        return get_records_sql_array("
340
            SELECT at.tag
341
342
            FROM {tag} at
            JOIN {artefact} a ON (at.resourcetype ='artefact' AND at.resourceid = a.id" . $typecast . ")
343
344
345
            WHERE
                a.owner = ?
                AND a.artefacttype = 'blogpost'
346
347
348
            GROUP BY at.tag
            ORDER BY at.tag ASC
            ", array($USER->id));
349
350
351
352
353
354
355
356
    }

    public static function has_instance_config() {
        return true;
    }

    public static function instance_config_form(BlockInstance $instance) {
        global $USER;
357
        safe_require('artefact', 'blog');
358
        $configdata = $instance->get('configdata');
359
        $tags = self::get_chooseable_tags();
360

361
        $elements = array();
362
        if (!empty($tags)) {
363
            $tagselect = array();
364
365
366
367
368
369
370
371
372
373
374
375
            $typecast = is_postgres() ? '::varchar' : '';
            $tagrecords = get_records_sql_array("
                 SELECT
                     (CASE
                         WHEN bt.tag LIKE 'tagid_%' THEN CONCAT(i.displayname, ': ', t.tag)
                         ELSE bt.tag
                     END) AS tag, bt.tagtype
                 FROM {blocktype_taggedposts_tags} bt
                 LEFT JOIN {tag} t ON t.id" . $typecast . " = SUBSTRING(bt.tag, 7)
                 LEFT JOIN {institution} i ON i.name = t.ownerid
                 WHERE bt.block_instance = ?
                ORDER BY tagtype DESC", array($instance->get('id')));
376
377
378
            if ($tagrecords) {
                foreach ($tagrecords as $tag) {
                    if ($tag->tagtype == PluginBlocktypeTaggedposts::TAGTYPE_INCLUDE) {
379
                        $tagselect[] = $tag->tag;
380
381
                    }
                    else {
382
                        $tagselect[] = '-' . $tag->tag;
383
384
                    }
                }
385
            }
386
387
388
389
            // The javascript to alter the display for the excluded tags
            $excludetag = get_string('excludetag', 'blocktype.blog/taggedposts');
            $formatSelection = <<<EOF
function (item, container) {
390
    item.title = item.id;
391
    if (item.id[0] == "-") {
392
393
        container.addClass("tagexcluded");
        if (!item.text.match(/sr\-only/)) {
394
            return '<span class="sr-only">{$excludetag}</span>' + jQuery('<div>').text(item.text).html();
395
        }
396
397
398
399
    }
    return item.text;
}
EOF;
400
            $elements['tagselect'] = array(
401
                'type'          => 'autocomplete',
402
                'title'         => get_string('taglist','blocktype.blog/taggedposts'),
403
                'description'   => get_string('taglistdesc1', 'blocktype.blog/taggedposts'),
404
405
406
407
408
409
                'defaultvalue'  => $tagselect,
                'ajaxurl' => get_config('wwwroot') . 'artefact/blog/blocktype/taggedposts/taggedposts.json.php',
                'initfunction' => 'translate_ids_to_tags',
                'multiple' => true,
                'ajaxextraparams' => array(),
                'rules'         => array('required' => 'true'),
410
                'required'      => true,
411
412
413
                'blockconfig'   => true,
                'help'          => true,
                'mininputlength' => 0,
414
415
416
                'extraparams'   => array(
                        'templateSelection' => "$formatSelection",
                        // We'll escape the text on the PHP side, so select2 doesn't need to
417
418
419
420
421
422
423
424
                        'escapeMarkup' => 'function(textToEscape) {
                            if (textToEscape.match(/sr\-only/)) {
                                return textToEscape;
                            }
                            else {
                                return jQuery("<div>").text(textToEscape).html();
                            }
                        }',
425
                ),
426
            );
427
            $elements[] = PluginArtefactBlog::block_advanced_options_element($configdata, 'taggedposts');
428
429
430
431
432
433
434
            $elements['count']  = array(
                'type'          => 'text',
                'title'         => get_string('itemstoshow', 'blocktype.blog/taggedposts'),
                'description'   => get_string('betweenxandy', 'mahara', 1, 100),
                'defaultvalue'  => isset($configdata['count']) ? $configdata['count'] : 10,
                'size'          => 3,
                'rules'         => array('integer' => true, 'minvalue' => 1, 'maxvalue' => 999),
435
            );
436
            $elements['full']  = array(
437
                'type'         => 'switchbox',
438
                'title'        => get_string('showjournalitemsinfull', 'blocktype.blog/taggedposts'),
439
                'description'  => get_string('showjournalitemsinfulldesc1', 'blocktype.blog/taggedposts'),
440
441
442
443
                'defaultvalue' => isset($configdata['full']) ? $configdata['full'] : false,
            );

            return $elements;
444
445
446
447
448
        }
        else {
            return array(
                'notags'    => array(
                    'type'          => 'html',
449
450
                    'title'         => get_string('taglist', 'blocktype.blog/taggedposts'),
                    'value'         => get_string('notagsavailable', 'blocktype.blog/taggedposts'),
451
452
453
454
455
456
                ),
            );
        }

    }

457
458
459
460
    public static function delete_instance(BlockInstance $instance) {
        delete_records('blocktype_taggedposts_tags', 'block_instance', $instance->get('id'));
    }

461
    public static function instance_config_validate(Pieform $form, $values) {
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477

        if (empty($values['tagselect'])) {
            // We don't have a tagselect field due to no journal entries having a tag
            $form->set_error(null, get_string('notagsavailableerror', 'blocktype.blog/taggedposts'));
        }
        else {
            // Need to fully check that the returned array is empty
            $values['tagselect'] = array_filter($values['tagselect']);
            if (empty($values['tagselect'])) {
                $result['message'] = get_string('required', 'mahara');
                $form->set_error('tagselect', $form->i18n('rule', 'required', 'required'), false);
                $form->reply(PIEFORM_ERR, $result);
            }
        }
    }

478
    public static function instance_config_save($values, BlockInstance $instance) {
479
480
481
        $tagselect = $values['tagselect'];
        unset($values['tagselect']);
        if (!empty($tagselect)) {
482
            self::save_tag_selection($tagselect, $instance);
483
484
485
486
        }
        return $values;
    }

487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
    public static function save_tag_selection($tagselect, BlockInstance $instance) {
        delete_records('blocktype_taggedposts_tags', 'block_instance', $instance->get('id'));
        foreach ($tagselect as $tag) {
            $value = PluginBlocktypeTaggedposts::TAGTYPE_INCLUDE;
            if (substr($tag, 0, 1) == '-') {
                $value = PluginBlocktypeTaggedposts::TAGTYPE_EXCLUDE;
                $tag = substr($tag, 1);
            }
            // If tag is institution tag, save it's correct form.
            if (strpos($tag, ':')) {
                $tagarray = explode(': ', $tag);
                $sql = "SELECT t.id
                    FROM {tag} t
                    JOIN {institution} i ON i.name = t.ownerid
                    WHERE t.tag = ? AND t.resourcetype = 'institution' AND i.displayname = ?";
                $insttagid = get_field_sql($sql, array($tagarray[1], $tagarray[0]));
                $tag = 'tagid_' . $insttagid;
            }
            $todb = new stdClass();
            $todb->block_instance = $instance->get('id');
            $todb->tag = htmlspecialchars_decode($tag);
            $todb->tagtype = $value;
            insert_record('blocktype_taggedposts_tags', $todb);
        }
    }

513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
    /**
     * Returns a list of artefact IDs that are "in" this blockinstance.
     *
     * {@internal{Because links to artefacts within blogposts don't count
     * as making those artefacts 'children' of the blog post, we have to add
     * them directly to the blog.}}
     *
     * @return array List of artefact IDs that are 'in' this blog - all
     *               blogposts in it plus all links to other artefacts that are
     *               part of the blogpost text. Note that proper artefact
     *               children, such as blog post attachments, aren't included -
     *               the artefact parent cache is used for them
     * @see PluginBlocktypeBlogPost::get_artefacts()
     */
    public static function get_artefacts(BlockInstance $instance) {
        $artefacts = array();
        $blogposts = self::get_blog_posts_in_block($instance);
        foreach ($blogposts as $blogpost) {
            $artefacts[] = $blogpost->id;

            $blogpostobj = $instance->get_artefact_instance($blogpost->id);
            $artefacts = array_merge($artefacts, $blogpostobj->get_referenced_artefacts_from_postbody());
        }
        $artefacts = array_unique($artefacts);
        return $artefacts;
    }

540
    public static function default_copy_type() {
541
        return 'nocopy';
542
543
544
545
546
547
548
549
550
551
552
    }

    /**
     * Taggedposts blocktype is only allowed in personal views, because currently
     * there's no such thing as group/site blogs
     */
    public static function allowed_in_view(View $view) {
        return $view->get('owner') != null;
    }

}
553
554
555
556
557
558
559
560
561
562
563
564

function translate_ids_to_tags(array $ids) {
    $ids = array_diff($ids, array(''));
    $results = array();
    if (!empty($ids)) {
        foreach ($ids as $id) {
            $text = (substr($id, 0, 1) == '-') ? substr($id, 1) : $id;
            $results[] = (object) array('id' => $id, 'text' => $text);
        }
    }
    return $results;
}