lib.php 83.9 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 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
    /**
     * To define a pluginwide configuration
     */
    public static function has_base_config() {
        return true;
    }

    /**
     * To define a pluginwide configuration
     */
    public static function get_base_config_options() {
        $type = array();
        $blocks = get_records_sql_array("SELECT b.name, b.artefactplugin, bc.sortorder,
                                   (SELECT COUNT(bi.*) FROM {block_instance} bi WHERE bi.blocktype = b.name) AS blockcount
                                  FROM {blocktype_installed} b
                                  JOIN {blocktype_installed_category} bc ON bc.blocktype = b.name
                                  WHERE b.active = 1
                                  AND b.name != ?
                                  ORDER BY bc.sortorder", array('placeholder'));
        foreach ($blocks as $block) {
            $namespaced = blocktype_single_to_namespaced($block->name, $block->artefactplugin);
            safe_require('blocktype', $namespaced);
            $classname = generate_class_name('blocktype', $namespaced);
            $types[] = array('name' => $block->name,
                             'title' => call_static_method($classname, 'get_title'),
                             'cssicon' => call_static_method($classname, 'get_css_icon', $block->name),
                             'cssicontype' => call_static_method($classname, 'get_css_icon_type', $block->name),
                             'count' => $block->blockcount,
                             );
        }
        $form = array(
            'elements' => array(
                'types' => array(
                    'type' => 'fieldset',
                    'legend' => get_string('contenttypes', 'blocktype.placeholder'),
                    'elements' => array(
                        'contenttypes' => array(
                            'type' => 'html',
                            'value' => '',
                        ),
                    ),
                ),
            )
        );
        $smarty = smarty_core();
        $smarty->assign('types', $types);
        $typeslist = $smarty->fetch('blocktype:placeholder:contenttypeslist.tpl');
        $smarty->assign('typeslist', $typeslist);
        $typeshtml = $smarty->fetch('blocktype:placeholder:contenttypes.tpl');
        $form['elements']['types']['elements']['contenttypes']['value'] = $typeshtml;
        return $form;
    }

    /**
     * To define a pluginwide configuration
     */
    public static function get_base_config_options_js() {
        global $USER;

        $sesskey = $USER->get('sesskey');
        $js = <<<EOF
$(function() {
    $('#placeholderlist button').each(function() {
        $(this).off('click');
        $(this).on('click', function(ev) {
            ev.stopPropagation();
            ev.preventDefault();
        });
    });

    var updaterows = function(option) {
        var sortorder = $('#placeholderlist').sortable('serialize');
        $.post(config['wwwroot'] + "blocktype/config.json.php", { sesskey: '$sesskey', id: option, direction: sortorder })
        .done(function(data) {
            // update the page with the new list
            if (data.returnCode == '0') {
                $('#placeholderlist').replaceWith(data.message.html);
                if (data.message.message) {
                    var okmessage = $('<div id="changestatusline" class="alert alert-dismissible alert-success" role="alert"><button type="button" class="close" data-dismiss="alert" aria-label="' + get_string('Close') + '"><span aria-hidden="true">&times;</span></button><p>' + data.message.message + '</p></div>');
                    $('#messages').empty().append(okmessage);
                }
                wiresortables();
            }
        });
    };

    var wiresortables = function() {
        $('#placeholderlist > div').each(function() {
            $(this).prepend('<div class="handle">&nbsp;</div>');
            $('.handle').css('position', 'absolute');
            $('.handle').css('z-index', '3');
            $('.handle').css('width', $(this).find('button').css('width'));
            $('.handle').css('height', $(this).find('button').css('height'));
        });
        $('#placeholderlist').sortable({
            items: '> div',
            appendTo: '#placeholderlist',
            cursor: 'move',
            opacity: 0.8,
            helper: 'clone',
            handle: '.handle',
            stop: function(e, ui) {
                var id = $(ui.item).find('button').data('option');
                updaterows(id);
            },
        })
        .disableSelection()
        .on("mouseenter mouseleave", function() {
            $(this).css('cursor', 'move');
        });
        // hide the 'save' button as this form works with drag / drop saving
        $('#pluginconfig_save_container').hide();
    };

    // init
    wiresortables();
});
EOF;
        return $js;
    }

210 211 212 213 214 215 216 217 218 219
    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);
        }
    }

220 221 222 223 224 225 226 227 228 229 230 231 232 233

    /**
     * This function returns an array of menu items
     * to be displayed in the top right navigation menu
     *
     * See the function find_menu_children() in lib/web.php
     * for a description of the expected array structure.
     *
     * @return array
     */
    public static function right_nav_menu_items() {
        return array();
    }

234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
    /**
     * 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;
    }

258 259 260 261
    public static function get_css_icon_type($blocktypename) {
        return '';
    }

262 263
    public static function extra_xmldb_substitution($xml) {
        return str_replace(
Aaron Wells's avatar
Aaron Wells committed
264
        '<!-- PLUGINTYPE_INSTALLED_EXTRAFIELDS -->',
265
        ' <FIELD NAME="artefactplugin" TYPE="char" LENGTH="255" NOTNULL="false" />',
266
        str_replace(
Aaron Wells's avatar
Aaron Wells committed
267
            '<!-- PLUGINTYPE_INSTALLED_EXTRAKEYS -->',
268 269 270 271 272 273
            '<KEY NAME="artefactpluginfk" TYPE="foreign" FIELDS="artefactplugin" REFTABLE="artefact_installed" REFFIELDS="name" />',
            $xml
            )
        );
    }

Aaron Wells's avatar
Aaron Wells committed
274 275
    /**
     * override this to return true if the blocktype
276 277 278 279 280 281
     * can only reasonably be placed once in a view
    */
    public static function single_only() {
        return false;
    }

Aaron Wells's avatar
Aaron Wells committed
282 283 284 285 286
    /**
     * 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.
     *
287
     * You should avoid enabling this for:
Aaron Wells's avatar
Aaron Wells committed
288 289 290
     * - 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)
291 292
     * - 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
293 294 295 296
     *
     * @return boolean
     */
    public static function should_ajaxify() {
297
        return false;
Aaron Wells's avatar
Aaron Wells committed
298 299
    }

300 301 302 303 304 305 306 307
    /**
     * 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) {
    }

308 309 310 311 312 313 314 315 316 317 318 319
    public static function get_viewtypes() {
        static $viewtypes = null;

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

        return $viewtypes;
    }
320

321 322 323 324 325 326 327 328 329
    /**
    * 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();
    }

330 331 332 333 334 335 336 337 338
   /**
    * 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();
    }

339 340 341 342 343 344 345 346 347
    /**
    * 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();
    }

348 349 350 351 352 353
    /**
     * Inline js to be executed when a block is rendered.
     */
    public static function get_instance_inline_javascript(BlockInstance $instance) {
    }

354 355 356
    /**
    * 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
357
    *
358 359 360 361
    * @param BlockInstance $instance
    */
    public static function delete_instance(BlockInstance $instance) { }

362 363 364
    /**
    * This function must be implemented in the subclass if it has config
    */
365 366
    public static function instance_config_form(BlockInstance $instance) {
        throw new SystemException(get_string('blocktypemissingconfigform', 'error', $instance->get('blocktype')));
367 368
    }

369 370 371 372 373 374 375 376 377
    /**
    * 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();
    }

378
    /**
Aaron Wells's avatar
Aaron Wells committed
379
     * Blocktype plugins can implement this to perform custom pieform
380 381 382
     * validation, should they need it
     */
    public static function instance_config_validate(Pieform $form, $values) { }
383

384
    /**
385
    * Most blocktype plugins will attach to artefacts.
Aaron Wells's avatar
Aaron Wells committed
386 387 388
    * 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.
389 390 391 392
    * 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.
393 394 395
    *
    * @return array ids of artefacts in this block instance
    */
396 397 398 399 400 401 402 403 404 405
    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;
    }
406 407 408 409 410 411 412 413 414 415 416
    /**
    * 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();
    }
417

Aaron Wells's avatar
Aaron Wells committed
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
     * 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
     */
443
    public static function has_instance_config() {
444 445
        return false;
    }
446

447
    public static function category_title_from_name($name) {
448
        $title = get_string('blocktypecategory.'. $name, 'view');
449 450 451 452 453 454
        if (strpos($title, '[[') !== 0) {
            return $title;
        }
        // else we're an artefact
        return get_string('pluginname', 'artefact.' . $name);
    }
455

456 457 458 459 460
    public static function category_description_from_name($name) {
        $description = get_string('blocktypecategorydesc.'. $name, 'view');
        return $description;
    }

461
    public static function get_blocktypes_for_category($category, View $view, $blocktype = null) {
462
        $sql = 'SELECT bti.name, bti.artefactplugin, btic.sortorder
Aaron Wells's avatar
Aaron Wells committed
463
            FROM {blocktype_installed} bti
464
            JOIN {blocktype_installed_category} btic ON btic.blocktype = bti.name
465
            JOIN {blocktype_installed_viewtype} btiv ON btiv.blocktype = bti.name
466 467 468 469 470 471 472 473
            WHERE btic.category = ? AND bti.active = 1 AND btiv.viewtype = ?';
        $where = array($category, $view->get('type'));
        if ($blocktype) {
            $sql .= ' AND bti.name = ?';
            $where[] = $blocktype;
        }
        $sql .= ' ORDER BY btic.sortorder, bti.name';
        if (!$bts = get_records_sql_array($sql, $where)) {
474 475 476 477 478
            return false;
        }

        $blocktypes = array();

479 480 481 482
        if (function_exists('local_get_allowed_blocktypes')) {
            $localallowed = local_get_allowed_blocktypes($category, $view);
        }

483 484
        foreach ($bts as $bt) {
            $namespaced = blocktype_single_to_namespaced($bt->name, $bt->artefactplugin);
485
            if (isset($localallowed) && is_array($localallowed) && !in_array($namespaced, $localallowed)) {
486 487
                continue;
            }
Aaron Wells's avatar
Aaron Wells committed
488 489 490
            safe_require('blocktype', $namespaced);
            // Note for later: this is Blocktype::allowed_in_view, which
            // returns true if the blocktype should be insertable into the
491
            // given view.
Aaron Wells's avatar
Aaron Wells committed
492
            // e.g. for blogs it returns false when view owner is not set,
493
            // because blogs can't be inserted into group views.
Aaron Wells's avatar
Aaron Wells committed
494 495
            // This could be different from whether a blockinstance is allowed
            // to be copied into a View (see the other place in this file where
496 497
            // allowed_in_view is called)
            //
Aaron Wells's avatar
Aaron Wells committed
498 499 500
            // 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
501
            // View::get_category_data
502 503
            $classname = generate_class_name('blocktype', $namespaced);
            if (call_static_method($classname, 'allowed_in_view', $view)) {
504 505
                $blocktypes[] = array(
                    'name'           => $bt->name,
506 507 508
                    'title'          => call_static_method($classname, 'get_title'),
                    'description'    => call_static_method($classname, 'get_description'),
                    'singleonly'     => call_static_method($classname, 'single_only'),
509 510
                    'artefactplugin' => $bt->artefactplugin,
                    'thumbnail_path' => get_config('wwwroot') . 'thumb.php?type=blocktype&bt=' . $bt->name . ((!empty($bt->artefactplugin)) ? '&ap=' . $bt->artefactplugin : ''),
511
                    'cssicon'        => call_static_method($classname, 'get_css_icon', $bt->name),
512
                    'cssicontype'    => call_static_method($classname, 'get_css_icon_type', $bt->name),
513
                    'sortorder'      => $bt->sortorder,
514 515
                );
            }
516 517 518
        }
        return $blocktypes;
    }
Richard Mansfield's avatar
Richard Mansfield committed
519

520
    /**
Aaron Wells's avatar
Aaron Wells committed
521
     * Takes config data for an existing blockinstance of this class and rewrites it so
522 523
     * it can be used to configure a block instance being put in a new view
     *
Aaron Wells's avatar
Aaron Wells committed
524 525
     * 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
526 527
     * example, who will own it.
     *
Aaron Wells's avatar
Aaron Wells committed
528 529
     * As an example - when the profile information blocktype is copied, we
     * want it so that all the fields that were configured previously are
530 531 532 533
     * 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
534
     * @param View $view The view that the blocktype will be placed into (e.g.
535 536 537 538 539
     *                   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) {
540 541 542 543 544 545 546 547 548
        if (isset($configdata['artefactid'])) {
            $configdata['artefactid'] = null;
        }
        if (isset($configdata['artefactids'])) {
            $configdata['artefactids'] = array();
        }
        return $configdata;
    }

549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588
    /**
     * 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;
    }

589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605

    /**
     * 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
    }

606 607
    /*
     * The copy_type of a block affects how it should be copied when its view gets copied.
608 609 610 611 612 613 614 615 616
     * 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.
617
     *
618
     * If the old owner and the new owner are the same, reference is used unless 'fullinclself' is specified.
619 620 621 622 623 624
     * If a block contains no artefacts, reference and full are equivalent.
     */
    public static function default_copy_type() {
        return 'shallow';
    }

625 626 627 628 629 630 631 632
    /*
     * 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();
    }

633 634 635
    /**
     * Whether this blocktype is allowed in the given View.
     *
Aaron Wells's avatar
Aaron Wells committed
636 637
     * 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
638 639
     * of owner.
     *
Aaron Wells's avatar
Aaron Wells committed
640
     * For example, the 'profile information' blocktype makes no sense in a
641 642
     * group View.
     *
Aaron Wells's avatar
Aaron Wells committed
643 644
     * Of course, blocktypes could implement stranger rules - e.g. only allow
     * when the view has 'ponies' in its description (BTW: such blocktypes
645 646 647
     * would be totally awesome).
     *
     * @param View     The View to check
Aaron Wells's avatar
Aaron Wells committed
648
     * @return boolean Whether blocks of this blocktype are allowed in the
649 650 651 652 653 654
     *                 given view.
     */
    public static function allowed_in_view(View $view) {
        return true;
    }

655
    /**
Aaron Wells's avatar
Aaron Wells committed
656
     * Given a block instance, returns a hash with enough information so that
657 658
     * we could reconstruct it if given this information again.
     *
Aaron Wells's avatar
Aaron Wells committed
659 660
     * Import/Export routines can serialise this information appropriately, and
     * unserialise it on the way back in, where it is passed to {@link
661 662 663 664 665 666
     * 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) {
667 668 669
        $configdata = $bi->get('configdata');

        if (is_array($configdata)) {
Aaron Wells's avatar
Aaron Wells committed
670 671
            // Unset a bunch of stuff that we don't want to export. These fields
            // weren't being cleaned up before blockinstances were being saved
672 673 674 675 676 677 678 679 680 681 682
            // 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;
683
    }
684 685

    /**
Francois Marier's avatar
Francois Marier committed
686
     * Exports configuration data the format required for Leap2A export.
687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708
     *
     * 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;
    }
709 710 711 712

    /**
     * Creates a block instance from a given configuration.
     *
713 714
     * The configuration is whatever was generated by {@link
     * export_blockinstance_config()}. This method doesn't have to worry about
715 716
     * setting the block title, or the position in the View.
     *
717 718
     * @param array $biconfig   The config to use to create the blockinstance
     * @param array $viewconfig The configuration for the view being imported
719 720
     * @return BlockInstance The new block instance
     */
721
    public static function import_create_blockinstance(array $biconfig, array $viewconfig) {
722 723
        $bi = new BlockInstance(0,
            array(
724 725
                'blocktype'  => $biconfig['type'],
                'configdata' => $biconfig['config'],
726 727 728 729
            )
        );

        return $bi;
730 731
    }

732 733 734 735 736 737 738 739 740 741 742 743
    /**
     * 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;
    }

744 745 746 747 748 749 750 751 752 753 754 755 756
    /**
     * 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;
    }

757 758 759 760 761 762 763 764 765 766 767 768 769
    /**
     * 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');
    }

770 771
}

772

773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
/**
 * 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 {
805
    public static function get_artefacts(BlockInstance $instance) {
806 807 808
        return array();
    }

809
    public final static function artefactchooser_element($default=null) {
810
    }
811 812 813
}


814 815
class BlockInstance {

816 817 818 819
    const RETRACTABLE_NO = 0;
    const RETRACTABLE_YES = 1;
    const RETRACTABLE_RETRACTED = 2;

820 821
    private $id;
    private $blocktype;
822
    private $artefactplugin;
823
    private $title;
824
    private $configdata = array();
825
    private $dirty;
Penny Leach's avatar
Penny Leach committed
826 827
    private $view;
    private $view_obj;
828
    private $row;
Penny Leach's avatar
Penny Leach committed
829
    private $column;
830
    private $order;
Penny Leach's avatar
Penny Leach committed
831 832 833 834
    private $canmoveleft;
    private $canmoveright;
    private $canmoveup;
    private $canmovedown;
835
    private $maxorderincolumn;
836
    private $artefacts = array();
837
    private $temp = array();
838
    private $tags = array();
839
    private $inedit = false;
Penny Leach's avatar
Penny Leach committed
840 841 842 843 844

    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
845 846 847
                    // 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
848
                    // from lang/*/view.php where there is already one for it
Penny Leach's avatar
Penny Leach committed
849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864
                    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;
            }
        }
865
        $this->artefactplugin = blocktype_artefactplugin($this->blocktype);
Penny Leach's avatar
Penny Leach committed
866
    }
867 868 869 870 871

    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
872 873 874 875 876 877
        if ($field == 'configdata') {
            // make sure we unserialise it
            if (!is_array($this->configdata)) {
                $this->configdata = unserialize($this->configdata);
            }
        }
878 879 880 881 882 883 884 885 886 887 888 889 890 891
        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
892 893 894 895 896 897 898
        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
899 900
                    'block_instance',
                    'max("order")',
Penny Leach's avatar
Penny Leach committed
901 902 903
                    'view', $this->view, 'column', $this->column);
            }
        }
904 905 906 907 908
        return $this->{$field};
    }

    public function set($field, $value) {
        if (property_exists($this, $field)) {
909 910 911
            if ($field == 'tags') {
                $this->set_tags($value);
            }
Penny Leach's avatar
Penny Leach committed
912
            if ($field == 'configdata') {
913
                $value = serialize($value);
Penny Leach's avatar
Penny Leach committed
914
            }
915
            if ($this->{$field} !== $value) {
916 917
                // only set it to dirty if it's changed
                $this->dirty = true;
918
                $this->{$field} = $value;
919 920 921
            }
            return true;
        }
922
        throw new ParamOutOfRangeException("Field $field wasn't found in class " . get_class($this));
Penny Leach's avatar
Penny Leach committed
923 924
    }

925 926 927
    private function set_tags($tags) {
        global $USER;

928 929 930 931
        if (empty($this->view_obj)) {
            $this->get_view();
        }

932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
        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);
949
            $tag = check_if_institution_tag($tag);
950 951 952 953 954 955 956 957 958 959 960 961 962 963
            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'),
                )
            );
        }
    }

964 965 966 967 968 969 970 971
    // 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);
972
                if (!$USER->can_view_artefact($file)) {
973 974 975 976 977 978 979
                    // bail out now as at least one attachment is bad
                    return false;
                }
            }
        }
        else {
            $file = artefact_instance_from_id($id);
980
            if (!$USER->can_view_artefact($file)) {
981 982 983 984 985 986
                return false;
            }
        }
        return true;
    }

Nigel McNie's avatar
Nigel McNie committed
987
    public function instance_config_store(Pieform $form, $values) {
988
        global $SESSION, $USER;
989

Nigel McNie's avatar
Nigel McNie committed
990 991 992 993
        // Destroy form values we don't care about
        unset($values['sesskey']);
        unset($values['blockinstance']);
        unset($values['action_configureblockinstance_id_' . $this->get('id')]);
994 995 996 997
        unset($values['blockconfig']);
        unset($values['id']);
        unset($values['change']);
        unset($values['new']);
998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
        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
1015

1016 1017 1018
        // 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;
1019 1020
        if (isset($values['blocktemplate']) && !empty($values['blocktemplate'])) {
            // Ignore check on artefactids as they are not relating to actual artefacts
1021
        }
1022 1023 1024 1025 1026 1027 1028
        else {
            if (!empty($values['artefactid'])) {
                $badattachment = !$this->verify_attachment_permissions($values['artefactid']);
            }
            if (!empty($values['artefactids'])) {
                $badattachment = !$this->verify_attachment_permissions($values['artefactids']);
            }
1029
        }
1030

1031 1032 1033 1034 1035 1036 1037
        if ($badattachment) {
            $result['message'] = get_string('unrecoverableerror', 'error');
            $form->set_error(null, $result['message']);
            $form->reply(PIEFORM_ERR, $result);
            exit();
        }

1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049
        $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
1050
        if (is_callable(array(generate_class_name('blocktype', $this->get('blocktype')), 'instance_config_save'))) {
1051 1052 1053 1054 1055 1056 1057 1058
            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
1059 1060
        }

1061 1062
        $title = (isset($values['title'])) ? $values['title'] : '';
        unset($values['title']);
1063

1064 1065 1066 1067 1068
        if (isset($values['tags'])) {
            $this->set('tags', $values['tags']);
            unset($values['tags']);
        }

1069 1070 1071 1072 1073
        // 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']);

1074
        $this->set('configdata', $values);
1075
        $this->set('title', $title);
1076

1077 1078
        $this->commit();

1079 1080 1081 1082 1083 1084 1085 1086
        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));
        }

1087 1088 1089
        $result = array(
            'error'   => false,
            'message' => get_string('blockinstanceconfiguredsuccessfully', 'view'),
1090
            'data'    => $rendered,
1091
            'blockid' => $this->get('id'),
1092
            'viewid'  => $this->get('view'),
1093
            'goto'    => $redirect,
1094 1095
        );

1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107
        // 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),
                );
            }
        }

1108
        $form->reply(PIEFORM_OK, $result);
Nigel McNie's avatar
Nigel McNie committed
1109 1110
    }

1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
    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 '';
    }

1125 1126 1127 1128
    public function to_stdclass() {
        return (object)get_object_vars($this);
    }

Penny Leach's avatar
Penny Leach committed
1129
    /**
Aaron Wells's avatar
Aaron Wells committed
1130
     * Builds the HTML for the block, inserting the blocktype content at the
1131
     * appropriate place
1132
     *
Aaron Wells's avatar
Aaron Wells committed
1133
     * @param bool $configure Whether to render the block instance in configure
1134
     *                        mode
1135
     * @return array Array with two keys: 'html' for raw html, 'javascript' for
1136
     *               javascript to run
Penny Leach's avatar
Penny Leach committed
1137
     */
1138
    public function render_editing($configure=false, $new=false, $jsreply=false) {
1139 1140
        global $USER;

Penny Leach's avatar
Penny Leach committed
1141
        safe_require('blocktype', $this->get('blocktype'));
1142
        $movecontrols = array();
1143
        $this->inedit = true;
1144
        $blocktypeclass = generate_class_name('blocktype', $this->get('blocktype'));
1145 1146 1147 1148 1149 1150 1151
        try {
            $title = $this->get_title();
        }
        catch (NotFoundException $e) {
            log_debug('Cannot render block title. Original error follows: ' . $e->getMessage());
            $title = get_string('notitle', 'view');
        }
1152

1153
        if ($configure) {
1154
            list($content, $js, $css) = array_values($this->build_configure_form($new));
1155 1156
        }
        else {
1157
            try {
1158 1159 1160 1161 1162 1163 1164
              $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   {
1165
                $content = call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'render_instance', $this, true);
1166 1167 1168
                $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;
1169
                $css = '';
1170
              }
1171
            }
1172
            catch (NotFoundException $e) {
Aaron Wells's avatar
Aaron Wells committed
1173 1174 1175
                // 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
1176 1177 1178 1179 1180 1181
                // 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 = '';
1182
                $js = '';
1183
                $css = '';
1184
            }
1185

1186 1187 1188 1189 1190
            if (!defined('JSON') && !$jsreply) {
                if ($this->get('canmoveleft')) {
                    $movecontrols[] = array(
                        'column' => $this->get('column') - 1,
                        'order'  => $this->get('order'),
1191
                        'title'  => $title == '' ? get_string('movethisblockleft', 'view') : get_string('moveblockleft', 'view', "'$title'"),
1192
                        'arrow'  => "icon icon-long-arrow-alt-left",
1193 1194 1195 1196 1197 1198 1199
                        'dir'    => 'left',
                    );
                }
                if ($this->get('canmovedown')) {
                    $movecontrols[] = array(
                        'column' => $this->get('column'),
                        'order'  => $this->get('order') + 1,
1200
                        'title'  => $title == '' ? get_string('movethisblockdown', 'view') : get_string('moveblockdown', 'view', "'$title'"),
1201
                        'arrow'  => 'icon icon-long-arrow-alt-down',
1202 1203 1204 1205 1206 1207 1208
                        'dir'    => 'down',
                    );
                }
                if ($this->get('canmoveup')) {
                    $movecontrols[] = array(
                        'column' => $this->get('column'),
                        'order'  => $this->get('order') - 1,
1209
                        'title'  => $title == '' ? get_string('movethisblockup', 'view') : get_string('moveblockup', 'view', "'$title'"),
1210
                        'arrow'  => 'icon icon-long-arrow-alt-up',
1211 1212 1213 1214 1215 1216 1217
                        'dir'    => 'up',
                    );
                }
                if ($this->get('canmoveright')) {
                    $movecontrols[] = array(
                        'column' => $this->get('column') + 1,
                        'order'  => $this->get('order'),
1218
                        'title'  => $title == '' ? get_string('movethisblockright', 'view') : get_string('moveblockright', 'view', "'$title'"),
1219
                        'arrow'  => 'icon icon-long-arrow-alt-right',
1220 1221 1222
                        'dir'    => 'right',
                    );
                }
1223 1224
            }
        }
1225 1226 1227

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

1228
        $smarty = smarty_core();
1229 1230
        $id = $this->get('id');
        $smarty->assign('id',     $id);
1231
        $smarty->assign('viewid', $this->get('view'));
1232
        $smarty->assign('title',  $title);
1233
        $smarty->assign('row',    $this->get('row'));
1234 1235
        $smarty->assign('column', $this->get('column'));
        $smarty->assign('order',  $this->get('order'));
1236
        $smarty->assign('blocktype', $this->get('blocktype'));
1237
        $smarty->assign('movecontrols', $movecontrols);
1238
        $smarty->assign('configurable', call_static_method($blocktypeclass, 'has_instance_config'));
1239
        $smarty->assign('configure', $configure); // Used by the javascript to rewrite the block, wider.
1240
        $smarty->assign('configtitle',  $configtitle);
1241
        $smarty->assign('content', $content);
1242
        $smarty->assign('javascript', defined('JSON'));
1243
        $smarty->assign('strnotitle', get_string('notitle', 'view'));
1244
        $smarty->assign('strmovetitletext', $title == '' ? get_string('movethisblock', 'view') : get_string('moveblock', 'view', "'$title'"));
1245
        $smarty->assign('strmovetitletexttooltip', get_string('moveblock2', 'view'));
1246
        $smarty->assign('strconfigtitletext', $title == '' ? get_string('configurethisblock1', 'view', $id) : get_string('configureblock1', 'view', "'$title'", $id));
1247
        $smarty->assign('strconfigtitletexttooltip', get_string('configureblock2', 'view'));
1248
        $smarty->assign('strremovetitletext', $title == '' ? get_string('removethisblock1', 'view', $id) : get_string('removeblock1', 'view', "'$title'", $id));
1249
        $smarty->assign('strremovetitletexttooltip', get_string('removeblock2', 'view'));
1250
        $smarty->assign('lockblocks', ($this->get_view()->get('lockblocks') && $this->get_view()->get('owner'))); // Only lock blocks for user's portfolio pages
1251

1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262
        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');
                }
            }
        }
1263 1264 1265 1266
        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
1267 1268
    }

1269 1270 1271 1272 1273 1274 1275 1276


    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
          );
1277 1278 1279 1280 1281
          if ($artefacts) {
              uasort($artefacts, array("BlockInstance", "my_files_cmp"));
              foreach ($artefacts as $artefact) {
                  $result[] = $artefact->id;
              }
1282 1283 1284 1285 1286 1287 1288 1289 1290
          }
      }
      return $result;
    }

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

1291 1292 1293 1294 1295
    /**
     * 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
1296 1297
     * @param boolean $versioning Indicate the rendering is for an older view version
     *
1298 1299
     * @return the rendered block
     */
1300
    public function render_viewing($exporting=false, $versioning=false) {
1301
        global $USER;
1302

1303 1304 1305
        if (!safe_require_plugin('blocktype', $this->get('blocktype'))) {
            return;
        }
1306 1307 1308

        $smarty = smarty_core();

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

1311
        $classname = generate_class_name('blocktype', $this->get('blocktype'));
1312
        $displayforrole = call_static_method($classname, 'display_for_roles', $user_roles);
1313
        $checkview = $this->get_view();
1314 1315
        if ($checkview->get('owner') == NULL ||
            ($USER->is_admin_for_user($checkview->get('owner')) && $checkview->is_objectionable())) {
1316 1317
            $displayforrole = true;
        }
1318 1319 1320 1321
        if (!$displayforrole) {
            $content = '';
            $smarty->assign('loadbyajax', false);
        }
1322
        else if (get_config('ajaxifyblocks') && call_static_method($classname, 'should_ajaxify') && $exporting === false && $versioning === false) {
1323
            $content = '';
1324
            $smarty->assign('loadbyajax', true);
1325
        }
Aaron Wells's avatar
Aaron Wells committed
1326
        else {
1327
            $smarty->assign('loadbyajax', false);
Aaron Wells's avatar
Aaron Wells committed
1328
            try {
1329
                $content = call_static_method($classname, 'render_instance', $this, false, $versioning);
Aaron Wells's avatar
Aaron Wells committed
1330 1331
            }
            catch (NotFoundException $e) {
1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342
                //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
1343 1344 1345
                $content = '';
            }
        }
1346 1347

        $smarty->assign('id',     $this->get('id'));
1348
        $smarty->assign('blocktype', $this->get('blocktype'));
1349
        // hide the title if required and no content is present
1350
        if (call_static_method($classname, 'hide_title_on_empty_content')
1351 1352 1353
            && !trim($content)) {
            return;
        }
1354 1355 1356 1357 1358 1359 1360 1361
        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);
1362

Nigel McNie's avatar
Nigel McNie committed
1363
        // If this block is for just one artefact, we set the title of the
1364 1365
        // block to be a link to view more information about that artefact
        $configdata = $this->get('configdata');
1366
        if (!empty($configdata['artefactid']) && $displayforrole) {
1367
            if (call_static_method($classname, 'has_title_link')) {
1368
                $smarty->assign('viewartefacturl', get_config('wwwroot') . 'artefact/artefact.php?artefact='
1369
                    . $configdata['artefactid'] . '&view=' . $this->get('view') . '&block=' . $this->get('id'));
1370
            }
1371 1372
        }

1373 1374 1375 1376
        if ($displayforrole) {
            if (method_exists($classname, 'feed_url')) {
                $smarty->assign('feedlink', call_static_method($classname, 'feed_url', $this));
            }
1377

1378 1379
            $smarty->assign('link', call_static_method($classname, 'get_link', $this));
        }
1380

1381
        $smarty->assign('content', $content);
1382
        if (isset($configdata['retractable']) && $title && !$exporting) {
1383 1384 1385 1386 1387
            $smarty->assign('retractable', $configdata['retractable']);
            if (isset($configdata['retractedonload'])) {
                $smarty->assign('retractedonload', $configdata['retractedonload']);
            }
        }
1388 1389 1390
        $cssicontype = call_static_method($classname, 'get_css_icon_type', $this->blocktype);
        $cardicontype = !empty($cssicontype) ? preg_replace('/^icon-/', 'card-', $cssicontype) : '';
        $smarty->assign('cardicontype', $cardicontype);
1391
        $smarty->assign('versioning', $versioning);
1392 1393 1394
        return $smarty->fetch('view/blocktypecontainerviewing.tpl');
    }

1395 1396 1397
    /**
     * Builds the configuration pieform for this blockinstance
     *
1398
     * @return array Array with two keys: 'html' for raw html, 'javascript' for
1399
     *               javascript to run, 'css' for dynamic css to add to header
Nigel McNie's avatar