web.php 146 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
145
146
147
                    // Check whether to make the spellchecker available
                    $aspellpath = get_config('pathtoaspell');
                    if ($aspellpath && file_exists($aspellpath) && is_executable($aspellpath)) {
                        $spellchecker = ',spellchecker';
148
                        $spellchecker_toolbar = '| spellchecker';
149
150
151
                        $spellchecker_config = "gecko_spellcheck : false, spellchecker_rpc_url : \"{$jsroot}tinymce/plugins/spellchecker/rpc.php\",";
                    }
                    else {
152
                        $spellchecker = $spellchecker_toolbar = '';
153
154
155
                        $spellchecker_config = 'gecko_spellcheck : true,';
                    }

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

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

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

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

193
                    $headers[] = <<<EOF
194
<script type="application/javascript">
195
tinyMCE.init({
196
197
    {$tinymceconfig}
    schema: 'html4',
198
    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]",
199
    urlconverter_callback : "custom_urlconvert",
200
    language: '{$language}',
201
    directionality: "{$tinymce_langdir}",
202
    content_css : {$content_css},
203
    remove_script_host: false,
204
    relative_urls: false,
205
    {$extramceconfig}
206
    setup: function(ed) {
207
        ed.on('init', function(ed) {
208
            if (typeof(editor_to_focus) == 'string' && ed.editorId == editor_to_focus) {
209
210
211
                ed.focus();
            }
        });
212
213
214
215
        ed.on('LoadContent', function(e) {
            // Hide all the 2nd/3rd row menu buttons
            jQuery('.mce-toolbar.mce-first').siblings().toggleClass('hidden');
        });
216
        {$extrasetup}
217
    }
218
});
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

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;
    }
}

function updateBlockConfigWidth(configblock, width) {
    var vpdim = getViewportDimensions();
    var w = Math.max(width, 500);
    var style = {
        'position': 'absolute',
        'z-index': 1
    };
    var lborder = parseFloat(getStyle(configblock, 'border-left-width'));
    var lpadding = parseFloat(getStyle(configblock, 'padding-left'));
    style.left = ((vpdim.w - w) / 2 - lborder - lpadding) + 'px';
    style.width = w + 'px';
    setStyle(configblock, style);
}

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

257
258
    // 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)
259
260
      return u;

261
262
    // Convert to relative
    if (s.relative_urls)
263
264
      return t.documentBaseURI.toRelative(u);

265
266
    // Convert to absolute
    u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
267

268
    return u;
269
}
270
271
272
</script>

EOF;
273
274
275
276
277
278
279
                    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]);
280
                }
281
            }
282

283
284
285
286
            // If any page adds jquery explicitly, remove it from the list
            if (($key = array_search('jquery', $check)) !== false) {
                unset($check[$key]);
            }
287
        }
288
    }
289
290
291
292
293
294
295
296
    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]);
        }
    }
297

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

311
    $strings = array();
312
313
314
315
316
317
318
319
320
    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);
        }
321
322
    }

323
    $jsstrings = jsstrings();
Martyn Smith's avatar
Martyn Smith committed
324
    $themepaths = themepaths();
325

Richard Mansfield's avatar
Richard Mansfield committed
326
    foreach ($javascript as $jsfile) {
327
328
329
330
        // 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.
331
        if (strpos($jsfile, '/') === false) {
332
            $javascript_array[] = $jsroot . $jsfile . '.js';
333
            if (isset($jsstrings[$jsfile])) {
334
335
336
                foreach ($jsstrings[$jsfile] as $section => $tags) {
                    foreach ($tags as $tag) {
                        $strings[$tag] = get_raw_string($tag, $section);
337
338
339
                    }
                }
            }
Martyn Smith's avatar
Martyn Smith committed
340
341
            if (isset($themepaths[$jsfile])) {
                foreach ($themepaths[$jsfile] as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
342
                    $theme_list[$themepath] = $THEME->get_url($themepath);
Martyn Smith's avatar
Martyn Smith committed
343
344
                }
            }
345
        }
346
        else if (stripos($jsfile, 'http://') === false && stripos($jsfile, 'https://') === false) {
347
            // A local .js file with a fully specified path
348
            $javascript_array[] = $wwwroot . $jsfile;
349
350
351
352
353
354
            // 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
355
            $bits = explode('/', $jsfile);
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
            $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'));
373
374
                if (is_callable(array($pluginclass, 'jsstrings'))) {
                    $tempstrings = call_static_method($pluginclass, 'jsstrings', $name);
375
376
377
                    foreach ($tempstrings as $section => $tags) {
                        foreach ($tags as $tag) {
                            $strings[$tag] = get_raw_string($tag, $section);
378
379
                        }
                    }
Richard Mansfield's avatar
Richard Mansfield committed
380
                }
381
382
383
384
                if (is_callable(array($pluginclass, 'jshelp'))) {
                    $tempstrings = call_static_method($pluginclass, 'jshelp', $name);
                    foreach ($tempstrings as $section => $tags) {
                        foreach ($tags as $tag) {
385
                            $strings[$tag . '.help'] = get_help_icon($plugintype, $pluginname, null, null,
386
387
388
389
                                                                     null, $tag);
                        }
                    }
                }
Martyn Smith's avatar
Martyn Smith committed
390
391
392
                if (is_callable(array($pluginclass, 'themepaths'))) {
                    $tmpthemepaths = call_static_method($pluginclass, 'themepaths', $name);
                    foreach ($tmpthemepaths as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
393
                        $theme_list[$themepath] = $THEME->get_url($themepath);
Martyn Smith's avatar
Martyn Smith committed
394
395
                    }
                }
Martyn Smith's avatar
Martyn Smith committed
396
            }
Martyn Smith's avatar
Martyn Smith committed
397
        }
398
399
400
401
        else {
            // A remote .js file
            $javascript_array[] = $jsfile;
        }
402
    }
403
404

    $javascript_array[] = $jsroot . 'mahara.js';
405
    $javascript_array[] = $jsroot . 'formchangechecker.js';
406
    if (get_config('developermode') & DEVMODE_DEBUGJS) {
407
        $javascript_array[] = $jsroot . 'debug.js';
408
    }
409

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

421
    $stringjs = '<script type="application/javascript">';
422
    $stringjs .= 'var strings = ' . json_encode($strings) . ';';
423
    $stringjs .= "\nfunction plural(n) { return " . get_raw_string('pluralrule', 'langconfig') . "; }\n";
424
425
    $stringjs .= '</script>';

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

    if ($adminsection = in_admin_section()) {
435
436
437
        if ($adminsheets = $THEME->get_url('style/admin.css', true)) {
            $stylesheets = array_merge($stylesheets, array_reverse($adminsheets));
        }
438
    }
439

440
    if (get_config('developermode') & DEVMODE_DEBUGCSS) {
441
442
        $stylesheets[] = get_config('wwwroot') . 'theme/debug.css';
    }
443

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

456
457
458
459
460
461
462
463
    // 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));
    }

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

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

475
    $smarty->assign('STRINGJS', $stringjs);
476
    $stylesheets = append_version_number($stylesheets);
477
    $smarty->assign('STYLESHEETLIST', $stylesheets);
478
479
    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
480
        $smarty->assign('THEMELIST', json_encode(array_merge((array)json_decode($smarty->get_template_vars('THEMELIST')),  $theme_list)));
481
    }
482

483
484
485
486
487
488
489
490
491
492
493
494
495
    $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)
496
497
                    || (empty($USER->accountprefs) && $i->theme == $THEME->basename && $USER->institutiontheme->institutionname == $i->institution)) {
                    $dropdownmenu = $i->dropdownmenu;
498
499
500
501
502
503
504
505
                }
            }
        }
    }

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

506
507
    if ($dropdownmenu) {
        $smarty->assign('DROPDOWNMENU', $dropdownmenu);
508
        $javascript_array[] = $jsroot . 'dropdown-nav.js';
509
    }
510

511
    $smarty->assign('MOBILE', $SESSION->get('mobile'));
512
    $smarty->assign('HANDHELD_DEVICE', $SESSION->get('handheld_device'));
513

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

Martyn Smith's avatar
Martyn Smith committed
525
    if (defined('TITLE')) {
526
        $smarty->assign('PAGETITLE', TITLE . ' - ' . $sitename);
527
        $smarty->assign('heading', TITLE);
Martyn Smith's avatar
Martyn Smith committed
528
529
    }
    else {
530
        $smarty->assign('PAGETITLE', $sitename);
Martyn Smith's avatar
Martyn Smith committed
531
532
    }

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

556
    $smarty->assign('LOGGEDIN', $USER->is_logged_in());
557
558
559
560
561
562
563
    $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);
564
    if ($USER->is_logged_in()) {
565
        global $SELECTEDSUBNAV; // It's evil, but rightnav & mainnav stuff are now in different templates.
566
        $smarty->assign('MAINNAV', main_nav());
567
        $mainnavsubnav = $SELECTEDSUBNAV;
568
        $smarty->assign('RIGHTNAV', right_nav());
569
570
571
572
573
574
575
576
577
        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);
        }
578
    }
579
    else {
580
        $smarty->assign('languageform', $langselectform);
581
    }
582
    $smarty->assign('FOOTERMENU', footer_menu());
583

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

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

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

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

650
        if ($USER->is_logged_in() && !$adminsection) {
651
            $sideblocks[] = array(
652
                'name'   => 'profile',
653
                'id'     => 'sb-profile',
654
655
656
                'weight' => -20,
                'data'   => profile_sideblock()
            );
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
            $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) {
672
                $sideblocks[] = array(
673
                    'name'   => 'onlineusers',
674
                    'id'     => 'sb-onlineusers',
675
676
677
678
                    'weight' => -10,
                    'data'   => onlineusers_sideblock(),
                );
            }
679
            if (get_config('showprogressbar') && $USER->get_account_preference('showprogressbar')) {
680
                $sideblocks[] = array(
681
682
                    'name'   => 'progressbar',
                    'id'     => 'sb-progressbar',
683
                    'weight' => -8,
684
685
686
687
688
689
                    'data'   => progressbar_sideblock(),
                );
            }
        }

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

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

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

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

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

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

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

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

744
745
    }

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

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

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

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

782
783
784
785

/**
 * Manages theme configuration.
 *
Aaron Wells's avatar
Aaron Wells committed
786
 * Does its best to give the user _a_ theme, even if it's not the theme they
787
788
789
790
791
792
793
794
795
 * 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 = '';

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

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

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

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

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

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

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
854
855
856
857
858
859
    /**
     * 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';


    /**
     * Initialises a theme object based on the theme 'hint' passed.
     *
Aaron Wells's avatar
Aaron Wells committed
860
861
     * 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
862
863
     * that's a user ID and ask for the theme for that user.
     *
Aaron Wells's avatar
Aaron Wells committed
864
     * If the theme they want doesn't exist, the object is initialised for the
865
866
867
868
869
870
871
872
     * 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;
873
            $themedata = null;
874
875
        }
        else if ($arg instanceof User) {
876
            $themedata = $arg->get_themedata();
877
878
879
880
        }
        else if (is_int($arg)) {
            $user = new User();
            $user->find_by_id($arg);
881
            $themedata = $user->get_themedata();
882
883
884
885
886
        }
        else {
            throw new SystemException("Argument to Theme::__construct was not a theme name, user object or user ID");
        }

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

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

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

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

    /**
     * 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);
912
913
914
915
916
    }

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

919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
        // 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
935
            // We can safely assume that the default theme is installed, users
936
            // should never be able to remove it
937
938
            $themename ='default';
            $themeconfig = $getthemeconfig($themename);
939
940
        }

941
        $this->basename = $themename;
942

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

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

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

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

958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973