web.php 150 KB
Newer Older
1
2
3
4
5
<?php
/**
 *
 * @package    mahara
 * @subpackage core
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
 * @copyright  (C) portions from Moodle, (C) Martin Dougiamas http://dougiamas.com
 */

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


15
16
17
function smarty_core() {
    require_once 'dwoo/dwoo/dwooAutoload.php';
    require_once 'dwoo/mahara/Dwoo_Mahara.php';
18

19
    return new Dwoo_Mahara();
20
21
22
}


23
24
25
26
/**
 * This function creates a Smarty object and sets it up for use within our
 * podclass app, setting up some variables.
 *
27
28
 * WARNING: If you are using pieforms, set them up BEFORE calling this function.
 *
29
30
 * The variables that it sets up are:
 *
31
 * - WWWROOT: The base url for the Mahara system
32
33
34
35
36
 * - USER: The user object
 * - JAVASCRIPT: A list of javascript files to include in the header.  This
 *   list is passed into this function (see below).
 * - HEADERS: An array of any further headers to set.  Each header is just
 *   straight HTML (see below).
37
38
 * - PUBLIC: Set true if this page is a public page
 * - MAINNAV: Array defining the main navigation
39
 *
40
 * @param $javascript A list of javascript includes.  Each include should be just
41
 *                    the name of a file, and reside in js/{filename}
42
43
44
 * @param $headers    A list of additional headers.  These are to be specified as
 *                    actual HTML.
 * @param $strings    A list of language strings required by the javascript code.
45
46
 * @return Smarty
 */
47

48
function smarty($javascript = array(), $headers = array(), $pagestrings = array(), $extraconfig = array()) {
49
    global $USER, $SESSION, $THEME, $HEADDATA, $langselectform;
50
51
52
53
54
55
56
57
58
59
60

    if (!is_array($headers)) {
        $headers = array();
    }
    if (!is_array($pagestrings)) {
        $pagestrings = array();
    }
    if (!is_array($extraconfig)) {
        $extraconfig = array();
    }

61
    $sideblocks = array();
62
63
64
65
66
    // Some things like die_info() will try and create a smarty() call when we are already in one, which causes
    // language_select_form() to throw headdata error as it is called twice.
    if (!isset($langselectform)) {
        $langselectform = language_select_form();
    }
67
68
    $smarty = smarty_core();

69
    $wwwroot = get_config('wwwroot');
Aaron Wells's avatar
Aaron Wells committed
70
    // NOTE: not using jswwwroot - it seems to wreck image paths if you
71
    // drag them around the wysiwyg editor
72
    $jswwwroot = json_encode($wwwroot);
Martyn Smith's avatar
Martyn Smith committed
73

74
75
76
77
78
79
80
81
82
83
    // Workaround for $cfg->cleanurlusersubdomains.
    // When cleanurlusersubdomains is on, ajax requests might come from somewhere other than
    // the wwwroot.  To avoid cross-domain requests, set a js variable when this page is on a
    // different subdomain, and let the ajax wrapper function sendjsonrequest rewrite its url
    // if necessary.
    if (get_config('cleanurls') && get_config('cleanurlusersubdomains')) {
        if ($requesthost = get_requested_host_name()) {
            $wwwrootparts = parse_url($wwwroot);
            if ($wwwrootparts['host'] != $requesthost) {
                $fakewwwroot = $wwwrootparts['scheme'] . '://' . $requesthost . '/';
84
                $headers[] = '<script type="application/javascript">var fakewwwroot = ' . json_encode($fakewwwroot) . ';</script>';
85
86
87
88
            }
        }
    }

Martyn Smith's avatar
Martyn Smith committed
89
    $theme_list = array();
Aaron Wells's avatar
Aaron Wells committed
90

91
92
    if (function_exists('pieform_get_headdata')) {
        $headers = array_merge($headers, pieform_get_headdata());
93
94
95
        if (!defined('PIEFORM_GOT_HEADDATA')) {
          define('PIEFORM_GOT_HEADDATA', 1);
        }
96
    }
97

98
99
100
    // Define the stylesheets array early so that javascript modules can add extras
    $stylesheets = array();

Aaron Wells's avatar
Aaron Wells committed
101
    // Insert the appropriate javascript tags
102
    $javascript_array = array();
103
    $jsroot = $wwwroot . 'js/';
104

105
106
    $langdirection = get_string('thisdirection', 'langconfig');

107
108
    // Make jQuery accessible with $j (Mochikit has $)
    $javascript_array[] = $jsroot . 'jquery/jquery.js';
109
    $javascript_array[] = $jsroot . 'jquery/deprecated_jquery.js';
110
    $headers[] = '<script type="application/javascript">$j=jQuery;</script>';
111

Richard Mansfield's avatar
Richard Mansfield committed
112
    // TinyMCE must be included first for some reason we're not sure about
113
114
115
116
    //
    // Note: we do not display tinyMCE for mobile devices
    // as it doesn't work on some of them and can
    // disable the editing of a textarea field
117
    if ($SESSION->get('handheld_device') == false) {
118
119
120
121
122
123
        $checkarray = array(&$javascript, &$headers);
        $found_tinymce = false;
        foreach ($checkarray as &$check) {
            if (($key = array_search('tinymce', $check)) !== false || ($key = array_search('tinytinymce', $check)) !== false) {
                if (!$found_tinymce) {
                    $found_tinymce = $check[$key];
124
                    $javascript_array[] = $wwwroot . 'artefact/file/js/filebrowser.js';
125
126
                    $javascript_array[] = $jsroot . 'tinymce/tinymce.js';
                    $stylesheets = array_merge($stylesheets, array_reverse(array_values($THEME->get_url('style/tinymceskin.css', true))));
127
                    $content_css = json_encode($THEME->get_url('style/tinymce.css'));
128
129
                    $language = current_language();
                    $language = substr($language, 0, ((substr_count($language, '_') > 0) ? 5 : 2));
130
                    if ($language != 'en' && !file_exists(get_config('docroot') . 'js/tinymce/langs/' . $language . '.js')) {
131
132
133
134
135
136
137
138
                        // In case the language file exists as a string with both lower and upper case, eg fr_FR we test for this
                        $language = substr($language, 0, 2) . '_' . strtoupper(substr($language, 0, 2));
                        if (!file_exists(get_config('docroot') . 'js/tinymce/langs/' . $language . '.js')) {
                            // In case we fail to find a language of 5 chars, eg pt_BR (Portugese, Brazil) we try the 'parent' pt (Portugese)
                            $language = substr($language, 0, 2);
                            if ($language != 'en' && !file_exists(get_config('docroot') . 'js/tinymce/langs/' . $language . '.js')) {
                                $language = 'en';
                            }
139
                        }
140
141
                    }
                    $extrasetup = isset($extraconfig['tinymcesetup']) ? $extraconfig['tinymcesetup'] : '';
142
                    $extramceconfig = isset($extraconfig['tinymceconfig']) ? $extraconfig['tinymceconfig'] : '';
143

144
                    // Check whether to make the spellchecker available
145
                    if (get_config('tinymcespellcheckerengine')) {
146
                        $spellchecker = ',spellchecker';
147
                        $spellchecker_toolbar = '| spellchecker';
148
                        $spellchecker_config = "gecko_spellcheck : false, spellchecker_rpc_url : \"{$jsroot}tinymce/plugins/spellchecker/spellchecker.php\",";
149
150
                    }
                    else {
151
                        $spellchecker = $spellchecker_toolbar = '';
152
153
154
                        $spellchecker_config = 'gecko_spellcheck : true,';
                    }

155
                    $toolbar = array(
156
                        null,
157
                        '"toolbar_toggle | formatselect | bold italic | bullist numlist | link unlink | imagebrowser | undo redo"',
158
                        '"underline strikethrough subscript superscript | alignleft aligncenter alignright alignjustify | outdent indent | forecolor backcolor | ltr rtl | fullscreen"',
159
                        '"fontselect | fontsizeselect | emoticons nonbreaking charmap ' . $spellchecker_toolbar . ' | table | removeformat pastetext | code"',
160
161
162
163
164
165
                    );

                    // For right-to-left langs, reverse button order & align controls right.
                    $tinymce_langdir = $langdirection == 'rtl' ? 'rtl' : 'ltr';
                    $toolbar_align = 'left';

166
167
168
169
                    // Language strings required for TinyMCE
                    $pagestrings['mahara'] = isset($pagestrings['mahara']) ? $pagestrings['mahara'] : array();
                    $pagestrings['mahara'][] = 'attachedimage';

170
                    if ($check[$key] == 'tinymce') {
171
172
                        $tinymceconfig = <<<EOF
    theme: "modern",
173
    plugins: "tooltoggle,textcolor,visualblocks,wordcount,link,imagebrowser,table,emoticons{$spellchecker},paste,code,fullscreen,directionality,searchreplace,nonbreaking,charmap",
174
175
    toolbar1: {$toolbar[1]},
    toolbar2: {$toolbar[2]},
176
    toolbar3: {$toolbar[3]},
177
    menubar: false,
178
    fix_list_elements: true,
179
    image_advtab: true,
180
    {$spellchecker_config}
181
EOF;
182
183
                    }
                    else {
184
185
186
187
188
                        $tinymceconfig = <<<EOF
    selector: "textarea.tinywysiwyg",
    theme: "modern",
    plugins: "fullscreen,autoresize",
    toolbar: {$toolbar[0]},
189
EOF;
190
                    }
191

192
                    $headers[] = <<<EOF
193
<script type="application/javascript">
194
tinyMCE.init({
195
196
    {$tinymceconfig}
    schema: 'html4',
197
    extended_valid_elements : "object[width|height|classid|codebase],param[name|value],embed[src|type|width|height|flashvars|wmode],script[src,type,language],+ul[id|type|compact],iframe[src|width|height|align|title|class|type|frameborder|allowfullscreen]",
198
    urlconverter_callback : "custom_urlconvert",
199
    language: '{$language}',
200
    directionality: "{$tinymce_langdir}",
201
    content_css : {$content_css},
202
    remove_script_host: false,
203
    relative_urls: false,
204
    {$extramceconfig}
205
    setup: function(ed) {
206
        ed.on('init', function(ed) {
207
            if (typeof(editor_to_focus) == 'string' && ed.editorId == editor_to_focus) {
208
209
210
                ed.focus();
            }
        });
211
212
213
        ed.on('LoadContent', function(e) {
            // Hide all the 2nd/3rd row menu buttons
            jQuery('.mce-toolbar.mce-first').siblings().toggleClass('hidden');
214
215
216
217
218
219
220
221
222
223
224
            // The tinymce fullscreen mode does not work properly in a transformed container div
            // such as div.vertcentre
            // and IE doesn't like a preset z-index
            // This work-around will remove/add classes: .vertcenter .configure .blockinstane
            // of the configure block div
            // when toggling fullscreen
            jQuery('div[aria-label="Fullscreen"]').on('click', function(e) {
                jQuery('div#configureblock').toggleClass('vertcentre');
                jQuery('div#configureblock').toggleClass('blockinstance');
                jQuery('div#configureblock').toggleClass('configure');
            });
225
        });
226
        {$extrasetup}
227
    }
228
});
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245

function imageBrowserConfigSuccess(form, data) {
    // handle updates to file browser
    // final form submission handled by tinymce plugin
    if (data.formelementsuccess) {
        eval(data.formelementsuccess + '(form, data)');
        return;
    }
}

function imageBrowserConfigError(form, data) {
    if (data.formelementerror) {
        eval(data.formelementerror + '(form, data)');
        return;
    }
}

246
function custom_urlconvert (u, n, e) {
247
248
    // Don't convert the url on the skype status buttons.
    if (u.indexOf('skype:') == 0) {
249
      return u;
250
251
    }
    var t = tinyMCE.activeEditor, s = t.settings;
252

253
254
    // Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
    if (!s.convert_urls || (e && e.nodeName == 'LINK') || u.indexOf('file:') === 0)
255
256
      return u;

257
258
    // Convert to relative
    if (s.relative_urls)
259
260
      return t.documentBaseURI.toRelative(u);

261
262
    // Convert to absolute
    u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
263

264
    return u;
265
}
266
267
268
</script>

EOF;
269
270
271
272
273
274
275
                    unset($check[$key]);
                }
                else {
                    if ($check[$key] != $found_tinymce) {
                        log_warn('Two differently configured tinyMCE instances have been asked for on this page! This is not possible');
                    }
                    unset($check[$key]);
276
                }
277
            }
278

279
280
281
282
            // If any page adds jquery explicitly, remove it from the list
            if (($key = array_search('jquery', $check)) !== false) {
                unset($check[$key]);
            }
283
        }
284
    }
285
286
287
288
289
290
291
292
    else {
        if (($key = array_search('tinymce', $javascript)) !== false || ($key = array_search('tinytinymce', $javascript)) !== false) {
            unset($javascript[$key]);
        }
        if (($key = array_search('tinymce', $headers)) !== false || ($key = array_search('tinytinymce', $headers)) !== false) {
            unset($headers[$key]);
        }
    }
293

294
    if (get_config('developermode') & DEVMODE_UNPACKEDJS) {
295
        $javascript_array[] = $jsroot . 'MochiKit/MochiKit.js';
296
297
298
299
        $javascript_array[] = $jsroot . 'MochiKit/Position.js';
        $javascript_array[] = $jsroot . 'MochiKit/Color.js';
        $javascript_array[] = $jsroot . 'MochiKit/Visual.js';
        $javascript_array[] = $jsroot . 'MochiKit/DragAndDrop.js';
300
        $javascript_array[] = $jsroot . 'MochiKit/Format.js';
301
302
303
304
    }
    else {
        $javascript_array[] = $jsroot . 'MochiKit/Packed.js';
    }
Martyn Smith's avatar
Martyn Smith committed
305
    $javascript_array[] = $jsroot . 'keyboardNavigation.js';
306

307
    $strings = array();
308
309
310
311
312
313
314
315
316
    foreach ($pagestrings as $k => $v) {
        if (is_array($v)) {
            foreach ($v as $tag) {
                $strings[$tag] = get_raw_string($tag, $k);
            }
        }
        else {
            $strings[$k] = get_raw_string($k, $v);
        }
317
318
    }

319
    $jsstrings = jsstrings();
Martyn Smith's avatar
Martyn Smith committed
320
    $themepaths = themepaths();
321

Richard Mansfield's avatar
Richard Mansfield committed
322
    foreach ($javascript as $jsfile) {
323
324
325
326
        // For now, if there's no path in the js file, assume it's in
        // $jsroot and append '.js' to the name.  Later we may want to
        // ensure all smarty() calls include the full path to the js
        // file, with the proper extension.
327
        if (strpos($jsfile, '/') === false) {
328
            $javascript_array[] = $jsroot . $jsfile . '.js';
329
            if (isset($jsstrings[$jsfile])) {
330
331
332
                foreach ($jsstrings[$jsfile] as $section => $tags) {
                    foreach ($tags as $tag) {
                        $strings[$tag] = get_raw_string($tag, $section);
333
334
335
                    }
                }
            }
Martyn Smith's avatar
Martyn Smith committed
336
337
            if (isset($themepaths[$jsfile])) {
                foreach ($themepaths[$jsfile] as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
338
                    $theme_list[$themepath] = $THEME->get_url($themepath);
Martyn Smith's avatar
Martyn Smith committed
339
340
                }
            }
341
        }
342
        else if (stripos($jsfile, 'http://') === false && stripos($jsfile, 'https://') === false) {
343
            // A local .js file with a fully specified path
344
            $javascript_array[] = $wwwroot . $jsfile;
345
346
347
348
349
350
            // If $jsfile is from a plugin or plugin's block, i.e.:
            // - plugintype/pluginname/js/foo.js
            // - plugintype/pluginname/blocktype/pluginname/js/foo.js
            // Then get js strings from static function jsstrings in:
            // - plugintype/pluginname/lib.php, or
            // - plugintype/pluginname/blocktype/pluginname/lib.php
351
            $bits = explode('/', $jsfile);
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
            $pluginname = false;
            $plugintype = false;
            $jsfilename = false;
            if (count($bits) == 4 && $bits[2] == 'js' && in_array($bits[0], plugin_types())) {
                $plugintype = $bits[0];
                $pluginname = $bits[1];
                $jsfilename = $bits[3];
            }
            if (count($bits) == 6 && $bits[0] == 'artefact' && $bits[2] == 'blocktype' && $bits[4] == 'js') {
                $plugintype = 'blocktype';
                $pluginname = $bits[3];
                $jsfilename = $bits[5];
            }
            if ($pluginname) {
                safe_require($plugintype, $pluginname);
                $pluginclass = generate_class_name($plugintype, $pluginname);
                $name = substr($jsfilename, 0, strpos($jsfilename, '.js'));
369
370
                if (is_callable(array($pluginclass, 'jsstrings'))) {
                    $tempstrings = call_static_method($pluginclass, 'jsstrings', $name);
371
372
373
                    foreach ($tempstrings as $section => $tags) {
                        foreach ($tags as $tag) {
                            $strings[$tag] = get_raw_string($tag, $section);
374
375
                        }
                    }
Richard Mansfield's avatar
Richard Mansfield committed
376
                }
377
378
379
380
                if (is_callable(array($pluginclass, 'jshelp'))) {
                    $tempstrings = call_static_method($pluginclass, 'jshelp', $name);
                    foreach ($tempstrings as $section => $tags) {
                        foreach ($tags as $tag) {
381
                            $strings[$tag . '.help'] = get_help_icon($plugintype, $pluginname, null, null,
382
383
384
385
                                                                     null, $tag);
                        }
                    }
                }
Martyn Smith's avatar
Martyn Smith committed
386
387
388
                if (is_callable(array($pluginclass, 'themepaths'))) {
                    $tmpthemepaths = call_static_method($pluginclass, 'themepaths', $name);
                    foreach ($tmpthemepaths as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
389
                        $theme_list[$themepath] = $THEME->get_url($themepath);
Martyn Smith's avatar
Martyn Smith committed
390
391
                    }
                }
Martyn Smith's avatar
Martyn Smith committed
392
            }
Martyn Smith's avatar
Martyn Smith committed
393
        }
394
395
396
397
        else {
            // A remote .js file
            $javascript_array[] = $jsfile;
        }
398
    }
399
400

    $javascript_array[] = $jsroot . 'mahara.js';
401
    $javascript_array[] = $jsroot . 'formchangechecker.js';
402
    if (get_config('developermode') & DEVMODE_DEBUGJS) {
403
        $javascript_array[] = $jsroot . 'debug.js';
404
    }
405

406
407
408
    foreach ($jsstrings['mahara'] as $section => $tags) {
        foreach ($tags as $tag) {
            $strings[$tag] = get_raw_string($tag, $section);
409
410
        }
    }
411
412
    if (isset($extraconfig['themepaths']) && is_array($extraconfig['themepaths'])) {
        foreach ($extraconfig['themepaths'] as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
413
            $theme_list[$themepath] = $THEME->get_url($themepath);
414
415
        }
    }
416

417
    $stringjs = '<script type="application/javascript">';
418
    $stringjs .= 'var strings = ' . json_encode($strings) . ';';
419
    $stringjs .= "\nfunction plural(n) { return " . get_raw_string('pluralrule', 'langconfig') . "; }\n";
420
421
    $stringjs .= '</script>';

422
    // stylesheet set up - if we're in a plugin also get its stylesheet
423
    $stylesheets = array_merge($stylesheets, array_reverse(array_values($THEME->get_url('style/style.css', true))));
424
    if (defined('SECTION_PLUGINTYPE') && defined('SECTION_PLUGINNAME') && SECTION_PLUGINTYPE != 'core') {
Nigel McNie's avatar
Nigel McNie committed
425
        if ($pluginsheets = $THEME->get_url('style/style.css', true, SECTION_PLUGINTYPE . '/' . SECTION_PLUGINNAME)) {
426
427
428
            $stylesheets = array_merge($stylesheets, array_reverse($pluginsheets));
        }
    }
429
430

    if ($adminsection = in_admin_section()) {
431
432
433
        if ($adminsheets = $THEME->get_url('style/admin.css', true)) {
            $stylesheets = array_merge($stylesheets, array_reverse($adminsheets));
        }
434
    }
435

436
    if (get_config('developermode') & DEVMODE_DEBUGCSS) {
437
438
        $stylesheets[] = get_config('wwwroot') . 'theme/debug.css';
    }
439

440
441
442
    // look for extra stylesheets
    if (isset($extraconfig['stylesheets']) && is_array($extraconfig['stylesheets'])) {
        foreach ($extraconfig['stylesheets'] as $extrasheet) {
443
            if ($sheets = $THEME->get_url($extrasheet, true)) {
Nigel McNie's avatar
Nigel McNie committed
444
                $stylesheets = array_merge($stylesheets, array_reverse(array_values($sheets)));
445
446
447
            }
        }
    }
448
449
450
    if ($sheets = $THEME->additional_stylesheets()) {
        $stylesheets = array_merge($stylesheets, $sheets);
    }
451

452
453
454
455
456
457
458
459
    // Give the skin a chance to affect the page
    if (!empty($extraconfig['skin'])) {
        require_once(get_config('docroot').'/lib/skin.php');
        $skinobj = new Skin($extraconfig['skin']['skinid']);
        $viewid = isset($extraconfig['skin']['viewid']) ? $extraconfig['skin']['viewid'] : null;
        $stylesheets = array_merge($stylesheets, $skinobj->get_stylesheets($viewid));
    }

460
461
462
    // Allow us to set the HTML lang attribute
    $smarty->assign('LANGUAGE', substr(current_language(), 0, 2));

463
    // Include rtl.css for right-to-left langs
464
    if ($langdirection == 'rtl') {
465
        $smarty->assign('LANGDIRECTION', 'rtl');
466
467
468
469
470
        if ($rtlsheets = $THEME->get_url('style/rtl.css', true)) {
            $stylesheets = array_merge($stylesheets, array_reverse($rtlsheets));
        }
    }

471
    $smarty->assign('STRINGJS', $stringjs);
472
    $stylesheets = append_version_number($stylesheets);
473
    $smarty->assign('STYLESHEETLIST', $stylesheets);
474
475
    if (!empty($theme_list)) {
        // this gets assigned in smarty_core, but do it again here if it's changed locally
Aaron Wells's avatar
Aaron Wells committed
476
        $smarty->assign('THEMELIST', json_encode(array_merge((array)json_decode($smarty->get_template_vars('THEMELIST')),  $theme_list)));
477
    }
478

479
480
481
482
483
484
485
486
487
488
489
490
491
    $dropdownmenu = get_config('dropdownmenu');
    // disable drop-downs if overridden at institution level
    $sitethemeprefs = get_config('sitethemeprefs');
    $institutions = $USER->institutions;
    if (!empty($institutions)) {
        foreach ($institutions as $i) {
            if (!empty($sitethemeprefs)) {
                if (!empty($USER->accountprefs['theme']) && $USER->accountprefs['theme'] == $THEME->basename . '/' . $i->institution) {
                    $dropdownmenu = $i->dropdownmenu;
                }
            }
            else {
                if ((!empty($USER->accountprefs['theme']) && $USER->accountprefs['theme'] == $THEME->basename . '/' . $i->institution)
492
493
                    || (empty($USER->accountprefs) && $i->theme == $THEME->basename && $USER->institutiontheme->institutionname == $i->institution)) {
                    $dropdownmenu = $i->dropdownmenu;
494
495
496
497
498
499
500
501
                }
            }
        }
    }

    // and/or disable drop-downs if a handheld device detected
    $dropdownmenu = $SESSION->get('handheld_device') ? false : $dropdownmenu;

502
503
    if ($dropdownmenu) {
        $smarty->assign('DROPDOWNMENU', $dropdownmenu);
504
        $javascript_array[] = $jsroot . 'dropdown-nav.js';
505
    }
506

507
    $smarty->assign('MOBILE', $SESSION->get('mobile'));
508
    $smarty->assign('HANDHELD_DEVICE', $SESSION->get('handheld_device'));
509

510
511
512
513
    $sitename = get_config('sitename');
    if (!$sitename) {
       $sitename = 'Mahara';
    }
514
    $smarty->assign('sitename', $sitename);
515
    $sitelogo = $THEME->header_logo();
516
    $sitelogo = append_version_number($sitelogo);
517
    $smarty->assign('sitelogo', $sitelogo);
518
519
    $smarty->assign('sitelogo4facebook', $THEME->facebook_logo());
    $smarty->assign('sitedescription4facebook', get_string('facebookdescription', 'mahara'));
520

Martyn Smith's avatar
Martyn Smith committed
521
    if (defined('TITLE')) {
522
        $smarty->assign('PAGETITLE', TITLE . ' - ' . $sitename);
523
        $smarty->assign('heading', TITLE);
Martyn Smith's avatar
Martyn Smith committed
524
525
    }
    else {
526
        $smarty->assign('PAGETITLE', $sitename);
Martyn Smith's avatar
Martyn Smith committed
527
528
    }

529
    $smarty->assign('PRODUCTIONMODE', get_config('productionmode'));
530
    if (function_exists('local_header_top_content')) {
531
        $sitetop = (isset($sitetop) ? $sitetop : '') . local_header_top_content();
532
533
534
    }
    if (isset($sitetop)) {
        $smarty->assign('SITETOP', $sitetop);
535
    }
536
537
538
    if (defined('PUBLIC')) {
        $smarty->assign('PUBLIC', true);
    }
539
540
541
    if (defined('ADMIN')) {
        $smarty->assign('ADMIN', true);
    }
542
543
544
    if (defined('INSTITUTIONALADMIN')) {
        $smarty->assign('INSTITUTIONALADMIN', true);
    }
545
546
547
548
549
550
    if (defined('STAFF')) {
        $smarty->assign('STAFF', true);
    }
    if (defined('INSTITUTIONALSTAFF')) {
        $smarty->assign('INSTITUTIONALSTAFF', true);
    }
551

552
    $smarty->assign('LOGGEDIN', $USER->is_logged_in());
553
554
555
556
557
558
559
    $publicsearchallowed = false;
    $searchplugin = get_config('searchplugin');
    if ($searchplugin) {
        safe_require('search', $searchplugin);
        $publicsearchallowed = (call_static_method(generate_class_name('search', $searchplugin), 'publicform_allowed') && get_config('publicsearchallowed'));
    }
    $smarty->assign('publicsearchallowed', $publicsearchallowed);
560
    if ($USER->is_logged_in()) {
561
        global $SELECTEDSUBNAV; // It's evil, but rightnav & mainnav stuff are now in different templates.
562
        $smarty->assign('MAINNAV', main_nav());
563
        $mainnavsubnav = $SELECTEDSUBNAV;
564
        $smarty->assign('RIGHTNAV', right_nav());
565
566
567
568
569
570
571
572
573
        if (!$mainnavsubnav && $dropdownmenu) {
            // In drop-down navigation, the submenu is only usable if its parent is one of the top-level menu
            // items.  But if the submenu comes from something in right_nav (settings), it's unreachable.
            // Turning the submenu into SUBPAGENAV group-style tabs makes it usable.
            $smarty->assign('SUBPAGENAV', $SELECTEDSUBNAV);
        }
        else {
            $smarty->assign('SELECTEDSUBNAV', $SELECTEDSUBNAV);
        }
574
    }
575
    else {
576
        $smarty->assign('languageform', $langselectform);
577
    }
578
    $smarty->assign('FOOTERMENU', footer_menu());
579

580
    $smarty->assign_by_ref('USER', $USER);
581
    $smarty->assign('SESSKEY', $USER->get('sesskey'));
582
    $smarty->assign('CC_ENABLED', get_config('cookieconsent_enabled'));
583
    $javascript_array = append_version_number($javascript_array);
584
    $smarty->assign_by_ref('JAVASCRIPT', $javascript_array);
585
    $smarty->assign('RELEASE', get_config('release'));
586
    $smarty->assign('SERIES', get_config('series'));
587
    $smarty->assign('CACHEVERSION', get_config('cacheversion'));
588
589
    $siteclosedforupgrade = get_config('siteclosed');
    if ($siteclosedforupgrade && get_config('disablelogin')) {
590
        $smarty->assign('SITECLOSED', 'logindisabled');
591
592
    }
    else if ($siteclosedforupgrade || get_config('siteclosedbyadmin')) {
593
        $smarty->assign('SITECLOSED', 'loginallowed');
594
    }
595

596
597
    if ((!isset($extraconfig['pagehelp']) || $extraconfig['pagehelp'] !== false)
        and $help = has_page_help()) {
598
599
600
        $smarty->assign('PAGEHELPNAME', $help[0]);
        $smarty->assign('PAGEHELPICON', $help[1]);
    }
601
    if (defined('GROUP')) {
602
        require_once('group.php');
603
604
605
606
607
608
        if ($group = group_current_group()) {
            $smarty->assign('GROUP', $group);
            if (!defined('NOGROUPMENU')) {
                $smarty->assign('SUBPAGENAV', group_get_menu_tabs());
                $smarty->assign('PAGEHEADING', $group->name);
            }
609
        }
610
    }
611

Martyn Smith's avatar
Martyn Smith committed
612
    // ---------- sideblock stuff ----------
613
614
    $sidebars = !isset($extraconfig['sidebars']) || $extraconfig['sidebars'] !== false;
    if ($sidebars && !defined('INSTALLER') && (!defined('MENUITEM') || substr(MENUITEM, 0, 5) != 'admin')) {
615
        if (get_config('installed') && !$adminsection) {
616
617
618
            $data = site_menu();
            if (!empty($data)) {
                $smarty->assign('SITEMENU', site_menu());
619
                $sideblocks[] = array(
620
                    'name'   => 'linksandresources',
621
622
623
624
625
626
                    'weight' => 10,
                    'data'   => $data,
                );
            }
        }

627
628
        if ($USER->is_logged_in() && defined('MENUITEM') &&
            (substr(MENUITEM, 0, 11) == 'myportfolio' || substr(MENUITEM, 0, 7) == 'content')) {
629
            if (get_config('showselfsearchsideblock')) {
630
                $sideblocks[] = array(
631
632
633
634
635
636
                    'name'   => 'selfsearch',
                    'weight' => 0,
                    'data'   => array(),
                );
            }
            if (get_config('showtagssideblock')) {
637
                $sideblocks[] = array(
638
639
640
641
642
643
                    'name'   => 'tags',
                    'id'     => 'sb-tags',
                    'weight' => 0,
                    'data'   => tags_sideblock(),
                );
            }
Clare Lenihan's avatar
Clare Lenihan committed
644
        }
Clare Lenihan's avatar
Clare Lenihan committed
645

646
        if ($USER->is_logged_in() && !$adminsection) {
647
            $sideblocks[] = array(
648
                'name'   => 'profile',
649
                'id'     => 'sb-profile',
650
651
652
                'weight' => -20,
                'data'   => profile_sideblock()
            );
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
            $showusers = 2;
            $institutions = $USER->institutions;
            if (!empty($institutions)) {
                $showusers = 0;
                foreach ($institutions as $i) {
                    if ($i->showonlineusers == 2) {
                        $showusers = 2;
                        break;
                    }
                    if ($i->showonlineusers == 1) {
                        $showusers = 1;
                    }
                }
            }
            if (get_config('showonlineuserssideblock') && $showusers > 0) {
668
                $sideblocks[] = array(
669
                    'name'   => 'onlineusers',
670
                    'id'     => 'sb-onlineusers',
671
672
673
674
                    'weight' => -10,
                    'data'   => onlineusers_sideblock(),
                );
            }
675
            if (get_config('showprogressbar') && $USER->get_account_preference('showprogressbar')) {
676
                $sideblocks[] = array(
677
678
                    'name'   => 'progressbar',
                    'id'     => 'sb-progressbar',
679
                    'weight' => -8,
680
681
682
683
684
685
                    'data'   => progressbar_sideblock(),
                );
            }
        }

        if ($USER->is_logged_in() && $adminsection && defined('SECTION_PAGE') && SECTION_PAGE == 'progressbar') {
686
            $sideblocks[] = array(
687
688
                'name'   => 'progressbar',
                'id'     => 'sb-progressbar',
689
                'weight' => -8,
690
691
                'data'   => progressbar_sideblock(true),
            );
692
        }
Martyn Smith's avatar
Martyn Smith committed
693

694
695
696
        $isloginblockvisible = !$USER->is_logged_in() && !(get_config('siteclosed') && get_config('disablelogin'))
                && get_config('showloginsideblock');
        if ($isloginblockvisible) {
697
            $sideblocks[] = array(
698
699
                'name'   => 'login',
                'weight' => -10,
700
                'id'     => 'sb-loginbox',
701
702
703
                'data'   => array(
                    'loginform' => auth_generate_login_form(),
                ),
704
705
            );
        }
706

707
708
709
        if (get_config('enablenetworking')) {
            require_once(get_config('docroot') .'api/xmlrpc/lib.php');
            if ($USER->is_logged_in() && $ssopeers = get_service_providers($USER->authinstance)) {
710
                $sideblocks[] = array(
711
712
713
714
715
                    'name'   => 'ssopeers',
                    'weight' => 1,
                    'data'   => $ssopeers,
                );
            }
Martyn Smith's avatar
Martyn Smith committed
716
717
        }

718
719
        if (isset($extraconfig['sideblocks']) && is_array($extraconfig['sideblocks'])) {
            foreach ($extraconfig['sideblocks'] as $sideblock) {
720
                $sideblocks[] = $sideblock;
721
722
            }
        }
Martyn Smith's avatar
Martyn Smith committed
723

724
        // local_sideblocks_update allows sites to customise the sideblocks by munging the $sideblocks array.
725
        if (function_exists('local_sideblocks_update')) {
726
            local_sideblocks_update($sideblocks);
727
728
        }

729
        usort($sideblocks, create_function('$a,$b', 'if ($a["weight"] == $b["weight"]) return 0; return ($a["weight"] < $b["weight"]) ? -1 : 1;'));
730

Aaron Wells's avatar
Aaron Wells committed
731
732
        // Place all sideblocks on the right. If this structure is munged
        // appropriately, you can put blocks on the left. In future versions of
733
        // Mahara, we'll make it easy to do this.
734
735
        $sidebars = $sidebars && !empty($sideblocks);
        $sideblocks = array('left' => array(), 'right' => $sideblocks);
736

737
        $smarty->assign('userauthinstance', $SESSION->get('authinstance'));
738
        $smarty->assign('MNETUSER', $SESSION->get('mnetuser'));
739
        $smarty->assign('SIDEBLOCKS', $sideblocks);
740
        $smarty->assign('SIDEBARS', $sidebars);
741

742
743
    }

744
745
746
747
748
    if (is_array($HEADDATA) && !empty($HEADDATA)) {
        $headers = array_merge($HEADDATA, $headers);
    }
    $smarty->assign_by_ref('HEADERS', $headers);

749
750
    if ($USER->get('parentuser')) {
        $smarty->assign('USERMASQUERADING', true);
751
        $smarty->assign('masqueradedetails', get_string('youaremasqueradingas', 'mahara', display_name($USER)));
752
753
        $smarty->assign('becomeyouagain',
            ' <a href="' . hsc($wwwroot) . 'admin/users/changeuser.php?restore=1">'
754
            . get_string('becomeadminagain', 'admin', hsc($USER->get('parentuser')->name))
755
            . '</a>');
756
    }
Martyn Smith's avatar
Martyn Smith committed
757

758
759
    // Define additional html content
    if (get_config('installed')) {
760
761
762
763
        $additionalhtmlitems = array(
            'ADDITIONALHTMLHEAD'      => get_config('additionalhtmlhead'),
            'ADDITIONALHTMLTOPOFBODY' => get_config('additionalhtmltopofbody'),
            'ADDITIONALHTMLFOOTER'    => get_config('additionalhtmlfooter')
764
765
        );
        if ($additionalhtmlitems) {
766
767
            foreach ($additionalhtmlitems as $name=>$content) {
                $smarty->assign($name, $content);
768
769
770
            }
        }
    }
771
772
773
774
775
776

    // If Cookie Consent is enabled, than define conent
    if (get_config('cookieconsent_enabled')) {
        require_once('cookieconsent.php');
        $smarty->assign('COOKIECONSENTCODE', get_cookieconsent_code());
    }
777
778
779
    return $smarty;
}

780
781
782
783

/**
 * Manages theme configuration.
 *
Aaron Wells's avatar
Aaron Wells committed
784
 * Does its best to give the user _a_ theme, even if it's not the theme they
785
786
787
788
789
790
791
792
793
 * want to use (e.g. the theme they want has been uninstalled)
 */
class Theme {

    /**
     * The base name of the theme (the name of the directory in which it lives)
     */
    public $basename = '';

794
795
796
797
798
    /**
     * A user may have had the header logo overridden by an institution
     */
    public $headerlogo;

799
800
801
802
803
    /**
     * Additional stylesheets to display after the basename theme's stylesheets
     */
    public $addedstylesheets;

804
805
806
807
808
809
810
811
812
813
    /**
     * A human-readable version of the theme name
     */
    public $displayname = '';

    /**
     * Which pieform renderer to use by default for all forms
     */
    public $formrenderer = '';

814
815
816
817
818
819
820
821
822
823
    /**
     * Directories where to look for templates by default
     */
    public $templatedirs = array();

    /**
     * Theme inheritance path from this theme to 'raw'
     */
    public $inheritance = array();

824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
    /**
     * What unit the left/center/right column widths are in. 'pixels' or 'percent'
     */
    public $columnwidthunits    = '';

    /**
     * Width of the left column. Integer - see $columnwidthunits
     */
    public $leftcolumnwidth     = 256;

    /**
     * Background colour for the left column
     */
    public $leftcolumnbgcolor   = '#fff';

    /**
     * Background colour for the center column
     */
    public $centercolumnbgcolor = '#fff';

    /**
     * Width of the right column. Integer - see $columnwidthunits
     */
    public $rightcolumnwidth    = 256;

    /**
     * Background colour for the right column
     */
    public $rightcolumnbgcolor  = '#fff';

854
855
856
857
    /**
     * If the theme can use the svg image file format.
     */
    public $usesvg  = false;
858
859
860
861

    /**
     * Initialises a theme object based on the theme 'hint' passed.
     *
Aaron Wells's avatar
Aaron Wells committed
862
863
     * If arg is a string, it's taken to be a theme name. If it's a user
     * object, we ask it for a theme name. If it's an integer, we pretend
864
865
     * that's a user ID and ask for the theme for that user.
     *
Aaron Wells's avatar
Aaron Wells committed
866
     * If the theme they want doesn't exist, the object is initialised for the
867
868
869
870
871
872
873
874
     * default theme. This means you can initialise one of these for a user
     * and then use it without worrying if the theme exists.
     *
     * @param mixed $arg Theme name, user object or user ID
     */
    public function __construct($arg) {
        if (is_string($arg)) {
            $themename = $arg;
875
            $themedata = null;
876
877
        }
        else if ($arg instanceof User) {
878
            $themedata = $arg->get_themedata();
879
880
881
882
        }
        else if (is_int($arg)) {
            $user = new User();
            $user->find_by_id($arg);
883
            $themedata = $user->get_themedata();
884
885
886
887
888
        }
        else {
            throw new SystemException("Argument to Theme::__construct was not a theme name, user object or user ID");
        }

889
890
891
892
        if (isset($themedata)) {
            $themename = $themedata->basename;
        }

893
        if (empty($themename)) {
894
            // Theme to show to when no theme has been suggested
895
896
897
            if (!$themename = get_config('theme')) {
                $themename = 'raw';
            }
898
        }
899
900

        // check the validity of the name
901
        if (!$this->name_is_valid($themename)) {
902
903
            throw new SystemException("Theme name is in invalid form: '$themename'");
        }
904
905

        $this->init_theme($themename, $themedata);
906
907
908
909
910
911
912
913
    }

    /**
     * Given a theme name, check that it is valid
     */
    public static function name_is_valid($themename) {
        // preg_match returns 0 if invalid characters were found, 1 if not
        return (preg_match('/^[a-zA-Z0-9_-]+$/', $themename) == 1);
914
915
916
917
918
    }

    /**
     * Given a theme name, reads in all config and sets fields on this object
     */
919
    private function init_theme($themename, $themedata) {
920

921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
        // A little anonymous function to retrieve *only* the $theme variable from
        // the themeconfig.php file
        $getthemeconfig = function($themename) {
            $themeconfigfile = get_config('docroot') . 'theme/' . $themename . '/themeconfig.php';
            if (is_readable($themeconfigfile)) {
                require( get_config('docroot') . 'theme/' . $themename . '/themeconfig.php' );
                return $theme;
            }
            else {
                return false;
            }
        };

        $themeconfig = $getthemeconfig($themename);

        if (!$themeconfig) {
Aaron Wells's avatar
Aaron Wells committed
937
            // We can safely assume that the default theme is installed, users
938
            // should never be able to remove it
939
940
            $themename ='default';
            $themeconfig = $getthemeconfig($themename);
941
942
        }

943
        $this->basename = $themename;
944

945
        foreach (get_object_vars($themeconfig) as $key => $value) {
946
947
948
949
            $this->$key = $value;
        }

        if (!isset($this->displayname)) {
950
            $this->displayname = $this->basename;
951
952
        }

953
954
955
956
        // Local theme overrides come first
        $this->templatedirs[] = get_config('docroot') . 'local/theme/templates/';

        // Then the current theme
957
958
959
        $this->templatedirs[] = get_config('docroot') . 'theme/' . $this->basename . '/templates/';
        $this->inheritance[]  = $this->basename;

960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
        // 'raw' is the default parent theme
        // (If a theme has no parent, it should set $themeconfig->parent = false)
        if (!isset($themeconfig->parent)) {
            $themeconfig->parent = 'raw';
        }
        $currentthemename = $this->basename;
        while ($themeconfig->parent !== false) {
            // Now go through the theme hierarchy assigning variables from the
            // parent themes
            $parentthemename = $themeconfig->parent;
            $parentthemeconfig = $getthemeconfig($parentthemename);

            // If the parent theme is missing, short-circuit to the "raw" theme
            if (!$parentthemeconfig) {
                log_warn("Theme \"{$currentthemename}\" has missing parent theme \"{$parentthemename}\".");
                $parentthemename = 'raw';
                $parentthemeconfig = $getthemeconfig($parentthemename);
            }
            $currentthemename = $parentthemename;
            $themeconfig = $parentthemeconfig;
980

981
            foreach (get_object_vars($themeconfig) as $key => $value) {
982
                if (!isset($this->$key) || !$this->$key) {
983
984
985
                    $this->$key = $value;
                }
            }
986
987
988
989
990
            $this->templatedirs[] = get_config('docroot') . 'theme/' . $currentthemename . '/templates/';
            $this->inheritance[]  = $currentthemename;
            if (!isset($themeconfig->parent)) {
                $themeconfig->parent = 'raw';
            }