lib.php 21.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
/**
 * This program is part of Mahara
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 * @package    mahara
 * @subpackage blocktype
 * @author     Penny Leach <penny@catalyst.net.nz>
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
 * @copyright  (C) 2006,2007 Catalyst IT Ltd http://catalyst.net.nz
 *
 */

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


/**
 * Base blocktype plugin class
 * @abstract
 */
abstract class PluginBlocktype extends Plugin {

    public static function extra_xmldb_substitution($xml) {
        return str_replace(
        '<!-- PLUGINTYPE_INSTALLED_EXTRAFIELDS -->', 
39
        ' <FIELD NAME="artefactplugin" TYPE="char" LENGTH="255" NOTNULL="false" />',
40
41
42
43
44
45
46
47
48
49
50
51
52
53
        str_replace(
            '<!-- PLUGINTYPE_INSTALLED_EXTRAKEYS -->', 
            '<KEY NAME="artefactpluginfk" TYPE="foreign" FIELDS="artefactplugin" REFTABLE="artefact_installed" REFFIELDS="name" />',
            $xml
            )
        );
    }

    public static abstract function get_title();

    public static abstract function get_description();

    public static abstract function get_categories();

Penny Leach's avatar
Penny Leach committed
54
55
    public static abstract function render_instance(BlockInstance $instance);

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
    /**
     * 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 abstract function artefactchooser_element($default=null);

74
75
76
77
78
79
80
81
    /**
    * subclasses can override this if they need to do something a bit special
    * eg more than just what the BlockInstance->delete function does.
    * 
    * @param BlockInstance $instance
    */
    public static function delete_instance(BlockInstance $instance) { }

82
83
84
    /**
    * This function must be implemented in the subclass if it has config
    */
85
86
    public static function instance_config_form(BlockInstance $instance) {
        throw new SystemException(get_string('blocktypemissingconfigform', 'error', $instance->get('blocktype')));
87
88
    }

89
90
91
92
93
    /**
     * Blocktype plugins can implement this to perform custom pieform 
     * validation, should they need it
     */
    public static function instance_config_validate(Pieform $form, $values) { }
94

95
96
97
98
99
100
101
102
    /**
    * most Blocktype plugins will attach to artefacts.
    * They should implement this function to keep a list of which ones
    * note that it should just handle top level artefacts.
    * the cache rebuilder will figure out the children.
    *
    * @return array ids of artefacts in this block instance
    */
103
104
105
106
107
108
109
110
111
112
    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;
    }
113

114
115
116
117
118
119
    /** 
    * 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
    */
    public static function has_instance_config() {
120
121
        return false;
    }
122

123
    public static function category_title_from_name($name) {
124
        $title = get_string('blocktypecategory.'. $name, 'view');
125
126
127
128
129
130
        if (strpos($title, '[[') !== 0) {
            return $title;
        }
        // else we're an artefact
        return get_string('pluginname', 'artefact.' . $name);
    }
131
132

    public static function get_blocktypes_for_category($category) {
133
        $sql = 'SELECT bti.name, bti.artefactplugin
134
135
            FROM {blocktype_installed} bti 
            JOIN {blocktype_installed_category} btic ON btic.blocktype = bti.name
136
137
            WHERE btic.category = ?
            ORDER BY bti.name';
138
139
140
141
142
143
144
145
146
147
148
149
150
151
        if (!$bts = get_records_sql_array($sql, array($category))) {
            return false;
        }

        $blocktypes = array();

        foreach ($bts as $bt) {
            $namespaced = blocktype_single_to_namespaced($bt->name, $bt->artefactplugin);
            safe_require('blocktype', $namespaced); 
            $temp = array(
                'name'           => $bt->name,
                'title'          => call_static_method(generate_class_name('blocktype', $namespaced), 'get_title'),
                'description'    => call_static_method(generate_class_name('blocktype', $namespaced), 'get_description'),
                'artefactplugin' => $bt->artefactplugin,
152
                'thumbnail_path' => get_config('wwwroot') . 'thumb.php?type=blocktype&bt=' . $bt->name . ((!empty($bt->artefactplugin)) ? '&ap=' . $bt->artefactplugin : ''),
153
154
155
156
157
            );
            $blocktypes[] = $temp;
        }
        return $blocktypes;
    }
158
159
}

160
161
162
163
164
165
abstract class SystemBlockType extends PluginBlockType {

    public final static function get_artefacts(BlockInstance $instance) {
        return array();
    }

166
167
168
    public final static function artefactchooser_element($default=null) {
    }

169
170
171
}


172
173
174
175
176
177
178
class BlockInstance {

    private $id;
    private $blocktype;
    private $title;
    private $configdata;
    private $dirty;
Penny Leach's avatar
Penny Leach committed
179
180
181
182
183
184
185
186
187
188
189
190
191
192
    private $view;
    private $view_obj;
    private $column;
    private $order; 
    private $canmoveleft;
    private $canmoveright;
    private $canmoveup;
    private $canmovedown;
    private $maxorderincolumn; 

    public function __construct($id=0, $data=null) {
         if (!empty($id)) {
            if (empty($data)) {
                if (!$data = get_record('block_instance','id',$id)) {
193
194
195
196
                    // 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 
                    // from lang/*/view.php where there is already one for it
Penny Leach's avatar
Penny Leach committed
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
                    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;
            }
        }
    }
214
215
216
217
218

    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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
        if ($field == 'configdata') {
            // make sure we unserialise it
            if (!is_array($this->configdata)) {
                $this->configdata = unserialize($this->configdata);
            }
        }
        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(
                    'block_instance', 
                    'max("order")', 
                    'view', $this->view, 'column', $this->column);
            }
        }
237
238
239
240
241
        return $this->{$field};
    }

    public function set($field, $value) {
        if (property_exists($this, $field)) {
Penny Leach's avatar
Penny Leach committed
242
            if ($field == 'configdata') {
243
                $value = serialize($value);
Penny Leach's avatar
Penny Leach committed
244
            }
245
            if ($this->{$field} !== $value) {
246
247
                // only set it to dirty if it's changed
                $this->dirty = true;
248
                $this->{$field} = $value;
249
250
251
            }
            return true;
        }
252
        throw new ParamOutOfRangeException("Field $field wasn't found in class " . get_class($this));
Penny Leach's avatar
Penny Leach committed
253
254
    }

Nigel McNie's avatar
Nigel McNie committed
255
    public function instance_config_store(Pieform $form, $values) {
256
257
        global $SESSION;

Nigel McNie's avatar
Nigel McNie committed
258
259
260
261
262
263
264
265
266
        // Destroy form values we don't care about
        unset($values['sesskey']);
        unset($values['blockinstance']);
        unset($values['action_configureblockinstance_id_' . $this->get('id')]);

        if (is_callable(array(generate_class_name('blocktype', $this->get('blocktype')), 'instance_config_save'))) {
            $values = call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'instance_config_save', $values);
        }

267
268
269
270
271
272
273
274
275
276
        $title = (isset($values['title'])) ? $values['title'] : '';
        unset($values['title']);
        $this->set('configdata', $values);

        $blocktypeclass = generate_class_name('blocktype', $this->get('blocktype'));
        if (method_exists($blocktypeclass, 'get_instance_title')) {
            // Get the explicitly set instance title
            $title = call_static_method($blocktypeclass, 'get_instance_title', $this);
        }

277
        $this->set('title', $title);
278
279
        $this->commit();

280
        $SESSION->add_ok_msg(get_string('blockinstanceconfiguredsuccessfully', 'view'));
281
282
283
        $new = param_boolean('new');
        $category = param_alpha('c', '');
        redirect('/view/blocks.php?id=' . $this->get('view') . '&c=' . $category . '&new=' . $new);
Nigel McNie's avatar
Nigel McNie committed
284
285
    }

Penny Leach's avatar
Penny Leach committed
286
    /**
287
288
     * Builds the HTML for the block, inserting the blocktype content at the 
     * appropriate place
289
290
291
     *
     * @param bool $configure Whether to render the block instance in configure 
     *                        mode
292
     * @return array Array with two keys: 'html' for raw html, 'javascript' for
293
     *               javascript to run
Penny Leach's avatar
Penny Leach committed
294
     */
295
    public function render_editing($configure=false) {
Penny Leach's avatar
Penny Leach committed
296
        safe_require('blocktype', $this->get('blocktype'));
297
        $js = '';
298
        if ($configure) {
299
            list($content, $js) = array_values($this->build_configure_form());
300
301
302
303
        }
        else {
            $content = call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'render_instance', $this);
        }
304

305
        $movecontrols = array();
306
        if (!defined('JSON')) {
307
            if ($this->get('canmoveleft')) {
308
309
310
                $movecontrols[] = array(
                    'column' => $this->get('column') - 1,
                    'order'  => $this->get('order'),
311
                    'title'  => get_string('moveblockleft', 'view'),
312
                    'arrow'  => '&larr;',
313
                    'dir'    => 'left',
314
                );
315
316
            }
            if ($this->get('canmovedown')) {
317
318
                $movecontrols[] = array(
                    'column' => $this->get('column'),
319
320
                    'order'  => $this->get('order') + 1,
                    'title'  => get_string('moveblockdown', 'view'),
321
322
                    'arrow'  => '&darr;',
                    'dir'    => 'down',
323
                );
324
325
            }
            if ($this->get('canmoveup')) {
326
327
                $movecontrols[] = array(
                    'column' => $this->get('column'),
328
329
                    'order'  => $this->get('order') - 1,
                    'title'  => get_string('moveblockup', 'view'),
330
                    'arrow'  => '&uarr;',
331
                    'dir'    => 'up',
332
                );
333
334
            }
            if ($this->get('canmoveright')) {
335
336
337
                $movecontrols[] = array(
                    'column' => $this->get('column') + 1,
                    'order'  => $this->get('order'),
338
                    'title'  => get_string('moveblockright', 'view'),
339
                    'arrow'  => '&rarr;',
340
                    'dir'    => 'right',
341
                );
342
343
            }
        }
344
345
346
347
348
349
350
351
        $smarty = smarty_core();

        $smarty->assign('id',     $this->get('id'));
        $smarty->assign('title',  $this->get('title'));
        $smarty->assign('column', $this->get('column'));
        $smarty->assign('order',  $this->get('order'));

        $smarty->assign('movecontrols', $movecontrols);
352
        $smarty->assign('configurable', call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'has_instance_config'));
353
        $smarty->assign('content', $content);
354
        $smarty->assign('javascript', defined('JSON'));
355
        $smarty->assign('strnotitle', get_string('notitle', 'view'));
356

357
        return array('html' => $smarty->fetch('view/blocktypecontainerediting.tpl'), 'javascript' => $js);
Penny Leach's avatar
Penny Leach committed
358
359
    }

360
361
362
363
364
365
366
367
368
    public function render_viewing() {

        safe_require('blocktype', $this->get('blocktype'));
        $content = call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'render_instance', $this);

        $smarty = smarty_core();
        $smarty->assign('id',     $this->get('id'));
        $smarty->assign('title',  $this->get('title'));

369
370
371
372
        // If this block is for just one artefact, we set the title of the 
        // block to be a link to view more information about that artefact
        $configdata = $this->get('configdata');
        if (!empty($configdata['artefactid'])) {
373
            $smarty->assign('viewartefacturl', get_config('wwwroot') . 'view/view.php?id='
374
375
376
                . $this->get('view') . '&artefact=' . $configdata['artefactid']);
        }

377
378
379
380
381
        $smarty->assign('content', $content);
        
        return $smarty->fetch('view/blocktypecontainerviewing.tpl');
    }

382
383
384
    /**
     * Builds the configuration pieform for this blockinstance
     *
385
     * @return array Array with two keys: 'html' for raw html, 'javascript' for
386
387
388
389
     *               javascript to run
     */
    public function build_configure_form() {
        safe_require('blocktype', $this->get('blocktype'));
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
        $elements = call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'instance_config_form', $this);

        $blocktypeclass = generate_class_name('blocktype', $this->get('blocktype'));
        if (method_exists($blocktypeclass, 'get_instance_title')) {
            // Get the explicitly set instance title
            $title = call_static_method($blocktypeclass, 'get_instance_title', $this);
        }
        else {
            // Use the blocktype title as a default
            $elements = array_merge(
                array(
                    'title' => array(
                        'type' => 'text',
                        'title' => 'Block Title',
                        'defaultvalue' => $this->get('title')
                    ),
406
                ),
407
408
409
                $elements
            );
        }
410
411
412
413
414

        // Add submit/cancel buttons
        $elements['action_configureblockinstance_id_' . $this->get('id')] = array(
            'type' => 'submitcancel',
            'value' => array(get_string('save'), get_string('cancel')),
415
            'goto' => View::make_base_url(),
416
417
418
419
420
421
        );

        $form = array(
            'name' => 'cb_' . $this->get('id'),
            'validatecallback' => array(generate_class_name('blocktype', $this->get('blocktype')), 'instance_config_validate'),
            'successcallback'  => array($this, 'instance_config_store'),
422
            'elements' => $elements,
423
424
        );

425
426
427
428
        if (param_variable('action_acsearch_id_' . $this->get('id'), false)) {
            $form['validate'] = false;
        }

429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
        require_once('pieforms/pieform.php');
        $pieform = new Pieform($form);

        if ($pieform->is_submitted()) {
            global $SESSION;
            $SESSION->add_error_msg(get_string('errorprocessingform'));
        }
        else {
            // This is a bit hacky. Because pieforms will take values from
            // $_POST before 'defaultvalue's of form elements, we need to nuke
            // all of the post values for the form. The situation where this
            // becomes relevant is when someone clicks the configure button for
            // one block, then immediately configures another block
            foreach (array_keys($elements) as $name) {
                unset($_POST[$name]);
            }
        }

447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
        $html = $pieform->build(false);

        // We need to load any javascript required for the pieform. We do this
        // by checking for an api function that has been added especially for 
        // the purpose, but that is not part of Pieforms. Maybe one day later 
        // it will be though
        $js = '';
        foreach ($elements as $key => $element) {
            $element['name'] = $key;
            $function = 'pieform_element_' . $element['type'] . '_views_js';
            if (is_callable($function)) {
                $js .= call_user_func_array($function, array($pieform, $element));
            }
        }

462
        return array('html' => $html, 'javascript' => $js);
463
464
    }

Penny Leach's avatar
Penny Leach committed
465
466
467
    public function commit() {
        if (empty($this->dirty)) {
            return;
468
        }
Penny Leach's avatar
Penny Leach committed
469
470
        $fordb = new StdClass;
        foreach (get_object_vars($this) as $k => $v) {
471
472
473
474
475
476
477
478
479
            // The configdata is initially fetched from the database in string 
            // form. Calls to get() will convert it to an array on the fly. We 
            // ensure that it is a string again here
            if ($k == 'configdata' && is_array($v)) {
                $fordb->{$k} = serialize($v);
            }
            else {
                $fordb->{$k} = $v;
            }
480
        }
Penny Leach's avatar
Penny Leach committed
481
482
483
484
485
        if (empty($this->id)) {
            $this->id = insert_record('block_instance', $fordb, 'id', true);
        }
        else {
            update_record('block_instance', $fordb, 'id');
486
        }
Penny Leach's avatar
Penny Leach committed
487

488
        $this->rebuild_artefact_list();
489
490
491

        // Tell stuff about this
        handle_event('blockinstancecommit', $this);
Penny Leach's avatar
Penny Leach committed
492
493

        $this->dirty = false;
494
495
    }

496
497
498
    public function rebuild_artefact_list() {
        db_begin();
        delete_records('view_artefact', 'block', $this->id);
499
        safe_require('blocktype', $this->get('blocktype'));
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
        if (!$artefacts = call_static_method(
            generate_class_name('blocktype', $this->get('blocktype')),
            'get_artefacts', $this)) {
            db_commit();
            return true;
        }
            
        $va = new StdClass;
        $va->view = $this->get('view');
        $va->block = $this->id;

        foreach ($artefacts as $id) {
            $va->artefact = $id;
            insert_record('view_artefact', $va);
        }

        db_commit();
    }

Penny Leach's avatar
Penny Leach committed
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
    /**
     * @return View the view object this block instance is in
     */
    public function get_view() {
        if (empty($this->view_obj)) {
            $this->view_obj = new View($this->get('view'));
        }
        return $this->view_obj;
    }

    public function can_move($direction) {
        switch ($direction) {
            case 'left':
                return ($this->column > 1);
            case 'right':
                return ($this->column < $this->get_view()->get('numcolumns'));
            case 'up':
                return ($this->order > 1);
                break;
            case 'down':
                return ($this->order < $this->get('maxorderincolumn'));
            default:
                throw new InvalidArgumentException(get_string('invaliddirection', 'error', $direction));
        }
543
544
    }

545
546
547
548
549
550
551
552
553
554
    public function delete() {
        if (empty($this->id)) {
            $this->dirty = false;
            return;
        }
        
        delete_records('view_artefact', 'block', $this->id);
        delete_records('block_instance', 'id', $this->id);

        $this->dirty = false;
555
        safe_require('blocktype', $this->get('blocktype'));
556
        call_static_method(generate_class_name('blocktype', $this->get('blocktype')), 'delete_instance', $this);
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
589
590
591
592
593
594
595
    /**
     * Deletes an artefact from the blockinstance.
     *
     * This is implemented in the baseclass by looking for arrays in the block 
     * instance configuration called 'artefactid' or 'artefactids', and 
     * removing the one we were looking to delete. This means two things:
     *
     * 1) In order to not have to re-implement this method for new blocktypes, 
     *    your blocktype should ALWAYS store its artefact IDs in the config data 
     *    value 'artefactid' or in the array 'artefactids'
     * 2) The block must ALWAYS continue to work even when artefacts are 
     *    removed from it
     */
    public function delete_artefact($artefact) {
        $configdata = $this->get('configdata');
        $changed = false;

        if (isset($configdata['artefactid'])) {
            if ($configdata['artefactid'] == $artefact) {
                $configdata['artefactid'] = null;
            }
            $changed = true;
        }

        if (isset($configdata['artefactids']) && is_array($configdata['artefactids'])) {
            $configdata['artefactids'] = array_diff($configdata['artefactids'], array($artefact));
            $changed = true;
        }

        if ($changed) {
            $this->set('configdata', $configdata);

            // We would commit here but we don't want to rebuild the artefact list
            set_field('block_instance', 'configdata', serialize($configdata), 'id', $this->get('id'));
        }
    }

596
597
598
599
}


?>