template.php 20.3 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
<?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 core
 * @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();
Penny Leach's avatar
Penny Leach committed
28
29
30
31

/**
 * render templates in readonly mode (used for viewing templates)
 */
32
define('TEMPLATE_RENDER_READONLY', 1);
Penny Leach's avatar
Penny Leach committed
33
34
35
36
 
/**
 * render templates in editmode mode (for view wizard)
 */
37
define('TEMPLATE_RENDER_EDITMODE', 2);
Penny Leach's avatar
Penny Leach committed
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

/**
 * display format for author names in views - firstname
 */
define('FORMAT_NAME_FIRSTNAME', 1);

/**
 * display format for author names in views - lastname
 */
define('FORMAT_NAME_LASTNAME', 2);

/**
 * display format for author names in views - firstname lastname
 */
define('FORMAT_NAME_FIRSTNAMELASTNAME', 3);

/**
 * display format for author names in views - preferred name
 */
define('FORMAT_NAME_PREFERREDNAME', 4);

/**
 * display format for author names in views - student id 
*/
define('FORMAT_NAME_STUDENTID', 5);

/**
 * display format for author names in views - obeys display_name 
 */
67
define('FORMAT_NAME_DISPLAYNAME', 6);
Penny Leach's avatar
Penny Leach committed
68

Martyn Smith's avatar
Martyn Smith committed
69
require_once('artefact.php');
70
71
72
73
74

function template_parse($templatename) {

    $t = array();
    
75
76
77
    $template = template_locate($templatename, false);
    
    $fragment = file_get_contents($template['fragment']);
78
79
80
81

    preg_match_all('/(.*?)\{\{(.*?)\}\}/xms', $fragment, $matches, PREG_SET_ORDER);
    
    $strlen = 0;
82
    $blockids = array();
83
84
85
86
87
88
89
90
91
    foreach ($matches as $m) {
        $temp = array('type'    => 'html',
                      'content' => $m[1],
                      );
        $t[] = $temp;
        $temp = array('type'    => 'block', 
                      'data'    => template_parse_block($m[2]),
                      );
        $t[] = $temp;
92
        $blockids[] = $temp['data']['id'];
93
94
95
96

        $strlen += strlen($m[0]);
           
    }
97
98
99

    if (count($blockids) != count(array_unique($blockids))) {
        $dups = array_unique(array_diff_assoc($blockids, array_unique($blockids)));
100
        throw new TemplateParserException("This template ($templatename) has duplicate block ids: " . implode(', ', $dups));
101
102
    }

103
104
105
106
107
108
    $temp = array('type'    => 'html',
                  'content' => substr($fragment, $strlen),
                  );

    $t[] = $temp;

109
110
    $template['parseddata'] = $t;
    return $template;
111
112
113
114
115
116
117
118
}

function template_parse_block($blockstr) {
    $data = array();
    $bits = explode(' ', $blockstr);

    // the first bit should be 'block'
    if ($bits[0] != 'block') {
119
        throw new TemplateParserException("Invalid block section $blockstr");
120
121
122
123
124
125
126
    }

    array_shift($bits);
    foreach ($bits as $b) {
        $keyvalue = explode('=', $b);
        $data[$keyvalue[0]] = substr($keyvalue[1], 1, -1);
    }
Penny Leach's avatar
Penny Leach committed
127
128

    if (!isset($data['id']) || empty($data['id']) || strpos($data['id'], 'tpl_') !== 0) {
129
        throw new TemplateParserException("Invalid block section $blockstr - must have an id beginning with tpl_");
Penny Leach's avatar
Penny Leach committed
130
131
132
    }

    if (!isset($data['type']) || empty($data['type'])) {
133
        throw new TemplateParserException("Invalid block section $blockstr - must have a type");
Penny Leach's avatar
Penny Leach committed
134
    }
135
    
Penny Leach's avatar
Penny Leach committed
136
137
    $types = array('artefact', 'label', 'title', 'author', 'description');
    if (!in_array($data['type'], $types)){
138
        throw new TemplateParserException("Invalid block section $blockstr (type " . $data['type'] 
Penny Leach's avatar
Penny Leach committed
139
140
141
142
143
                                           . " not one of " . implode(', ', $types));
    }
 
    if (!isset($data['tagtype'])) {
        $data['tagtype'] = 'div';
144
145
    }

Penny Leach's avatar
Penny Leach committed
146
147
148
149
    if ($data['type']  != 'artefact') {
        // no more validation to do.
        return $data;
    }
150

Penny Leach's avatar
Penny Leach committed
151
152
    if (isset($data['artefacttype'])) {
        if (!$plugin = get_field('artefact_installed_type', 'plugin', 'name', $data['artefacttype'])) {
153
            throw new TemplateParserException("artefacttype " . $data['artefacttype'] . " is not installed");
Penny Leach's avatar
Penny Leach committed
154
155
156
157
158
159
        }
     
        if (isset($data['format'])) { // check the artefacttype can render to this format.
            safe_require('artefact', $plugin);

            if (!artefact_can_render_to($data['artefacttype'], $data['format'])) {
160
                throw new TemplateParserException("Artefacttype " . $data['artefacttype'] . " can't render to format "
Penny Leach's avatar
Penny Leach committed
161
162
163
                                                   . $format['format']);
            }
        }
164
        
165
    }
166

Penny Leach's avatar
Penny Leach committed
167
168
169
170
171
    if (isset($data['plugintype'])) {
        try {
            safe_require('artefact', $data['plugintype']);
        }
        catch (Exception $e) {
172
            throw new TemplateParserException("Couldn't find plugin type " . $data['plugintype']);
Penny Leach's avatar
Penny Leach committed
173
        }
174
    }
Penny Leach's avatar
Penny Leach committed
175
176
177

    if (isset($data['defaultartefacttype'])) {
        if (isset($data['artefacttype']) && $data['artefacttype'] != $data['defaultartefacttype']) {
178
            throw new TemplateParserException("Default artefact type " . $data['defaultartefacttype']
Penny Leach's avatar
Penny Leach committed
179
180
                                               . " doesn't make sense given artefact type " . $data['artefacttype']);
        }
181
182
183
184
185
186
187
188
189
190
191
192
        else if (
            isset($data['plugintype']) 
            && !in_array(
                $data['defaultartefacttype'], 
                call_static_method(generate_class_name('artefact', $data['plugintype']), 'get_artefact_types')
            )
        ) {
            throw new TemplateParserException(
                "Default artefact type " . $data['defaultartefacttype'] ." doesn't make sense given plugin type " . $data['plugintype']
                . '. Default artefact type should be one of: '
                . join(', ', call_static_method(generate_class_name('artefact', $data['plugintype']), 'get_artefact_types'))
            );
Penny Leach's avatar
Penny Leach committed
193
194
        }
        if (!$plugin = get_field('artefact_installed_type', 'plugin', 'name', $data['defaultartefacttype'])) {
195
            throw new TemplateParserException("Default artefact type  " . $data['defaultartefacttype'] 
Penny Leach's avatar
Penny Leach committed
196
197
198
199
200
201
202
203
                                               . " is not installed");
        }
        // look for a default format...
        if (!isset($data['defaultformat'])) {
            if (isset($data['format'])) {
                $data['defaultformat'] = $data['format'];
            }
            else {
204
                throw new TemplateParserException("Default artefact type " . $data['defaultartefacttype']
Penny Leach's avatar
Penny Leach committed
205
206
207
208
209
210
211
                                                   ." specified but with no format method (couldn't find in either "
                                                   ." default format, or fallback format field");
            }
        }
        // check the default artefact type can render to the given default format
        safe_require('artefact', $plugin);
        if (!artefact_can_render_to($data['defaultartefacttype'], $data['defaultformat'])) {
212
            throw new TemplateParserException("Default artefact type " . $data['defaultartefacttype'] 
Penny Leach's avatar
Penny Leach committed
213
214
                                               . " can't render to defaultformat " . $format['defaultformat']);
        }
215
        
Penny Leach's avatar
Penny Leach committed
216
217
        // check this default artefact is a 0 or 1 artefact
        if (!call_static_method(generate_artefact_class_name($data['defaultartefacttype']), 'is_0_or_1')) {
218
            throw new TemplateParserException("Default artefact type " . $data['defaultartefacttype']
Penny Leach's avatar
Penny Leach committed
219
220
                                               ." is not a 0 or 1 type artefact");
        }
221
    }
222

Penny Leach's avatar
Penny Leach committed
223
    // @todo resizing stuff maybe
224
    
Penny Leach's avatar
Penny Leach committed
225
    return $data;        
226
227
}

228
function template_locate($templatename, $fetchdb=true) {
229
230

    // check dataroot first for custom templates
231
232
233
    $templatedir = 'templates/' . $templatename . '/';
    $fragment = $templatedir . 'fragment.template';
    $css = $templatedir . 'fragment.css';
234
235
236

    $template = array();

Penny Leach's avatar
Penny Leach committed
237
238
239
240
    $thumbnails = array('jpg'  => 'image/jpeg',
                        'jpeg' => 'image/jpeg',
                        'png'  => 'image/png',
                        'gif'  => 'image/gif');
241

242
    if ($path = realpath(get_config('dataroot') . $fragment)) {
243
        $template['fragment'] = $path;
244
        if (is_readable(get_config('dataroot') . $css)) {
245
246
            $template['css'] = get_config('dataroot') . $css;
        }
Penny Leach's avatar
Penny Leach committed
247
        foreach ($thumbnails as $t => $contenttype) {
248
            if (is_readable(get_config('dataroot') . $templatedir . 'thumbnail.' . $t)) {
Penny Leach's avatar
Penny Leach committed
249
                $template['thumbnailcontenttype'] = $contenttype;
250
251
252
                $template['thumbnail'] = get_config('dataroot') . $templatedir . 'thumbnail.' . $t;
                break;
            }
253
        }
Penny Leach's avatar
Penny Leach committed
254
255
256
257
        if ($dbstuff = get_record('template', 'name', $templatename)) {
            $template['cacheddata'] = unserialize($dbstuff->cacheddata);
            $template['category'] = $dbstuff->category;
        }
258
        $template['location'] = get_config('datarootroot') . 'templates/' . $templatename . '/';
259
260
261
262
        return $template;
    }

    if ($path = realpath(get_config('libroot') . $fragment)) {
263
        $template['fragment'] = $path;
264
        if (is_readable(get_config('libroot') . $css)) {
265
266
            $template['css'] = get_config('libroot') . $css;
        }
Penny Leach's avatar
Penny Leach committed
267
        foreach ($thumbnails as $t => $contenttype) {
268
            if (is_readable(get_config('libroot') . $templatedir . 'thumbnail.' . $t)) {
Penny Leach's avatar
Penny Leach committed
269
                $template['thumbnailcontenttype'] = $contenttype;
270
271
272
                $template['thumbnail'] = get_config('libroot') . $templatedir . 'thumbnail.' . $t;
                break;
            }
273
        }
Penny Leach's avatar
Penny Leach committed
274
275
276
277
        if ($dbstuff = get_record('template', 'name', $templatename)) {
            $template['cacheddata'] = unserialize($dbstuff->cacheddata);
            $template['category'] = $dbstuff->category;
        }
278
        $template['location'] = get_config('libroot') . 'templates/' . $templatename . '/';
279
280
281
        return $template;
    }

282
    throw new TemplateParserException("Invalid template name $templatename, couldn't find");
283
284
}

285
286
287
288
289
/**
 * renders a template in either edit mode or read only mode
 *
 * @param array $template a parsed template see {@link template_parse}
 * @param mode either TEMPLATE_RENDER_READONLY or TEMPLATE_RENDER_EDITMODE
Martyn Smith's avatar
Martyn Smith committed
290
291
292
 * @param array 
 *
 * @returns string the html of the rendered template
293
 */
Martyn Smith's avatar
Martyn Smith committed
294
295
296
297
298
299
300
301
function template_render($template, $mode, $data=array()) {
    if (isset($template['parseddata'])) {
        $td = $template['parseddata'];
    }
    else {
        $td = $template['cacheddata'];
    }

Martyn Smith's avatar
Martyn Smith committed
302
    $droplist = array();
Martyn Smith's avatar
Martyn Smith committed
303
304
    $html = '';

305
306
    foreach ($td as $t) {
        if ($t['type'] == 'html') {
Martyn Smith's avatar
Martyn Smith committed
307
            $html .= $t['content'];
308
309
        }
        else {
Penny Leach's avatar
Penny Leach committed
310
311
312
313
314
315
316
317
318
            $t = $t['data'];
            
            $attr = array(
                'id'    => $t['id'],
                'class' => array('block'),
            );

            if (isset($t['width']) && isset($t['height'])) {
                $attr['style'][] = 'width: ' . $t['width'] . 'px;height: ' . $t['height'] . 'px;';
Martyn Smith's avatar
Martyn Smith committed
319
            }
Penny Leach's avatar
Penny Leach committed
320
321
322
323
324
325
326
327
328
329
330
            
            $block = '';
            
            switch ($t['type']) {
                case 'label';
                    if ($mode == TEMPLATE_RENDER_EDITMODE) {
                        $block .= template_render_label_editmode($t, $data);
                    }
                    else {
                        $block .= $data[$t['id']]['value'];
                    }
331
                    break;
Penny Leach's avatar
Penny Leach committed
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
                case 'title';
                    if (isset($data['title'])) {
                        $block .= hsc($data['title']);
                    }
                    break;
                case 'author';
                    if (isset($data['author'])) {
                        // @todo get authorformat here
                        $block .= hsc($data['author']);
                    }
                    break;
                case 'description';
                    if (isset($data['description'])) {
                        $block .= hsc($data['description']);
                    }
                    break;
                case 'artefact';
                    $classes = array('block');
                    
351
352
353
354
355
                    $droplist[$t['id']] = array(
                        'artefacttype' => isset($t['artefacttype']) ? $t['artefacttype'] : null,
                        'plugintype'   => isset($t['plugintype'])   ? $t['plugintype'] : null,
                        'format'       => isset($t['format'])       ? $t['format'] : null,
                    );
Penny Leach's avatar
Penny Leach committed
356
357
358
                    
                    if ( isset($data[$t['id']]) ) {
                        $artefact = artefact_instance_from_id($data[$t['id']]['id']);
359

Penny Leach's avatar
Penny Leach committed
360
                        $format = FORMAT_ARTEFACT_LISTSELF;
361

Penny Leach's avatar
Penny Leach committed
362
363
                        if (isset($data[$t['id']]['format'])) {
                            $format = $data[$t['id']]['format'];
Martyn Smith's avatar
Martyn Smith committed
364
                        }
Penny Leach's avatar
Penny Leach committed
365
366
367
368
369
370
371
372
                        $block .= $artefact->render($format, null);
                    }
                    else {
                        $block .= '<i>' . get_string('empty_block', 'view') . '</i>';
                    }
                    
                    break;
            }
Martyn Smith's avatar
Martyn Smith committed
373

Penny Leach's avatar
Penny Leach committed
374
375
376
377
378
379
380
381
382
383
384
385
386
387
            // span or div?
            if (isset($t['tagtype']) && $t['tagtype'] == 'span') {
                $html .= '<span';
                $html .= template_render_attributes($attr);
                $html .= '>';
                $html .= $block;
                $html .= '</span>';
            }
            else {
                $html .= '<div';
                $html .= template_render_attributes($attr);
                $html .= '>';
                $html .= $block;
                $html .= '</div>';
Martyn Smith's avatar
Martyn Smith committed
388
            }
389
390
        }
    }
Penny Leach's avatar
Penny Leach committed
391
392
393
394
395
396
397
398
    $javascript = '';
    if ($mode == TEMPLATE_RENDER_EDITMODE) {
        $droplist = json_encode($droplist);
        $spinner_url = json_encode(theme_get_image_path('loading.gif'));
        $wwwroot = get_config('wwwroot');
        
        $json_emptylabel = json_encode(get_string('emptylabel', 'view'));
        $javascript = <<<EOF
Martyn Smith's avatar
Martyn Smith committed
399
400
401
<script type="text/javascript">
    var droplist = $droplist;

Martyn Smith's avatar
Martyn Smith committed
402
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
    function setLabel(element) {
        var value = '';
        if ($(element).labelValue) {
            value = $(element).labelValue;
        }

        var input = INPUT({'type': 'text', 'id': element + '_labelinput'});
        input.value = value;
        replaceChildNodes(element, input);
        input.focus();

        connect(input, 'onkeypress', function (e) {
            if (e.key().code == 13) {
                saveLabel(element);
                e.stop();
            }
            if (e.key().code == 27) {
                saveLabel(element, true);
                e.stop();
            }
            return false;
        });
    }

    function saveLabel(element, revert) {
        if (!revert) {
            $(element).labelValue = $(element + '_labelinput').value;
        }

        if ( $(element).labelValue ) {
            var label = SPAN({'class': 'clickable'}, $(element).labelValue);
            connect(label, 'onclick', function () { setLabel(element) });
            replaceChildNodes(element, label, INPUT({'type': 'hidden', 'name': 'template[' + $(element).id + '][value]', 'value': $(element).labelValue}));
        }
        else {
            var label = createDOM('EM', {'class': 'clickable'}, $json_emptylabel);
            connect(label, 'onclick', function () { setLabel(element) });
            replaceChildNodes(element, label);
        }
    }

443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
    function blockdrop(element, target, format) {
        var srcData = element.moveSource.acceptData;
        var dstData = target.moveTarget.acceptData;

        if ( dstData.format ) {
            format = dstData.format;
        }

        if ( srcData.rendersto.length == 1 ) {
            format = srcData.rendersto[0];
        }

        if (format) {
            replaceChildNodes(target, IMG({ src: {$spinner_url} }));
            var d = loadJSONDoc('{$wwwroot}json/renderartefact.php', {'id': element.artefactid, 'format': format} );
            d.addCallbacks(
                function (response) {
                    target.innerHTML = response.data;
                    appendChildNodes(target,
                        INPUT({'type': 'hidden', 'name': 'template[' + target.id + '][id]', 'value': element.artefactid }),
                        INPUT({'type': 'hidden', 'name': 'template[' + target.id + '][format]', 'value': format })
                    );
                },
                function (error) {
                    alert('TODO: error');
                }
            );
        }
        else {
            formatlist = [];
            forEach (srcData.rendersto, function (fmt) {
                var li = LI({'class': 'clickable'}, get_string('format.' + fmt))
                connect(li, 'onclick', function() { blockdrop(element,target,fmt); });
                formatlist.push(li);
            });
            // need to pick a format
            replaceChildNodes(target, P(null,get_string('chooseformat')), UL(null, formatlist));
        }
Martyn Smith's avatar
Martyn Smith committed
481
482
483
484
    }

    addLoadEvent(function () {
        for ( id in droplist ) {
485
            new MoveTarget(id, {
Martyn Smith's avatar
Martyn Smith committed
486
                ondrop: blockdrop,
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
                hoverClass: 'block_targetted',
                activeClass: 'block_potential',
                acceptData: droplist[id],
                acceptFunction: function (src, dst) {
                    if (dst.plugintype && dst.plugintype != src.plugin) {
                        return false;
                    }
                    if (dst.artefacttype && dst.artefacttype != src.type) {
                        return false;
                    }
                    if (dst.format && !some(src.rendersto, function (rtype) { return dst.format == rtype; })) {
                        return false;
                    }
                    return true;
                }
Martyn Smith's avatar
Martyn Smith committed
502
503
504
505
506
507
            });
        }
    });

</script>
EOF;
Penny Leach's avatar
Penny Leach committed
508
    }
Martyn Smith's avatar
Martyn Smith committed
509
    return $javascript . $html;
510
}
511

Martyn Smith's avatar
Martyn Smith committed
512
513
514
/*
 * @todo: some documentation
 */
Penny Leach's avatar
Penny Leach committed
515
function template_render_label_editmode($block, $data) {
Martyn Smith's avatar
Martyn Smith committed
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
    if (isset($data[$block['id']]['value'])) {
        return '<script type="text/javascript">addLoadEvent(function() { $(' . json_encode($block['id']) . ').labelValue = ' . json_encode($data[$block['id']]['value']) . ';});</script><span class="clickable" onclick="setLabel(' . hsc(json_encode($block['id'])) . ');">' . hsc($data[$block['id']]['value']) . '</span>';
    }
    else {
        return '<em class="clickable" onclick="setLabel(' . hsc(json_encode($block['id'])) . ');">' . get_string('emptylabel', 'view') . '</em>';
    }
}

// @todo : some documentation
function template_render_attributes($attr) {
    if (!is_array($attr) || count($attr) == 0) {
        return '';
    }

    $html = '';

    foreach ( $attr as $key => $value ) {
        if (is_array($value)) {
            $html .= ' ' . $key . '="' . join(' ', array_map('hsc', $value)) . '"';
        }
        else {
            $html .= ' ' . $key . '="' . hsc($value) . '"';
        }
    }

    return $html;
}

Penny Leach's avatar
Penny Leach committed
544
545
546
547
548
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
/**
 * This function formats a user's name
 * according to their view preference
 *
 * @param constant $format FORMAT_NAME_(FIRSTNAME|FIRSTNAMELASTNAME|LASTNAME|PREFERREDNAME|STUDENTID)
 * @param object $user must contain those ^^ fields (or id, in which case a db lookup will be done)
 *
 * @return string formatted name
 */
function template_format_owner($format, $user) {
    
    if (is_int($user)) {
        $user = get_record('usr', 'id', $user);
    }

    if (!is_object($user)) {
        return ''; // @todo throw exception?
    }

    switch ($format) {
        case FORMAT_NAME_FIRSTNAME:
            return $user->firstname;
        case FORMAT_NAME_LASTNAME:
            return $user->lastname;
        case FORMAT_NAME_FIRSTNAMELASTNAME:
            return $user->firstname . ' ' . $user->lastname;
        case FORMAT_NAME_PREFERREDNAME:
            return $user->preferredname;
        case FORMAT_NAME_STUDENTID:
            return $user->studentid;
        case FORMAT_NAME_DISPLAYNAME:
        default:
            return display_name($user);
    }
}

580
?>