web.php 147 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
84
85
86
87
88
    // 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 . '/';
                $headers[] = '<script type="text/javascript">var fakewwwroot = ' . json_encode($fakewwwroot) . ';</script>';
            }
        }
    }

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
111
    $headers[] = '<script type="text/javascript">$j=jQuery;</script>';

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
125
                    $javascript_array[] = $jsroot . 'tinymce/tinymce.js';
                    $stylesheets = array_merge($stylesheets, array_reverse(array_values($THEME->get_url('style/tinymceskin.css', true))));
126
                    $content_css = json_encode($THEME->get_url('style/tinymce.css'));
127
128
                    $language = current_language();
                    $language = substr($language, 0, ((substr_count($language, '_') > 0) ? 5 : 2));
129
                    if ($language != 'en' && !file_exists(get_config('docroot') . 'js/tinymce/langs/' . $language . '.js')) {
130
131
132
133
134
135
136
137
                        // 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';
                            }
138
                        }
139
140
                    }
                    $extrasetup = isset($extraconfig['tinymcesetup']) ? $extraconfig['tinymcesetup'] : '';
141
                    $extramceconfig = isset($extraconfig['tinymceconfig']) ? $extraconfig['tinymceconfig'] : '';
142

143
144
145
146
147
148
149
150
151
152
153
                    // Check whether to make the spellchecker available
                    $aspellpath = get_config('pathtoaspell');
                    if ($aspellpath && file_exists($aspellpath) && is_executable($aspellpath)) {
                        $spellchecker = ',spellchecker';
                        $spellchecker_config = "gecko_spellcheck : false, spellchecker_rpc_url : \"{$jsroot}tinymce/plugins/spellchecker/rpc.php\",";
                    }
                    else {
                        $spellchecker = '';
                        $spellchecker_config = 'gecko_spellcheck : true,';
                    }

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

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

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

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

191
                    $headers[] = <<<EOF
192
193
<script type="text/javascript">
tinyMCE.init({
194
195
    {$tinymceconfig}
    schema: 'html4',
196
    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]",
197
    urlconverter_callback : "custom_urlconvert",
198
    language: '{$language}',
199
    directionality: "{$tinymce_langdir}",
200
    content_css : {$content_css},
201
    remove_script_host: false,
202
    relative_urls: false,
203
    {$extramceconfig}
204
    setup: function(ed) {
205
        ed.on('init', function(ed) {
206
            if (typeof(editor_to_focus) == 'string' && ed.editorId == editor_to_focus) {
207
208
209
                ed.focus();
            }
        });
210
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
        {$extrasetup}
215
    }
216
});
217
function custom_urlconvert (u, n, e) {
218
219
    // Don't convert the url on the skype status buttons.
    if (u.indexOf('skype:') == 0) {
220
      return u;
221
222
    }
    var t = tinyMCE.activeEditor, s = t.settings;
223

224
225
    // 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)
226
227
      return u;

228
229
    // Convert to relative
    if (s.relative_urls)
230
231
      return t.documentBaseURI.toRelative(u);

232
233
    // Convert to absolute
    u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
234

235
    return u;
236
}
237
238
239
</script>

EOF;
240
241
242
243
244
245
246
                    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]);
247
                }
248
            }
249

250
251
252
253
            // If any page adds jquery explicitly, remove it from the list
            if (($key = array_search('jquery', $check)) !== false) {
                unset($check[$key]);
            }
254
        }
255
    }
256
257
258
259
260
261
262
263
    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]);
        }
    }
264

265
    if (get_config('developermode') & DEVMODE_UNPACKEDJS) {
266
        $javascript_array[] = $jsroot . 'MochiKit/MochiKit.js';
267
268
269
270
        $javascript_array[] = $jsroot . 'MochiKit/Position.js';
        $javascript_array[] = $jsroot . 'MochiKit/Color.js';
        $javascript_array[] = $jsroot . 'MochiKit/Visual.js';
        $javascript_array[] = $jsroot . 'MochiKit/DragAndDrop.js';
271
        $javascript_array[] = $jsroot . 'MochiKit/Format.js';
272
273
274
275
    }
    else {
        $javascript_array[] = $jsroot . 'MochiKit/Packed.js';
    }
Martyn Smith's avatar
Martyn Smith committed
276
    $javascript_array[] = $jsroot . 'keyboardNavigation.js';
277

278
    $strings = array();
279
280
281
282
283
284
285
286
287
    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);
        }
288
289
    }

290
    $jsstrings = jsstrings();
Martyn Smith's avatar
Martyn Smith committed
291
    $themepaths = themepaths();
292

Richard Mansfield's avatar
Richard Mansfield committed
293
    foreach ($javascript as $jsfile) {
294
295
296
297
        // 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.
298
        if (strpos($jsfile, '/') === false) {
299
            $javascript_array[] = $jsroot . $jsfile . '.js';
300
            if (isset($jsstrings[$jsfile])) {
301
302
303
                foreach ($jsstrings[$jsfile] as $section => $tags) {
                    foreach ($tags as $tag) {
                        $strings[$tag] = get_raw_string($tag, $section);
304
305
306
                    }
                }
            }
Martyn Smith's avatar
Martyn Smith committed
307
308
            if (isset($themepaths[$jsfile])) {
                foreach ($themepaths[$jsfile] as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
309
                    $theme_list[$themepath] = $THEME->get_url($themepath);
Martyn Smith's avatar
Martyn Smith committed
310
311
                }
            }
312
        }
313
        else if (stripos($jsfile, 'http://') === false && stripos($jsfile, 'https://') === false) {
314
            // A local .js file with a fully specified path
315
            $javascript_array[] = $wwwroot . $jsfile;
316
            // If $jsfile is from a plugin (i.e. plugintype/pluginname/js/foo.js)
Aaron Wells's avatar
Aaron Wells committed
317
            // Then get js strings from static function jsstrings in plugintype/pluginname/lib.php
318
319
320
321
            $bits = explode('/', $jsfile);
            if (count($bits) == 4) {
                safe_require($bits[0], $bits[1]);
                $pluginclass = generate_class_name($bits[0], $bits[1]);
322
                $name = substr($bits[3], 0, strpos($bits[3], '.js'));
323
324
                if (is_callable(array($pluginclass, 'jsstrings'))) {
                    $tempstrings = call_static_method($pluginclass, 'jsstrings', $name);
325
326
327
                    foreach ($tempstrings as $section => $tags) {
                        foreach ($tags as $tag) {
                            $strings[$tag] = get_raw_string($tag, $section);
328
329
                        }
                    }
Richard Mansfield's avatar
Richard Mansfield committed
330
                }
331
332
333
334
335
336
337
338
339
                if (is_callable(array($pluginclass, 'jshelp'))) {
                    $tempstrings = call_static_method($pluginclass, 'jshelp', $name);
                    foreach ($tempstrings as $section => $tags) {
                        foreach ($tags as $tag) {
                            $strings[$tag . '.help'] = get_help_icon($bits[0], $bits[1], null, null,
                                                                     null, $tag);
                        }
                    }
                }
Martyn Smith's avatar
Martyn Smith committed
340
341
342
                if (is_callable(array($pluginclass, 'themepaths'))) {
                    $tmpthemepaths = call_static_method($pluginclass, 'themepaths', $name);
                    foreach ($tmpthemepaths as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
343
                        $theme_list[$themepath] = $THEME->get_url($themepath);
Martyn Smith's avatar
Martyn Smith committed
344
345
                    }
                }
Martyn Smith's avatar
Martyn Smith committed
346
            }
Martyn Smith's avatar
Martyn Smith committed
347
        }
348
349
350
351
        else {
            // A remote .js file
            $javascript_array[] = $jsfile;
        }
352
    }
353
354

    $javascript_array[] = $jsroot . 'mahara.js';
355
    $javascript_array[] = $jsroot . 'formchangechecker.js';
356
    if (get_config('developermode') & DEVMODE_DEBUGJS) {
357
        $javascript_array[] = $jsroot . 'debug.js';
358
    }
359

360
361
362
    foreach ($jsstrings['mahara'] as $section => $tags) {
        foreach ($tags as $tag) {
            $strings[$tag] = get_raw_string($tag, $section);
363
364
        }
    }
365
366
    if (isset($extraconfig['themepaths']) && is_array($extraconfig['themepaths'])) {
        foreach ($extraconfig['themepaths'] as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
367
            $theme_list[$themepath] = $THEME->get_url($themepath);
368
369
        }
    }
370

371
    $stringjs = '<script type="text/javascript">';
372
    $stringjs .= 'var strings = ' . json_encode($strings) . ';';
373
    $stringjs .= "\nfunction plural(n) { return " . get_raw_string('pluralrule', 'langconfig') . "; }\n";
374
375
    $stringjs .= '</script>';

376
    // stylesheet set up - if we're in a plugin also get its stylesheet
377
    $stylesheets = array_merge($stylesheets, array_reverse(array_values($THEME->get_url('style/style.css', true))));
378
    if (defined('SECTION_PLUGINTYPE') && defined('SECTION_PLUGINNAME') && SECTION_PLUGINTYPE != 'core') {
Nigel McNie's avatar
Nigel McNie committed
379
        if ($pluginsheets = $THEME->get_url('style/style.css', true, SECTION_PLUGINTYPE . '/' . SECTION_PLUGINNAME)) {
380
381
382
            $stylesheets = array_merge($stylesheets, array_reverse($pluginsheets));
        }
    }
383
384

    if ($adminsection = in_admin_section()) {
385
386
387
        if ($adminsheets = $THEME->get_url('style/admin.css', true)) {
            $stylesheets = array_merge($stylesheets, array_reverse($adminsheets));
        }
388
    }
389

390
    if (get_config('developermode') & DEVMODE_DEBUGCSS) {
391
392
        $stylesheets[] = get_config('wwwroot') . 'theme/debug.css';
    }
393

394
395
396
    // look for extra stylesheets
    if (isset($extraconfig['stylesheets']) && is_array($extraconfig['stylesheets'])) {
        foreach ($extraconfig['stylesheets'] as $extrasheet) {
397
            if ($sheets = $THEME->get_url($extrasheet, true)) {
Nigel McNie's avatar
Nigel McNie committed
398
                $stylesheets = array_merge($stylesheets, array_reverse(array_values($sheets)));
399
400
401
            }
        }
    }
402
403
404
    if ($sheets = $THEME->additional_stylesheets()) {
        $stylesheets = array_merge($stylesheets, $sheets);
    }
405

406
407
408
409
410
411
412
413
    // 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));
    }

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

417
    // Include rtl.css for right-to-left langs
418
    if ($langdirection == 'rtl') {
419
        $smarty->assign('LANGDIRECTION', 'rtl');
420
421
422
423
424
        if ($rtlsheets = $THEME->get_url('style/rtl.css', true)) {
            $stylesheets = array_merge($stylesheets, array_reverse($rtlsheets));
        }
    }

425
    $smarty->assign('STRINGJS', $stringjs);
426
    $stylesheets = append_version_number($stylesheets);
427
    $smarty->assign('STYLESHEETLIST', $stylesheets);
428
429
    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
430
        $smarty->assign('THEMELIST', json_encode(array_merge((array)json_decode($smarty->get_template_vars('THEMELIST')),  $theme_list)));
431
    }
432

433
434
435
436
437
438
439
440
441
442
443
444
445
    $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)
446
447
                    || (empty($USER->accountprefs) && $i->theme == $THEME->basename && $USER->institutiontheme->institutionname == $i->institution)) {
                    $dropdownmenu = $i->dropdownmenu;
448
449
450
451
452
453
454
455
                }
            }
        }
    }

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

456
457
    if ($dropdownmenu) {
        $smarty->assign('DROPDOWNMENU', $dropdownmenu);
458
        $javascript_array[] = $jsroot . 'dropdown-nav.js';
459
    }
460

461
    $smarty->assign('MOBILE', $SESSION->get('mobile'));
462
    $smarty->assign('HANDHELD_DEVICE', $SESSION->get('handheld_device'));
463

464
465
466
467
    $sitename = get_config('sitename');
    if (!$sitename) {
       $sitename = 'Mahara';
    }
468
    $smarty->assign('sitename', $sitename);
469
    $sitelogo = $THEME->header_logo();
470
    $sitelogo = append_version_number($sitelogo);
471
    $smarty->assign('sitelogo', $sitelogo);
472
473
    $smarty->assign('sitelogo4facebook', $THEME->facebook_logo());
    $smarty->assign('sitedescription4facebook', get_string('facebookdescription', 'mahara'));
474

Martyn Smith's avatar
Martyn Smith committed
475
    if (defined('TITLE')) {
476
        $smarty->assign('PAGETITLE', TITLE . ' - ' . $sitename);
477
        $smarty->assign('heading', TITLE);
Martyn Smith's avatar
Martyn Smith committed
478
479
    }
    else {
480
        $smarty->assign('PAGETITLE', $sitename);
Martyn Smith's avatar
Martyn Smith committed
481
482
    }

483
    $smarty->assign('PRODUCTIONMODE', get_config('productionmode'));
484
    if (function_exists('local_header_top_content')) {
485
        $sitetop = (isset($sitetop) ? $sitetop : '') . local_header_top_content();
486
487
488
    }
    if (isset($sitetop)) {
        $smarty->assign('SITETOP', $sitetop);
489
    }
490
491
492
    if (defined('PUBLIC')) {
        $smarty->assign('PUBLIC', true);
    }
493
494
495
    if (defined('ADMIN')) {
        $smarty->assign('ADMIN', true);
    }
496
497
498
    if (defined('INSTITUTIONALADMIN')) {
        $smarty->assign('INSTITUTIONALADMIN', true);
    }
499
500
501
502
503
504
    if (defined('STAFF')) {
        $smarty->assign('STAFF', true);
    }
    if (defined('INSTITUTIONALSTAFF')) {
        $smarty->assign('INSTITUTIONALSTAFF', true);
    }
505

506
    $smarty->assign('LOGGEDIN', $USER->is_logged_in());
507
508
509
510
511
512
513
    $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);
514
    if ($USER->is_logged_in()) {
515
        global $SELECTEDSUBNAV; // It's evil, but rightnav & mainnav stuff are now in different templates.
516
        $smarty->assign('MAINNAV', main_nav());
517
        $mainnavsubnav = $SELECTEDSUBNAV;
518
        $smarty->assign('RIGHTNAV', right_nav());
519
520
521
522
523
524
525
526
527
        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);
        }
528
    }
529
    else {
530
        $smarty->assign('languageform', $langselectform);
531
    }
532
    $smarty->assign('FOOTERMENU', footer_menu());
533

534
    $smarty->assign_by_ref('USER', $USER);
535
    $smarty->assign('SESSKEY', $USER->get('sesskey'));
536
    $smarty->assign('CC_ENABLED', get_config('cookieconsent_enabled'));
537
    $javascript_array = append_version_number($javascript_array);
538
    $smarty->assign_by_ref('JAVASCRIPT', $javascript_array);
539
    $smarty->assign('RELEASE', get_config('release'));
540
    $smarty->assign('SERIES', get_config('series'));
541
    $smarty->assign('CACHEVERSION', get_config('cacheversion'));
542
543
    $siteclosedforupgrade = get_config('siteclosed');
    if ($siteclosedforupgrade && get_config('disablelogin')) {
544
        $smarty->assign('SITECLOSED', 'logindisabled');
545
546
    }
    else if ($siteclosedforupgrade || get_config('siteclosedbyadmin')) {
547
        $smarty->assign('SITECLOSED', 'loginallowed');
548
    }
549

550
551
    if ((!isset($extraconfig['pagehelp']) || $extraconfig['pagehelp'] !== false)
        and $help = has_page_help()) {
552
553
554
        $smarty->assign('PAGEHELPNAME', $help[0]);
        $smarty->assign('PAGEHELPICON', $help[1]);
    }
555
    if (defined('GROUP')) {
556
        require_once('group.php');
557
558
559
560
561
562
        if ($group = group_current_group()) {
            $smarty->assign('GROUP', $group);
            if (!defined('NOGROUPMENU')) {
                $smarty->assign('SUBPAGENAV', group_get_menu_tabs());
                $smarty->assign('PAGEHEADING', $group->name);
            }
563
        }
564
    }
565

Martyn Smith's avatar
Martyn Smith committed
566
    // ---------- sideblock stuff ----------
567
568
    $sidebars = !isset($extraconfig['sidebars']) || $extraconfig['sidebars'] !== false;
    if ($sidebars && !defined('INSTALLER') && (!defined('MENUITEM') || substr(MENUITEM, 0, 5) != 'admin')) {
569
        if (get_config('installed') && !$adminsection) {
570
571
572
            $data = site_menu();
            if (!empty($data)) {
                $smarty->assign('SITEMENU', site_menu());
573
                $sideblocks[] = array(
574
                    'name'   => 'linksandresources',
575
576
577
578
579
580
                    'weight' => 10,
                    'data'   => $data,
                );
            }
        }

581
582
        if ($USER->is_logged_in() && defined('MENUITEM') &&
            (substr(MENUITEM, 0, 11) == 'myportfolio' || substr(MENUITEM, 0, 7) == 'content')) {
583
            if (get_config('showselfsearchsideblock')) {
584
                $sideblocks[] = array(
585
586
587
588
589
590
                    'name'   => 'selfsearch',
                    'weight' => 0,
                    'data'   => array(),
                );
            }
            if (get_config('showtagssideblock')) {
591
                $sideblocks[] = array(
592
593
594
595
596
597
                    'name'   => 'tags',
                    'id'     => 'sb-tags',
                    'weight' => 0,
                    'data'   => tags_sideblock(),
                );
            }
Clare Lenihan's avatar
Clare Lenihan committed
598
        }
Clare Lenihan's avatar
Clare Lenihan committed
599

600
        if ($USER->is_logged_in() && !$adminsection) {
601
            $sideblocks[] = array(
602
                'name'   => 'profile',
603
                'id'     => 'sb-profile',
604
605
606
                'weight' => -20,
                'data'   => profile_sideblock()
            );
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
            $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) {
622
                $sideblocks[] = array(
623
                    'name'   => 'onlineusers',
624
                    'id'     => 'sb-onlineusers',
625
626
627
628
                    'weight' => -10,
                    'data'   => onlineusers_sideblock(),
                );
            }
629
            if (get_config('showprogressbar') && $USER->get_account_preference('showprogressbar')) {
630
                $sideblocks[] = array(
631
632
                    'name'   => 'progressbar',
                    'id'     => 'sb-progressbar',
633
                    'weight' => -8,
634
635
636
637
638
639
                    'data'   => progressbar_sideblock(),
                );
            }
        }

        if ($USER->is_logged_in() && $adminsection && defined('SECTION_PAGE') && SECTION_PAGE == 'progressbar') {
640
            $sideblocks[] = array(
641
642
                'name'   => 'progressbar',
                'id'     => 'sb-progressbar',
643
                'weight' => -8,
644
645
                'data'   => progressbar_sideblock(true),
            );
646
        }
Martyn Smith's avatar
Martyn Smith committed
647

648
        if (!$USER->is_logged_in() && !(get_config('siteclosed') && get_config('disablelogin'))) {
649
            $sideblocks[] = array(
650
651
                'name'   => 'login',
                'weight' => -10,
652
                'id'     => 'sb-loginbox',
653
654
655
                'data'   => array(
                    'loginform' => auth_generate_login_form(),
                ),
656
657
            );
        }
658

659
660
661
        if (get_config('enablenetworking')) {
            require_once(get_config('docroot') .'api/xmlrpc/lib.php');
            if ($USER->is_logged_in() && $ssopeers = get_service_providers($USER->authinstance)) {
662
                $sideblocks[] = array(
663
664
665
666
667
                    'name'   => 'ssopeers',
                    'weight' => 1,
                    'data'   => $ssopeers,
                );
            }
Martyn Smith's avatar
Martyn Smith committed
668
669
        }

670
671
        if (isset($extraconfig['sideblocks']) && is_array($extraconfig['sideblocks'])) {
            foreach ($extraconfig['sideblocks'] as $sideblock) {
672
                $sideblocks[] = $sideblock;
673
674
            }
        }
Martyn Smith's avatar
Martyn Smith committed
675

676
        // local_sideblocks_update allows sites to customise the sideblocks by munging the $sideblocks array.
677
        if (function_exists('local_sideblocks_update')) {
678
            local_sideblocks_update($sideblocks);
679
680
        }

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

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

689
        $smarty->assign('userauthinstance', $SESSION->get('authinstance'));
690
        $smarty->assign('MNETUSER', $SESSION->get('mnetuser'));
691
        $smarty->assign('SIDEBLOCKS', $sideblocks);
692
        $smarty->assign('SIDEBARS', $sidebars);
693

694
695
    }

696
697
698
699
700
    if (is_array($HEADDATA) && !empty($HEADDATA)) {
        $headers = array_merge($HEADDATA, $headers);
    }
    $smarty->assign_by_ref('HEADERS', $headers);

701
702
    if ($USER->get('parentuser')) {
        $smarty->assign('USERMASQUERADING', true);
703
        $smarty->assign('masqueradedetails', get_string('youaremasqueradingas', 'mahara', display_name($USER)));
704
705
        $smarty->assign('becomeyouagain',
            ' <a href="' . hsc($wwwroot) . 'admin/users/changeuser.php?restore=1">'
706
            . get_string('becomeadminagain', 'admin', hsc($USER->get('parentuser')->name))
707
            . '</a>');
708
    }
Martyn Smith's avatar
Martyn Smith committed
709

710
711
    // Define additional html content
    if (get_config('installed')) {
712
713
714
715
        $additionalhtmlitems = array(
            'ADDITIONALHTMLHEAD'      => get_config('additionalhtmlhead'),
            'ADDITIONALHTMLTOPOFBODY' => get_config('additionalhtmltopofbody'),
            'ADDITIONALHTMLFOOTER'    => get_config('additionalhtmlfooter')
716
717
        );
        if ($additionalhtmlitems) {
718
719
            foreach ($additionalhtmlitems as $name=>$content) {
                $smarty->assign($name, $content);
720
721
722
            }
        }
    }
723
724
725
726
727
728

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

732
733
734
735

/**
 * Manages theme configuration.
 *
Aaron Wells's avatar
Aaron Wells committed
736
 * Does its best to give the user _a_ theme, even if it's not the theme they
737
738
739
740
741
742
743
744
745
 * 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 = '';

746
747
748
749
750
    /**
     * A user may have had the header logo overridden by an institution
     */
    public $headerlogo;

751
752
753
754
755
    /**
     * Additional stylesheets to display after the basename theme's stylesheets
     */
    public $addedstylesheets;

756
757
758
759
760
761
762
763
764
765
    /**
     * A human-readable version of the theme name
     */
    public $displayname = '';

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

766
767
768
769
770
771
772
773
774
775
    /**
     * Directories where to look for templates by default
     */
    public $templatedirs = array();

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

776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
    /**
     * 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
810
811
     * 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
812
813
     * that's a user ID and ask for the theme for that user.
     *
Aaron Wells's avatar
Aaron Wells committed
814
     * If the theme they want doesn't exist, the object is initialised for the
815
816
817
818
819
820
821
822
     * 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;
823
            $themedata = null;
824
825
        }
        else if ($arg instanceof User) {
826
            $themedata = $arg->get_themedata();
827
828
829
830
        }
        else if (is_int($arg)) {
            $user = new User();
            $user->find_by_id($arg);
831
            $themedata = $user->get_themedata();
832
833
834
835
836
        }
        else {
            throw new SystemException("Argument to Theme::__construct was not a theme name, user object or user ID");
        }

837
838
839
840
        if (isset($themedata)) {
            $themename = $themedata->basename;
        }

841
        if (empty($themename)) {
842
            // Theme to show to when no theme has been suggested
843
844
845
            if (!$themename = get_config('theme')) {
                $themename = 'raw';
            }
846
        }
847
848

        // check the validity of the name
849
        if (!$this->name_is_valid($themename)) {
850
851
            throw new SystemException("Theme name is in invalid form: '$themename'");
        }
852
853

        $this->init_theme($themename, $themedata);
854
855
856
857
858
859
860
861
    }

    /**
     * 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);
862
863
864
865
866
    }

    /**
     * Given a theme name, reads in all config and sets fields on this object
     */
867
    private function init_theme($themename, $themedata) {
868
869
870
871
        $this->basename = $themename;

        $themeconfigfile = get_config('docroot') . 'theme/' . $this->basename . '/themeconfig.php';
        if (!is_readable($themeconfigfile)) {
Aaron Wells's avatar
Aaron Wells committed
872
            // We can safely assume that the default theme is installed, users
873
874
875
876
877
878
879
880
881
882
883
884
            // should never be able to remove it
            $this->basename = 'default';
            $themeconfigfile = get_config('docroot') . 'theme/default/themeconfig.php';
        }

        require($themeconfigfile);

        foreach (get_object_vars($theme) as $key => $value) {
            $this->$key = $value;
        }

        if (!isset($this->displayname)) {
885
            $this->displayname = $this->basename;
886
887
888
889
890
        }
        if (!isset($theme->parent) || !$theme->parent) {
            $theme->parent = 'raw';
        }

891
892
893
894
        // Local theme overrides come first
        $this->templatedirs[] = get_config('docroot') . 'local/theme/templates/';

        // Then the current theme
895
896
897
        $this->templatedirs[] = get_config('docroot') . 'theme/' . $this->basename . '/templates/';
        $this->inheritance[]  = $this->basename;

898

Aaron Wells's avatar
Aaron Wells committed
899
        // Now go through the theme hierarchy assigning variables from the
900
901
902
903
904
905
906
        // parent themes
        $currenttheme = $this->basename;
        while ($currenttheme != 'raw') {
            $currenttheme = isset($theme->parent) ? $theme->parent : 'raw';
            $parentconfigfile = get_config('docroot') . 'theme/' . $currenttheme . '/themeconfig.php';
            require($parentconfigfile);
            foreach (get_object_vars($theme) as $key => $value) {
907
                if (!isset($this->$key) || !$this->$key) {
908
909
910
                    $this->$key = $value;
                }
            }
911
912
            $this->templatedirs[] = get_config('docroot') . 'theme/' . $currenttheme . '/templates/';
            $this->inheritance[]  = $currenttheme;
913
        }
914
915
916
917

        if (!empty($themedata->headerlogo)) {
            $this->headerlogo = $themedata->headerlogo;
        }
918
919
920
        if (!empty($themedata->stylesheets)) {
            $this->addedstylesheets = $themedata->stylesheets;
        }
921
922
    }

923
    /**
924
925
926
927
928
929
930
931
     * Get the URL of a particular theme asset (i.e. an image or CSS file). Checks first for a copy
     * in /local/theme/static, then in the current theme, then this theme's parent, grandparent, etc.
     *
     * @param string $filename Relative path of the asset, e.g. 'images/newmail.png'
     * @param boolean $all Whether to return the first found copy of the asset, or all copies of it from all themes
     * in the hierarchy.
     * @param string $plugindirectory For if it's a plugin theme asset, e.g. 'artefact/file'
     * @return string|array The URL of the first match, or all matching ones, depending on $all
932
     */
933
934
    public function get_url($filename, $all=false, $plugindirectory='') {
        return $this->_get_path($filename, $all, $plugindirectory, get_config('wwwroot'));
935
936
    }

937
938
939
940
941
942
943
944
945
946
    /**
     * Get the full filesystem path of a particular theme asset (i.e. an image or CSS file). Checks first for a copy
     * in /local/theme/static, then in the current theme, then this theme's parent, grandparent, etc.
     *
     * @param string $filename Relative path of the asset, e.g. 'images/newmail.png'
     * @param boolean $all Whether to return the first found copy of the asset, or all copies it from all
     * themes in the hierarchy
     * @param string $plugindirectory For if it's a plugin theme asset, e.g. 'artefact/file'
     * @return string|array The full filesystem path of the first match, or of all matches, depending on $all
     */
947
948
    public function get_path($filename, $all=false, $plugindirectory='') {
        return $this->_get_path($filename, $all, $plugindirectory, get_config('docroot'));
949
950
    }

951
952
953
954
955
956
957
958
959
960
961
    /**
     * Internal function to return the path or URL of a particular theme asset. Relies on the fact that the URL
     * and the filesystem path are the same, except that one is prefaced by docroot and the other by wwwroot.
     *
     * @param string $filename Relative path of the asset, e.g. 'images/newmail.png'
     * @param boolean $all Whether to return the first found copy of the asset, or all copies it from all
     * themes in the hierarchy
     * @param string $plugindirectory For if it's a plugin theme asset, e.g. 'artefact/file'
     * @param string $returnprefix The part to put before the Mahara-relative path of the file. (i.e. docroot or wwwroot)
     * @return string|array The first match, or of all matches, depending on $all
     */
962
    private function _get_path($filename, $all, $plugindirectory, $returnprefix) {
Nigel McNie's avatar