lib.php 77.5 KB
Newer Older
1
2
3
4
5
<?php
/**
 *
 * @package    mahara
 * @subpackage blocktype
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
14
 *
 */

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


15
16
17
18
19
20
21
22
/**
 * Helper interface to hold IPluginBlocktype's abstract static methods
 */
interface IPluginBlocktype {
    public static function get_title();

    public static function get_description();

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
    /**
     * Should be an array of blocktype categories that this block should be included in,
     * for determining how it shows up in the page editor's pallette of blocks.
     * See the function get_blocktype_categories() in lib/upgrade.php for the full list.
     *
     * A block can belong to multiple categories.
     *
     * The special category "shortcut" will make the blocktype show up on the top of the
     * block pallette instead of in a category.
     *
     * Blocktypes can have a sortorder in each category, that determines how they are
     * ordered in the category. To give a sortorder, put the category as the array key,
     * and the sortorder as the array value, like so:
     *
     * return array(
     *     'shortcut' => 1000,
     *     'general'  => 500,
     * );
     *
     * If no sortorder is provided, the blocktype's sortorder will default to 100,000.
     * Core blocktypes should have sortorders separated by 1,000 to give space for 3rd-party
     * blocks in between.
     *
     * Blocktypess with the same sortorder are sorted by blocktype name.
     *
     * @return array
     */
50
51
    public static function get_categories();

52
    public static function render_instance(BlockInstance $instance, $editing=false, $versioning=false);
53
54
}

55
56
57
58
/**
 * Base blocktype plugin class
 * @abstract
 */
59
abstract class PluginBlocktype extends Plugin implements IPluginBlocktype {
60

61
62
63
64
65
66
67
68
    /**
     * Default sortorder for a blocktype that has no sortorder defined for a
     * particular blocktype category that it's in. See IPluginBlocktype::get_categories()
     * for a full explanation of blocktype sortorder.
     * @var int
     */
    public static $DEFAULT_SORTORDER = 100000;

69
70
71
72
73
74
    /**
     * Used in the get_blocktype_list_icon() method
     */
    const BLOCKTYPE_LIST_ICON_PNG = 0;
    const BLOCKTYPE_LIST_ICON_FONTAWESOME = 1;

75
76
77
78
    public static function get_plugintype_name() {
        return 'blocktype';
    }

79
    /**
80
     * Optionally specify a place for a block to link to. This will be rendered in the block header
81
82
83
84
85
86
87
88
     * in templates
     * @var BlockInstance
     * @return String or false
     */
    public static function get_link(BlockInstance $instance) {
        return false;
    }

89
90
91
92
93
94
95
96
97
98
    public static function get_theme_path($pluginname) {
        if (($artefactname = blocktype_artefactplugin($pluginname))) {
            // Path for block plugins that sit under an artefact
            return 'artefact/' . $artefactname . '/blocktype/' . $pluginname;
        }
        else {
            return parent::get_theme_path($pluginname);
        }
    }

99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    /**
     * If the theme wants to display CSS icons for Mahara blocks, then it will
     * call this method to find out the name of the CSS icon to use. If this
     * method returns false, it will fall back to using the thumbnail.png
     *
     * In the core themes, these icons come from FontAwesome.
     * See htdocs/theme/raw/sass/lib/font-awesome/_icons.scss
     * for the full list of icons loaded by Mahara. (Note that this may change
     * from one Mahara version to another, as we upgrade FontAwesome.)
     * (Also note that the .scss files are stripped from the Mahara packaged
     * ZIP file. IF you don't have them, look in our git repository:
     * https://git.mahara.org/mahara/mahara/blob/master/htdocs/theme/raw/sass/lib/font-awesome/_icons.scss
     *
     * For the core blocktypes, we have "aliased" the name of the block
     * to the appropriate icon. See theme/raw/sass/lib/typography/_icons.scss.
     *
     * @param string $blocktypename The name of the blocktype
     * (since blocktype classes don't always know their own name as a string)
     * @return mixed Name of icon, or boolean false to fall back to thumbnail.png
     */
    public static function get_css_icon($blocktypename) {
        return false;
    }

123
124
    public static function extra_xmldb_substitution($xml) {
        return str_replace(
Aaron Wells's avatar
Aaron Wells committed
125
        '<!-- PLUGINTYPE_INSTALLED_EXTRAFIELDS -->',
126
        ' <FIELD NAME="artefactplugin" TYPE="char" LENGTH="255" NOTNULL="false" />',
127
        str_replace(
Aaron Wells's avatar
Aaron Wells committed
128
            '<!-- PLUGINTYPE_INSTALLED_EXTRAKEYS -->',
129
130
131
132
133
134
            '<KEY NAME="artefactpluginfk" TYPE="foreign" FIELDS="artefactplugin" REFTABLE="artefact_installed" REFFIELDS="name" />',
            $xml
            )
        );
    }

Aaron Wells's avatar
Aaron Wells committed
135
136
    /**
     * override this to return true if the blocktype
137
138
139
140
141
142
     * can only reasonably be placed once in a view
    */
    public static function single_only() {
        return false;
    }

Aaron Wells's avatar
Aaron Wells committed
143
144
145
146
147
    /**
     * Indicates whether this block can be loaded by Ajax after the page is done. This
     * improves page-load times by allowing blocks to be rendered in parallel instead
     * of in serial.
     *
148
     * You should avoid enabling this for:
Aaron Wells's avatar
Aaron Wells committed
149
150
151
     * - Blocks with particularly finicky Javascript contents
     * - Blocks that need to write to the session (the Ajax loader uses the session in read-only)
     * - Blocks that won't take long to render (static content, external content)
152
153
     * - Blocks that use hide_title_on_empty_content() (since you have to compute the content first
     * in order for that to work)
Aaron Wells's avatar
Aaron Wells committed
154
155
156
157
     *
     * @return boolean
     */
    public static function should_ajaxify() {
158
        return false;
Aaron Wells's avatar
Aaron Wells committed
159
160
    }

161
162
163
164
165
166
167
168
    /**
     * Allows block types to override the instance's title.
     *
     * For example: My Views, My Groups, My Friends, Wall
     */
    public static function override_instance_title(BlockInstance $instance) {
    }

169
170
171
172
173
174
175
176
177
178
179
180
    public static function get_viewtypes() {
        static $viewtypes = null;

        if (is_null($viewtypes)) {
            $viewtypes = get_column('view_type', 'type');
            if (!$viewtypes) {
                $viewtypes = array();
            }
        }

        return $viewtypes;
    }
181

182
183
184
185
186
187
188
189
190
    /**
    * This function must be implemented in the subclass if it requires
    * javascript. It returns an array of javascript files, either local
    * or remote.
    */
    public static function get_instance_javascript(BlockInstance $instance) {
        return array();
    }

191
192
193
194
195
196
197
198
199
   /**
    * This function must be implemented in the subclass if it requires
    * toolbar options for the view. It returns an array of button <a> tags and/or
    * html to display in the toobar area.
    */
    public static function get_instance_toolbars(BlockInstance $instance) {
        return array();
    }

200
201
202
203
204
205
206
207
208
    /**
    * This function must be implemented in the subclass if it requires
    * css file outside of sass compiled css. It returns an array of css files, either local
    * or remote.
    */
    public static function get_instance_css(BlockInstance $instance) {
        return array();
    }

209
210
211
212
213
214
    /**
     * Inline js to be executed when a block is rendered.
     */
    public static function get_instance_inline_javascript(BlockInstance $instance) {
    }

215
216
217
    /**
    * subclasses can override this if they need to do something a bit special
    * eg more than just what the BlockInstance->delete function does.
Aaron Wells's avatar
Aaron Wells committed
218
    *
219
220
221
222
    * @param BlockInstance $instance
    */
    public static function delete_instance(BlockInstance $instance) { }

223
224
225
    /**
    * This function must be implemented in the subclass if it has config
    */
226
227
    public static function instance_config_form(BlockInstance $instance) {
        throw new SystemException(get_string('blocktypemissingconfigform', 'error', $instance->get('blocktype')));
228
229
    }

230
231
232
233
234
235
236
237
238
    /**
    * Thus function must be implemented in the subclass is it has an
    * instance config form that requires javascript. It returns an
    * array of javascript files, either local or remote.
    */
    public static function get_instance_config_javascript(BlockInstance $instance) {
        return array();
    }

239
    /**
Aaron Wells's avatar
Aaron Wells committed
240
     * Blocktype plugins can implement this to perform custom pieform
241
242
243
     * validation, should they need it
     */
    public static function instance_config_validate(Pieform $form, $values) { }
244

245
    /**
246
    * Most blocktype plugins will attach to artefacts.
Aaron Wells's avatar
Aaron Wells committed
247
248
249
    * They should implement this function to keep a list of which ones. The
    * result of this method is used to populate the view_artefact table, and
    * thus decide whether an artefact is in a view for the purposes of access.
250
251
252
253
    * See {@link artefact_in_view} for more information about this.
    *
    * Note that it should just handle top level artefacts.
    * The cache rebuilder will figure out the children.
254
255
256
    *
    * @return array ids of artefacts in this block instance
    */
257
258
259
260
261
262
263
264
265
266
    public static function get_artefacts(BlockInstance $instance) {
        $configdata = $instance->get('configdata');
        if (isset($configdata['artefactids']) && is_array($configdata['artefactids'])) {
            return $configdata['artefactids'];
        }
        if (!empty($configdata['artefactid'])) {
            return array($configdata['artefactid']);
        }
        return false;
    }
267
268
269
270
271
272
273
274
275
276
277
    /**
    * Some blocktype plugins will have related artefacts based on artefactid.
    * They should implement this function to keep a list of which ones. The
    * result of this method is used to work out what artefacts where present at
    * the time of the version creation to save in view_versioning.
    *
    * Note that it should just handle child level artefacts.
    */
    public static function get_current_artefacts(BlockInstance $instance) {
        return array();
    }
278

Aaron Wells's avatar
Aaron Wells committed
279
    /**
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
     * If this blocktype contains artefacts, and uses the artefactchooser
     * Pieform element to choose them, this method must return the definition
     * for the element.
     *
     * This is used in view/artefactchooser.json.php to build pagination for
     * the element.
     *
     * The element returned MUST have the name key set to either 'artefactid'
     * or 'artefactids', depending on whether 'selectone' is true or false.
     *
     * The element must also have the 'blocktype' key set to the name of the
     * blocktype that the form is for.
     *
     * @param mixed $default The default value for the element
     */
    public static function artefactchooser_element($default=null) {
    }

    /**
     *
     * this is different to has_config - has_config is plugin wide config settings
     * this is specific to this TYPE of plugin and relates to whether individual instances
     * can be configured within a view
     */
304
    public static function has_instance_config() {
305
306
        return false;
    }
307

308
    public static function category_title_from_name($name) {
309
        $title = get_string('blocktypecategory.'. $name, 'view');
310
311
312
313
314
315
        if (strpos($title, '[[') !== 0) {
            return $title;
        }
        // else we're an artefact
        return get_string('pluginname', 'artefact.' . $name);
    }
316

317
318
319
320
321
    public static function category_description_from_name($name) {
        $description = get_string('blocktypecategorydesc.'. $name, 'view');
        return $description;
    }

322
    public static function get_blocktypes_for_category($category, View $view) {
323
        $sql = 'SELECT bti.name, bti.artefactplugin
Aaron Wells's avatar
Aaron Wells committed
324
            FROM {blocktype_installed} bti
325
            JOIN {blocktype_installed_category} btic ON btic.blocktype = bti.name
326
327
            JOIN {blocktype_installed_viewtype} btiv ON btiv.blocktype = bti.name
            WHERE btic.category = ? AND bti.active = 1 AND btiv.viewtype = ?
328
            ORDER BY btic.sortorder, bti.name';
329
        if (!$bts = get_records_sql_array($sql, array($category, $view->get('type')))) {
330
331
332
333
334
            return false;
        }

        $blocktypes = array();

335
336
337
338
        if (function_exists('local_get_allowed_blocktypes')) {
            $localallowed = local_get_allowed_blocktypes($category, $view);
        }

339
340
        foreach ($bts as $bt) {
            $namespaced = blocktype_single_to_namespaced($bt->name, $bt->artefactplugin);
341
            if (isset($localallowed) && is_array($localallowed) && !in_array($namespaced, $localallowed)) {
342
343
                continue;
            }
Aaron Wells's avatar
Aaron Wells committed
344
345
346
            safe_require('blocktype', $namespaced);
            // Note for later: this is Blocktype::allowed_in_view, which
            // returns true if the blocktype should be insertable into the
347
            // given view.
Aaron Wells's avatar
Aaron Wells committed
348
            // e.g. for blogs it returns false when view owner is not set,
349
            // because blogs can't be inserted into group views.
Aaron Wells's avatar
Aaron Wells committed
350
351
            // This could be different from whether a blockinstance is allowed
            // to be copied into a View (see the other place in this file where
352
353
            // allowed_in_view is called)
            //
Aaron Wells's avatar
Aaron Wells committed
354
355
356
            // Note also that if we want templates to be able to have all
            // blocktypes, we can add $view->get('template') here as part of
            // the condition, and also to View::addblocktype and
357
            // View::get_category_data
358
359
            $classname = generate_class_name('blocktype', $namespaced);
            if (call_static_method($classname, 'allowed_in_view', $view)) {
360
361
                $blocktypes[] = array(
                    'name'           => $bt->name,
362
363
364
                    'title'          => call_static_method($classname, 'get_title'),
                    'description'    => call_static_method($classname, 'get_description'),
                    'singleonly'     => call_static_method($classname, 'single_only'),
365
366
                    'artefactplugin' => $bt->artefactplugin,
                    'thumbnail_path' => get_config('wwwroot') . 'thumb.php?type=blocktype&bt=' . $bt->name . ((!empty($bt->artefactplugin)) ? '&ap=' . $bt->artefactplugin : ''),
367
                    'cssicon'        => call_static_method($classname, 'get_css_icon', $bt->name),
368
369
                );
            }
370
371
372
        }
        return $blocktypes;
    }
Richard Mansfield's avatar
Richard Mansfield committed
373

374
    /**
Aaron Wells's avatar
Aaron Wells committed
375
     * Takes config data for an existing blockinstance of this class and rewrites it so
376
377
     * it can be used to configure a block instance being put in a new view
     *
Aaron Wells's avatar
Aaron Wells committed
378
379
     * This is used at view copy time, to give blocktypes the chance to change
     * the configuration for a block based on aspects about the new view - for
380
381
     * example, who will own it.
     *
Aaron Wells's avatar
Aaron Wells committed
382
383
     * As an example - when the profile information blocktype is copied, we
     * want it so that all the fields that were configured previously are
384
385
386
387
     * pointing to the new owner's versions of those fields.
     *
     * The base method clears out any artefact IDs that are set.
     *
Aaron Wells's avatar
Aaron Wells committed
388
     * @param View $view The view that the blocktype will be placed into (e.g.
389
390
391
392
393
     *                   the View being created as a result of the copy)
     * @param array $configdata The configuration data for the old blocktype
     * @return array            The new configuration data.
     */
    public static function rewrite_blockinstance_config(View $view, $configdata) {
394
395
396
397
398
399
400
401
402
        if (isset($configdata['artefactid'])) {
            $configdata['artefactid'] = null;
        }
        if (isset($configdata['artefactids'])) {
            $configdata['artefactids'] = array();
        }
        return $configdata;
    }

403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
    /**
     * Takes extra config data for an existing blockinstance of this class
     * and rewrites it so it can be used to configure a new block instance being put
     * in a new view
     *
     * This is used at view copy time, to give blocktypes the chance to change
     * the extra configuration for a block based on aspects about the new view
     *
     * As an example - when the 'Text' blocktype is copied, we
     * want it so that all image urls in the $configdata['text'] are
     * pointing to the new images.
     *
     * @param View $view The view that the blocktype will be placed into (e.g.
     *                   the View being created as a result of the copy)
     * @param BlockInstance $block The new block
     * @param array $configdata The configuration data for the old blocktype
     * @param array $artefactcopies The mapping of old artefact ids to new ones
     * @return array            The new configuration data.
     */
    public static function rewrite_blockinstance_extra_config(View $view, BlockInstance $block, $configdata, $artefactcopies) {
        return $configdata;
    }

    /**
     * Rewrite extra config data for a blockinstance of this class when
     * importing its view from Leap
     *
     * As an example - when the 'text' blocktype is imported, we
     * want all image urls in the $configdata['text'] are
     * pointing to the new images.
     *
     * @param array $artefactids The mapping of leap entries to their artefact ID
     *      see more PluginImportLeap->artefactids
     * @param array $configdata The imported configuration data for the blocktype
     * @return array            The new configuration data.
     */
    public static function import_rewrite_blockinstance_extra_config_leap(array $artefactids, array $configdata) {
        return $configdata;
    }

443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459

    /**
     * Rewrite a block instance's relationships to views & collections at the end of the leap import process.
     *
     * (For instance the navigation block stores a collection ID, and needs to know the new ID the
     * collection wound up with.)
     *
     * This method is called at the end of the import process. You will probably want to access the
     * $importer->viewids, $importer->collectionids, and/or $importer->artefactids fields
     *
     * @param int $blockinstanceid ID of the block instance.
     * @param PluginImportLeap $importer The importer object.
     */
    public static function import_rewrite_blockinstance_relationships_leap($blockinstanceid, $importer) {
        // Do nothing, in the default case
    }

460
461
    /*
     * The copy_type of a block affects how it should be copied when its view gets copied.
462
463
464
465
466
467
468
469
470
     * nocopy:       The block doesn't appear in the new view at all.
     * shallow:      A new block of the same type is created in the new view with a configuration as specified by the
     *               rewrite_blockinstance_config method
     * reference:    Block configuration is copied as-is.  If the block contains artefacts, the original artefact ids are
     *               retained in the new block's configuration even though they may have a different owner from the view.
     * full:         All artefacts referenced by the block are copied to the new owner's portfolio, and ids in the new
     *               block are updated to point to the copied artefacts.
     * fullinclself: All artefacts referenced by the block are copied, whether we are copying to a new owner's portfolio
     *               or our own one, and ids in the new block are updated to point to the copied artefacts.
471
     *
472
     * If the old owner and the new owner are the same, reference is used unless 'fullinclself' is specified.
473
474
475
476
477
478
     * If a block contains no artefacts, reference and full are equivalent.
     */
    public static function default_copy_type() {
        return 'shallow';
    }

479
480
481
482
483
484
485
486
    /*
     * The ignore_copy_artefacttypes of a block affects which artefacttypes should be ignored when copying.
     * You can specify which artefacts to ignore by an array of artefacttypes.
     */
    public static function ignore_copy_artefacttypes() {
        return array();
    }

487
488
489
    /**
     * Whether this blocktype is allowed in the given View.
     *
Aaron Wells's avatar
Aaron Wells committed
490
491
     * Some blocktypes may wish to limit whether they're allowed in a View if,
     * for example, they make no sense when the view is owned by a certain type
492
493
     * of owner.
     *
Aaron Wells's avatar
Aaron Wells committed
494
     * For example, the 'profile information' blocktype makes no sense in a
495
496
     * group View.
     *
Aaron Wells's avatar
Aaron Wells committed
497
498
     * Of course, blocktypes could implement stranger rules - e.g. only allow
     * when the view has 'ponies' in its description (BTW: such blocktypes
499
500
501
     * would be totally awesome).
     *
     * @param View     The View to check
Aaron Wells's avatar
Aaron Wells committed
502
     * @return boolean Whether blocks of this blocktype are allowed in the
503
504
505
506
507
508
     *                 given view.
     */
    public static function allowed_in_view(View $view) {
        return true;
    }

509
    /**
Aaron Wells's avatar
Aaron Wells committed
510
     * Given a block instance, returns a hash with enough information so that
511
512
     * we could reconstruct it if given this information again.
     *
Aaron Wells's avatar
Aaron Wells committed
513
514
     * Import/Export routines can serialise this information appropriately, and
     * unserialise it on the way back in, where it is passed to {@link
515
516
517
518
519
520
     * import_create_blockinstance()} for creation of a new block instance.
     *
     * @param BlockInstance $bi The block instance to export config for
     * @return array The configuration required to import the block again later
     */
    public static function export_blockinstance_config(BlockInstance $bi) {
521
522
523
        $configdata = $bi->get('configdata');

        if (is_array($configdata)) {
Aaron Wells's avatar
Aaron Wells committed
524
525
            // Unset a bunch of stuff that we don't want to export. These fields
            // weren't being cleaned up before blockinstances were being saved
526
527
528
529
530
531
532
533
534
535
536
            // previously, so we make sure they're not going to be in the result
            unset($configdata['blockconfig']);
            unset($configdata['id']);
            unset($configdata['change']);
            unset($configdata['new']);
        }
        else {
            $configdata = array();
        }

        return $configdata;
537
    }
538
539

    /**
Francois Marier's avatar
Francois Marier committed
540
     * Exports configuration data the format required for Leap2A export.
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
     *
     * This format is XML, and as the exporter can't generate complicated XML
     * structures, we have to json_encode all the values.
     *
     * Furthermore, because of how json_encode and json_decode "work" in PHP,
     * we make double sure that our values are all inside arrays. See the
     * craziness that is PHP bugs 38680 and 46518 for more information.
     *
     * The array is assumed to be there when importing, so if you're overriding
     * this method and don't wrap any values in an array, you can expect import
     * to growl at you and not import your config.
     *
     * @param BlockInstance $bi The block instance to export config for
     * @return array The configuration required to import the block again later
     */
    public static function export_blockinstance_config_leap(BlockInstance $bi) {
        $configdata = call_static_method(generate_class_name('blocktype', $bi->get('blocktype')), 'export_blockinstance_config', $bi);
        foreach ($configdata as $key => &$value) {
            $value = json_encode(array($value));
        }
        return $configdata;
    }
563
564
565
566

    /**
     * Creates a block instance from a given configuration.
     *
567
568
     * The configuration is whatever was generated by {@link
     * export_blockinstance_config()}. This method doesn't have to worry about
569
570
     * setting the block title, or the position in the View.
     *
571
572
     * @param array $biconfig   The config to use to create the blockinstance
     * @param array $viewconfig The configuration for the view being imported
573
574
     * @return BlockInstance The new block instance
     */
575
    public static function import_create_blockinstance(array $biconfig, array $viewconfig) {
576
577
        $bi = new BlockInstance(0,
            array(
578
579
                'blocktype'  => $biconfig['type'],
                'configdata' => $biconfig['config'],
580
581
582
583
            )
        );

        return $bi;
584
585
    }

586
587
588
589
590
591
592
593
594
595
596
597
    /**
     * defines if the title should be shown if there is no content in the block
     *
     * If the title of the block should be hidden when there is no content,
     * override the the function in the blocktype class.
     *
     * @return boolean  whether the title of the block should be shown or not
     */
    public static function hide_title_on_empty_content() {
        return false;
    }

598
599
600
601
602
603
604
605
606
607
608
609
610
    /**
     * Defines if the title should be linked to an artefact view (if possible)
     * when viewing the block
     *
     * This method should be overridden in the child class, if a title link
     * is not desired.
     *
     * @return boolean whether to link the title or not
     */
    public static function has_title_link() {
        return true;
    }

611
612
613
614
615
616
617
618
619
620
621
622
623
    /**
     * Defines if the block is viewable by the logged in user
     *
     * This method should be overridden in the child class, if peer role
     * should be able to see the block
     *
     * @param array user access role for the view
     * @return boolean whether display the block content for the roles
     */
    public static function display_for_roles($roles) {
        return !(count($roles) == 1 && $roles[0] == 'peer');
    }

624
625
}

626

627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
/**
 * Mahara core blocks should extend this class. (Currently it only controls styling,
 * useful as a way of mapping the behavior of core blocks to theme items that are
 * not easily queried by the code.)
 */
abstract class MaharaCoreBlocktype extends PluginBlockType {

    /**
     * Use a css icon based on the name of the block
     * (These are defined in typography.scss)
     *
     * @param string $blocktypename
     * @return string
     */
    public static function get_css_icon($blocktypename) {
        return $blocktypename;
    }
}

/**
 * Old half-used "SystemBlockType" class. Deprecated, but still included because
 * some 3rd-party blocktypes use it.
 *
 * It was never clearly described what the purpose of this blocktype is; but most
 * likely its purpose was to indicate blocks that don't "contain" artefacts, such
 * as the "new views" block.
 *
 * But as long as your block isn't storing an item called "artefactid" or "artefactids"
 * in its blocktype.config field, then the default implementation of get_artefacts()
 * doesn't really matter.
 */
abstract class SystemBlockType extends PluginBlockType {
659
    public static function get_artefacts(BlockInstance $instance) {
660
661
662
        return array();
    }

663
    public final static function artefactchooser_element($default=null) {
664
    }
665
666
667
}


668
669
class BlockInstance {

670
671
672
673
    const RETRACTABLE_NO = 0;
    const RETRACTABLE_YES = 1;
    const RETRACTABLE_RETRACTED = 2;

674
675
    private $id;
    private $blocktype;
676
    private $artefactplugin;
677
    private $title;
678
    private $configdata = array();
679
    private $dirty;
Penny Leach's avatar
Penny Leach committed
680
681
    private $view;
    private $view_obj;
682
    private $row;
Penny Leach's avatar
Penny Leach committed
683
    private $column;
684
    private $order;
Penny Leach's avatar
Penny Leach committed
685
686
687
688
    private $canmoveleft;
    private $canmoveright;
    private $canmoveup;
    private $canmovedown;
689
    private $maxorderincolumn;
690
    private $artefacts = array();
691
    private $temp = array();
692
    private $tags = array();
Penny Leach's avatar
Penny Leach committed
693
694
695
696
697

    public function __construct($id=0, $data=null) {
         if (!empty($id)) {
            if (empty($data)) {
                if (!$data = get_record('block_instance','id',$id)) {
Aaron Wells's avatar
Aaron Wells committed
698
699
700
                    // TODO: 1) doesn't need get string here if this is the
                    // only place the exception is used - can be done in the
                    // class itself. 2) String needs to be defined, or taken
701
                    // from lang/*/view.php where there is already one for it
Penny Leach's avatar
Penny Leach committed
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
                    throw new BlockInstanceNotFoundException(get_string('blockinstancenotfound', 'error', $id));
                }
            }
            $this->id = $id;
        }
        else {
            $this->dirty = true;
        }
        if (empty($data)) {
            $data = array();
        }
        foreach ((array)$data as $field => $value) {
            if (property_exists($this, $field)) {
                $this->{$field} = $value;
            }
        }
718
        $this->artefactplugin = blocktype_artefactplugin($this->blocktype);
Penny Leach's avatar
Penny Leach committed
719
    }
720
721
722
723
724

    public function get($field) {
        if (!property_exists($this, $field)) {
            throw new InvalidArgumentException("Field $field wasn't found in class " . get_class($this));
        }
Penny Leach's avatar
Penny Leach committed
725
726
727
728
729
730
        if ($field == 'configdata') {
            // make sure we unserialise it
            if (!is_array($this->configdata)) {
                $this->configdata = unserialize($this->configdata);
            }
        }
731
732
733
734
735
736
737
738
739
740
741
742
743
744
        if ($field == 'tags') {
            $typecast = is_postgres() ? '::varchar' : '';
            $this->tags = get_column_sql("
            SELECT
                (CASE
                    WHEN t.tag LIKE 'tagid_%' THEN CONCAT(i.displayname, ': ', t2.tag)
                    ELSE t.tag
                END) AS tag, t.resourceid
            FROM {tag} t
            LEFT JOIN {tag} t2 ON t2.id" . $typecast . " = SUBSTRING(t.tag, 7)
            LEFT JOIN {institution} i ON i.name = t2.ownerid
            WHERE t.resourcetype = ? AND t.resourceid = ?
            ORDER BY tag", array('blocktype', $this->get('id')));
        }
Penny Leach's avatar
Penny Leach committed
745
746
747
748
749
750
751
        if (strpos($field, 'canmove') === 0) {
            return $this->can_move(substr($field, strlen('canmove'))); // needs to be calculated.
        }
        if ($field == 'maxorderincolumn') {
            // only fetch this when we're asked, it's a db query.
            if (empty($this->maxorderincolumn)) {
                $this->maxorderincolumn = get_field(
Aaron Wells's avatar
Aaron Wells committed
752
753
                    'block_instance',
                    'max("order")',
Penny Leach's avatar
Penny Leach committed
754
755
756
                    'view', $this->view, 'column', $this->column);
            }
        }
757
758
759
760
761
        return $this->{$field};
    }

    public function set($field, $value) {
        if (property_exists($this, $field)) {
762
763
764
            if ($field == 'tags') {
                $this->set_tags($value);
            }
Penny Leach's avatar
Penny Leach committed
765
            if ($field == 'configdata') {
766
                $value = serialize($value);
Penny Leach's avatar
Penny Leach committed
767
            }
768
            if ($this->{$field} !== $value) {
769
770
                // only set it to dirty if it's changed
                $this->dirty = true;
771
                $this->{$field} = $value;
772
773
774
            }
            return true;
        }
775
        throw new ParamOutOfRangeException("Field $field wasn't found in class " . get_class($this));
Penny Leach's avatar
Penny Leach committed
776
777
    }

778
779
780
    private function set_tags($tags) {
        global $USER;

781
782
783
784
        if (empty($this->view_obj)) {
            $this->get_view();
        }

785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
        if ($this->view_obj->get('group')) {
            $ownertype = 'group';
            $ownerid = $this->view_obj->get('group');
        }
        else if ($this->view_obj->get('institution')) {
            $ownertype = 'institution';
            $ownerid = $this->view_obj->get('institution');
        }
        else {
            $ownertype = 'user';
            $ownerid = $this->view_obj->get('owner');
        }
        $this->tags = check_case_sensitive($tags, 'tag');
        delete_records('tag', 'resourcetype', 'blocktype', 'resourceid', $this->get('id'));
        foreach ($this->tags as $tag) {
            // truncate the tag before insert it into the database
            $tag = substr($tag, 0, 128);
802
            $tag = check_if_institution_tag($tag);
803
804
805
806
807
808
809
810
811
812
813
814
815
816
            insert_record('tag',
                (object)array(
                    'resourcetype' => 'blocktype',
                    'resourceid' => $this->get('id'),
                    'ownertype' => $ownertype,
                    'ownerid' => $ownerid,
                    'tag' => $tag,
                    'ctime' => db_format_timestamp(time()),
                    'editedby' => $USER->get('id'),
                )
            );
        }
    }

817
818
819
820
821
822
823
824
    // returns false if it finds a bad attachment
    // returns true if all attachments are allowed
    private function verify_attachment_permissions($id) {
        global $USER;

        if (is_array($id)) {
            foreach ($id as $id) {
                $file = artefact_instance_from_id($id);
825
                if (!$USER->can_view_artefact($file)) {
826
827
828
829
830
831
832
                    // bail out now as at least one attachment is bad
                    return false;
                }
            }
        }
        else {
            $file = artefact_instance_from_id($id);
833
            if (!$USER->can_view_artefact($file)) {
834
835
836
837
838
839
                return false;
            }
        }
        return true;
    }

Nigel McNie's avatar
Nigel McNie committed
840
    public function instance_config_store(Pieform $form, $values) {
841
        global $SESSION, $USER;
842

Nigel McNie's avatar
Nigel McNie committed
843
844
845
846
        // Destroy form values we don't care about
        unset($values['sesskey']);
        unset($values['blockinstance']);
        unset($values['action_configureblockinstance_id_' . $this->get('id')]);
847
848
849
850
        unset($values['blockconfig']);
        unset($values['id']);
        unset($values['change']);
        unset($values['new']);
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
        if (isset($values['retractable'])) {
            switch ($values['retractable']) {
                case BlockInstance::RETRACTABLE_YES:
                    $values['retractable'] = 1;
                    $values['retractedonload'] = 0;
                    break;
                case BlockInstance::RETRACTABLE_RETRACTED:
                    $values['retractable'] = 1;
                    $values['retractedonload'] = 1;
                    break;
                case BlockInstance::RETRACTABLE_NO:
                default:
                    $values['retractable'] = 0;
                    $values['retractedonload'] = 0;
                    break;
            }
        }
Nigel McNie's avatar
Nigel McNie committed
868

869
870
871
        // make sure that user is allowed to publish artefact. This is to stop
        // hacking of form value to attach other users private data.
        $badattachment = false;
872
873
        if (isset($values['blocktemplate']) && !empty($values['blocktemplate'])) {
            // Ignore check on artefactids as they are not relating to actual artefacts
874
        }
875
876
877
878
879
880
881
        else {
            if (!empty($values['artefactid'])) {
                $badattachment = !$this->verify_attachment_permissions($values['artefactid']);
            }
            if (!empty($values['artefactids'])) {
                $badattachment = !$this->verify_attachment_permissions($values['artefactids']);
            }
882
        }
883

884
885
886
887
888
889
890
        if ($badattachment) {
            $result['message'] = get_string('unrecoverableerror', 'error');
            $form->set_error(null, $result['message']);
            $form->reply(PIEFORM_ERR, $result);
            exit();
        }

891
892
893
894
895
896
897
898
899
900
901
902
        $redirect = '/view/blocks.php?id=' . $this->get('view');
        if (param_boolean('new', false)) {
            $redirect .= '&new=1';
        }
        if ($category = param_alpha('c', '')) {
            $redirect .= '&c='. $category;
        }

        $result = array(
            'goto' => $redirect,
        );

Nigel McNie's avatar
Nigel McNie committed
903
        if (is_callable(array(generate_class_name('blocktype', $this->get('blocktype')), 'instance_config_save'))) {
904
905
906
907
908
909
910
911
            try {
                $values = call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'instance_config_save', $values, $this);
            }
            catch (MaharaException $e) {
                $result['message'] = $e instanceof UserException ? $e->getMessage() : get_string('unrecoverableerror', 'error');
                $form->set_error(null, $result['message']);
                $form->reply(PIEFORM_ERR, $result);
            }
Nigel McNie's avatar
Nigel McNie committed
912
913
        }

914
915
        $title = (isset($values['title'])) ? $values['title'] : '';
        unset($values['title']);
916

917
918
919
920
921
        if (isset($values['tags'])) {
            $this->set('tags', $values['tags']);
            unset($values['tags']);
        }

922
923
924
925
926
        // A block may return a list of other blocks that need to be
        // redrawn after configuration of this block.
        $torender = !empty($values['_redrawblocks']) && $form->submitted_by_js() ? $values['_redrawblocks'] : array();
        unset($values['_redrawblocks']);

927
        $this->set('configdata', $values);
928
        $this->set('title', $title);
929

930
931
        $this->commit();

932
933
934
935
936
937
938
939
        try {
            $rendered = $this->render_editing(false, false, $form->submitted_by_js());
        }
        catch (HTMLPurifier_Exception $e) {
            $message = get_string('blockconfigurationrenderingerror', 'view') . ' ' . $e->getMessage();
            $form->reply(PIEFORM_ERR, array('message' => $message));
        }

940
941
942
        $result = array(
            'error'   => false,
            'message' => get_string('blockinstanceconfiguredsuccessfully', 'view'),
943
            'data'    => $rendered,
944
            'blockid' => $this->get('id'),
945
            'viewid'  => $this->get('view'),
946
            'goto'    => $redirect,
947
948
        );

949
950
951
952
953
954
955
956
957
958
959
960
        // Render all the other blocks in the torender list
        $result['otherblocks'] = array();
        foreach ($torender as $blockid) {
            if ($blockid != $result['blockid']) {
                $otherblock = new BlockInstance($blockid);
                $result['otherblocks'][] = array(
                    'blockid' => $blockid,
                    'data'    => $otherblock->render_editing(false, false, true),
                );
            }
        }

961
        $form->reply(PIEFORM_OK, $result);
Nigel McNie's avatar
Nigel McNie committed
962
963
    }

964
965
966
967
968
969
970
971
972
973
974
975
976
977
    public function get_title() {
        $blocktypeclass = generate_class_name('blocktype', $this->get('blocktype'));
        if ($override = call_static_method($blocktypeclass, 'override_instance_title', $this)) {
            return $override;
        }
        if ($title = $this->get('title') and $title != '') {
            return $title;
        }
        if (method_exists($blocktypeclass, 'get_instance_title')) {
            return call_static_method($blocktypeclass, 'get_instance_title', $this);
        }
        return '';
    }

978
979
980
981
    public function to_stdclass() {
        return (object)get_object_vars($this);
    }

Penny Leach's avatar
Penny Leach committed
982
    /**
Aaron Wells's avatar
Aaron Wells committed
983
     * Builds the HTML for the block, inserting the blocktype content at the
984
     * appropriate place
985
     *
Aaron Wells's avatar
Aaron Wells committed
986
     * @param bool $configure Whether to render the block instance in configure
987
     *                        mode
988
     * @return array Array with two keys: 'html' for raw html, 'javascript' for
989
     *               javascript to run
Penny Leach's avatar
Penny Leach committed
990
     */
991
    public function render_editing($configure=false, $new=false, $jsreply=false) {
992
993
        global $USER;

Penny Leach's avatar
Penny Leach committed
994
        safe_require('blocktype', $this->get('blocktype'));
995
996
        $movecontrols = array();

997
        $blocktypeclass = generate_class_name('blocktype', $this->get('blocktype'));
998
999
1000
1001
1002
1003
1004
        try {
            $title = $this->get_title();
        }
        catch (NotFoundException $e) {
            log_debug('Cannot render block title. Original error follows: ' . $e->getMessage());
            $title = get_string('notitle', 'view');
        }
1005

1006
        if ($configure) {
1007
            list($content, $js, $css) = array_values($this->build_configure_form($new));
1008
1009
        }
        else {
1010
            try {
1011
1012
1013
1014
1015
1016
1017
              $user_roles = get_column('view_access', 'role', 'usr', $USER->get('id'), 'view', $this->view);
              if (!call_static_method($blocktypeclass, 'display_for_roles', $user_roles)) {
                  $content = '';
                  $css = '';
                  $js = '';
              }
              else   {
1018
                $content = call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'render_instance', $this, true);
1019
1020
1021
                $jsfiles = call_static_method($blocktypeclass, 'get_instance_javascript', $this);
                $inlinejs = call_static_method($blocktypeclass, 'get_instance_inline_javascript', $this);
                $js = $this->get_get_javascript_javascript($jsfiles) . $inlinejs;
1022
                $css = '';
1023
              }
1024
            }
1025
            catch (NotFoundException $e) {
Aaron Wells's avatar
Aaron Wells committed
1026
1027
1028
                // Whoops - where did the image go? There is possibly a bug
                // somewhere else that meant that this blockinstance wasn't
                // told that the image was previously deleted. But the block
1029
1030
1031
1032
1033
1034
                // instance is not allowed to treat this as a failure
                log_debug('Artefact not found when rendering a block instance. '
                    . 'There might be a bug with deleting artefacts of this type? '
                    . 'Original error follows:');
                log_debug($e->getMessage());
                $content = '';
1035
                $js = '';
1036
                $css = '';
1037
            }
1038

1039
1040
1041
1042
1043
            if (!defined('JSON') && !$jsreply) {
                if ($this->get('canmoveleft')) {
                    $movecontrols[] = array(
                        'column' => $this->get('column') - 1,
                        'order'  => $this->get('order'),
1044
                        'title'  => $title == '' ? get_string('movethisblockleft', 'view') : get_string('moveblockleft', 'view', "'$title'"),
1045
                        'arrow'  => "icon icon-long-arrow-left",
1046
1047
1048
1049
1050
1051
1052
                        'dir'    => 'left',
                    );
                }
                if ($this->get('canmovedown')) {
                    $movecontrols[] = array(
                        'column' => $this->get('column'),
                        'order'  => $this->get('order') + 1,
1053
                        'title'  => $title == '' ? get_string('movethisblockdown', 'view') : get_string('moveblockdown', 'view', "'$title'"),
1054
                        'arrow'  => 'icon icon-long-arrow-down',
1055
1056
1057
1058
1059
1060
1061
                        'dir'    => 'down',
                    );
                }
                if ($this->get('canmoveup')) {
                    $movecontrols[] = array(
                        'column' => $this->get('column'),
                        'order'  => $this->get('order') - 1,
1062
                        'title'  => $title == '' ? get_string('movethisblockup', 'view') : get_string('moveblockup', 'view', "'$title'"),
1063
                        'arrow'  => 'icon icon-long-arrow-up',
1064
1065
1066
1067
1068
1069
1070
                        'dir'    => 'up',
                    );
                }
                if ($this->get('canmoveright')) {
                    $movecontrols[] = array(
                        'column' => $this->get('column') + 1,
                        'order'  => $this->get('order'),
1071
                        'title'  => $title == '' ? get_string('movethisblockright', 'view') : get_string('moveblockright', 'view', "'$title'"),
1072
                        'arrow'  => 'icon icon-long-arrow-right',
1073
1074
1075
                        'dir'    => 'right',
                    );
                }
1076
1077
            }
        }
1078
1079
1080

        $configtitle = $title == '' ? call_static_method($blocktypeclass, 'get_title') : $title;

1081
        $smarty = smarty_core();
1082
1083
        $id = $this->get('id');
        $smarty->assign('id',     $id);
1084
        $smarty->assign('viewid', $this->get('view'));
1085
        $smarty->assign('title',  $title);
1086
        $smarty->assign('row',    $this->get('row'));
1087
1088
        $smarty->assign('column', $this->get('column'));
        $smarty->assign('order',  $this->get('order'));
1089
        $smarty->assign('blocktype', $this->get('blocktype'));
1090
        $smarty->assign('movecontrols', $movecontrols);
1091
        $smarty->assign('configurable', call_static_method($blocktypeclass, 'has_instance_config'));
1092
        $smarty->assign('configure', $configure); // Used by the javascript to rewrite the block, wider.
1093
        $smarty->assign('configtitle',  $configtitle);
1094
        $smarty->assign('content', $content);
1095
        $smarty->assign('javascript', defined('JSON'));
1096
        $smarty->assign('strnotitle', get_string('notitle', 'view'));
1097
        $smarty->assign('strmovetitletext', $title == '' ? get_string('movethisblock', 'view') : get_string('moveblock', 'view', "'$title'"));
1098
1099
        $smarty->assign('strconfigtitletext', $title == '' ? get_string('configurethisblock1', 'view', $id) : get_string('configureblock1', 'view', "'$title'", $id));
        $smarty->assign('strremovetitletext', $title == '' ? get_string('removethisblock1', 'view', $id) : get_string('removeblock1', 'view', "'$title'", $id));
1100
        $smarty->assign('lockblocks', ($this->get_view()->get('lockblocks') && $this->get_view()->get('owner'))); // Only lock blocks for user's portfolio pages
1101

1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
        if (!$configure && $title) {
            $configdata = $this->get('configdata');
            if (isset($configdata['retractable']) && $configdata['retractable']) {
                $smarty->assign('retractable', true);
                if (defined('JSON') || $jsreply) {
                    $jssmarty = smarty_core();
                    $jssmarty->assign('id', $this->get('id'));
                    $js .= $jssmarty->fetch('view/retractablejs.tpl');
                }
            }
        }
1113
1114
1115
1116
        if (is_array($css)) {
            $css = array_unique($css);
        }
        return array('html' => $smarty->fetch('view/blocktypecontainerediting.tpl'), 'javascript' => $js, 'pieformcss' => $css);
Penny Leach's avatar
Penny Leach committed
1117
1118
    }

1119
1120
1121
1122
1123
1124
1125
1126


    public function order_artefacts_by_title($ids){
      $result = array();
      if ($ids) {
          $artefacts =  get_records_sql_array(
              'SELECT a.id, a.title FROM {artefact} a WHERE a.id in ( '. join(',', array_fill(0, count($ids), '?')) . ')', $ids
          );
1127
1128
1129
1130
1131
          if ($artefacts) {
              uasort($artefacts, array("BlockInstance", "my_files_cmp"));
              foreach ($artefacts as $artefact) {
                  $result[] = $artefact->id;
              }
1132
1133
1134
1135
1136
1137
1138
1139
1140
          }
      }
      return $result;
    }

    public static function my_files_cmp($a, $b) {
        return strnatcasecmp($a->title, $b->title);
    }

1141
1142
1143
1144
1145
    /**
     * To render the html of a block for viewing
     *
     * @param boolean $exporting  Indicate the rendering is for an export
     *                            If we are doing an export we can't render the block to be loaded via ajax
1146
1147
     * @param boolean $versioning Indicate the rendering is for an older view version
     *
1148
1149
     * @return the rendered block
     */
1150
    public function render_viewing($exporting=false, $versioning=false) {
1151
        global $USER;
1152

1153
1154
1155
        if (!safe_require_plugin('blocktype', $this->get('blocktype'))) {
            return;
        }
1156
1157
1158

        $smarty = smarty_core();

1159
1160
        $user_roles = get_column('view_access', 'role', 'usr', $USER->get('id'), 'view', $this->view);

1161
        $classname = generate_class_name('blocktype', $this->get('blocktype'));
1162
        $displayforrole = call_static_method($classname, 'display_for_roles', $user_roles);
1163
        $checkview = $this->get_view();
1164
1165
        if ($checkview->get('owner') == NULL ||
            ($USER->is_admin_for_user($checkview->get('owner')) && $checkview->is_objectionable())) {
1166
1167
            $displayforrole = true;
        }
1168
1169
1170
1171
        if (!$displayforrole) {
            $content = '';
            $smarty->assign('loadbyajax', false);
        }
1172
        else if (get_config('ajaxifyblocks') && call_static_method($classname, 'should_ajaxify') && $exporting === false && $versioning === false) {
1173
            $content = '';
1174
            $smarty->assign('loadbyajax', true);
1175
        }
Aaron Wells's avatar
Aaron Wells committed
1176
        else {
1177
            $smarty->assign('loadbyajax', false);
Aaron Wells's avatar
Aaron Wells committed
1178
            try {
1179
                $content = call_static_method($classname, 'render_instance', $this, false, $versioning);
Aaron Wells's avatar
Aaron Wells committed
1180
1181
            }
            catch (NotFoundException $e) {
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
                //Ignore not found error when fetching old verions of view
                if (!$versioning) {
                    // Whoops - where did the image go? There is possibly a bug
                    // somewhere else that meant that this blockinstance wasn't
                    // told that the image was previously deleted. But the block
                    // instance is not allowed to treat this as a failure
                    log_debug('Artefact not found when rendering a block instance. '
                        . 'There might be a bug with deleting artefacts of this type? '
                        . 'Original error follows:');
                    log_debug($e->getMessage());
                }
Aaron Wells's avatar
Aaron Wells committed
1193
1194
1195
                $content = '';
            }
        }
1196
1197

        $smarty->assign('id',     $this->get('id'));
1198
        $smarty->assign('blocktype', $this->get('blocktype'));
1199
        // hide the title if required and no content is present
1200
        if (call_static_method($classname, 'hide_title_on_empty_content')
1201
1202
1203
            && !trim($content)) {
            return;
        }
1204
1205
1206
1207
1208
1209
1210
1211
        try {
            $title = $this->get_title();
        }
        catch (NotFoundException $e) {
            log_debug('Cannot render block title. Original error follows: ' . $e->getMessage());
            $title = get_string('notitle', 'view');
        }
        $smarty->assign('title', $title);
1212

Nigel McNie's avatar
Nigel McNie committed
1213
        // If this block is for just one artefact, we set the title of the
1214
1215
        // block to be a link to view more information about that artefact
        $configdata = $this->get('configdata');
1216
        if (!empty($configdata['artefactid']) && $displayforrole) {
1217
            if (call_static_method($classname, 'has_title_link')) {
1218
                $smarty->assign('viewartefacturl', get_config('wwwroot') . 'artefact/artefact.php?artefact='
1219
                    . $configdata['artefactid'] . '&view=' . $this->get('view') . '&block=' . $this->get('id'));
1220
            }
1221
1222
        }

1223
1224
1225
1226
        if ($displayforrole) {
            if (method_exists($classname, 'feed_url')) {
                $smarty->assign('feedlink', call_static_method($classname, 'feed_url', $this));
            }
1227

1228
1229
            $smarty->assign('link', call_static_method($classname, 'get_link', $this));
        }
1230

1231
        $smarty->assign('content', $content);
1232
        if (isset($configdata['retractable']) && $title && !$exporting) {
1233
1234
1235
1236
1237
            $smarty->assign('retractable', $configdata['retractable']);
            if (isset($configdata['retractedonload'])) {
                $smarty->assign('retractedonload', $configdata['retractedonload']);
            }
        }
1238
        $smarty->assign('versioning', $versioning);
1239
1240
1241
        return $smarty->fetch('view/blocktypecontainerviewing.tpl');
    }

1242
1243
1244
    /**
     * Builds the configuration pieform for this blockinstance
     *