web.php 177 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
 * @copyright  (C) portions from Moodle, (C) Martin Dougiamas http://dougiamas.com
 */

12
use Mahara\Dwoo_Mahara as Dwoo_Mahara;
13
14
15
defined('INTERNAL') || die();


16
function smarty_core() {
17
18
    require_once(__DIR__ . '/dwoo/vendor/autoload.php');
    require_once(__DIR__ . '/dwoo/mahara/Dwoo_Mahara.php');
19

20
    return new Dwoo_Mahara();
21
22
23
}


24
25
26
27
28
29
30
31
32

/**
 * Function to set an optional page icon. Mahara uses fontawesome for icons by default,
 * (http://fortawesome.github.io/Font-Awesome/icons/) but this can be overridden at the theme
 * level by supplying a different icon font + css.
 *
 * @param Smarty | an initialized smarty object
 * @param String | the name of the icon to include (eg "icon-university")
 */
Pat Kira's avatar
Pat Kira committed
33
function setpageicon($smarty, $icon) {
34
35
36
37
38
39
    $smarty->assign('pageicon', 'icon ' . $icon);
}



/**
40
41
 * Helper function (called by smarty()) to determine what stylesheets to include
 * on the page (based on constants, global variables, and $extraconfig)
42
 *
43
44
 * @param $stylesheets Stylesheets we already know we're going to need
 * @param $extraconfig Extra configuration passed to smarty()
45
46
47
 * @return array
 */

Pat Kira's avatar
Pat Kira committed
48
function get_stylesheets_for_current_page($stylesheets, $extraconfig) {
49
50
51
52
53
54
55

    global $USER, $SESSION, $THEME, $HEADDATA, $langselectform;

    // stylesheet set up - if we're in a plugin also get its stylesheet
    $allstylesheets = $THEME->get_url('style/style.css', true);

    // determine if we want to include the parent css
Pat Kira's avatar
Pat Kira committed
56
    if (isset($THEME->overrideparentcss) && $THEME->overrideparentcss && $THEME->parent) {
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
        unset($allstylesheets[$THEME->parent]);
    }

    $stylesheets = array_merge($stylesheets, array_reverse(array_values($allstylesheets)));

    if (defined('SECTION_PLUGINTYPE') && defined('SECTION_PLUGINNAME') && SECTION_PLUGINTYPE != 'core') {
        if ($pluginsheets = $THEME->get_url('style/style.css', true, SECTION_PLUGINTYPE . '/' . SECTION_PLUGINNAME)) {
            $stylesheets = array_merge($stylesheets, array_reverse($pluginsheets));
        }
    }

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

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

    // look for extra stylesheets
    if (isset($extraconfig['stylesheets']) && is_array($extraconfig['stylesheets'])) {
        foreach ($extraconfig['stylesheets'] as $extrasheet) {
            if ($sheets = $THEME->get_url($extrasheet, true)) {
                $stylesheets = array_merge($stylesheets, array_reverse(array_values($sheets)));
            }
        }
    }
86
87
88
89

    // Only add additional stylesheets when configurable theme is set.
    if ($THEME->basename == 'custom') {
        $sheets = $THEME->additional_stylesheets();
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
        $stylesheets = array_merge($stylesheets, $sheets);
    }

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

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

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

    $stylesheets = append_version_number($stylesheets);

    return $stylesheets;
}

116
117
118
119
120
121
122
123
124
/**
* True if we are not in admin, institution or admin section
*/
function user_personal_section() {
    $usersection = !defined('ADMIN') && !defined('STAFF') && !defined('INSTITUTIONALADMIN') &&
        !defined('INSTITUTIONALSTAFF') && !defined('GROUP') && !defined('CREATEGROUP');

    return $usersection ? 1 : 0;
}
Pat Kira's avatar
Pat Kira committed
125

126
127
128
129
/**
 * This function creates a Smarty object and sets it up for use within our
 * podclass app, setting up some variables.
 *
130
131
 * WARNING: If you are using pieforms, set them up BEFORE calling this function.
 *
132
133
 * The variables that it sets up are:
 *
134
 * - WWWROOT: The base url for the Mahara system
135
136
137
138
139
 * - 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).
140
141
 * - PUBLIC: Set true if this page is a public page
 * - MAINNAV: Array defining the main navigation
142
 *
143
 * @param $javascript A list of javascript includes.  Each include should be just
144
 *                    the name of a file, and reside in js/{filename}
145
146
147
 * @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.
148
 * @return Dwoo_Mahara
149
 */
150

151
152


153
function smarty($javascript = array(), $headers = array(), $pagestrings = array(), $extraconfig = array()) {
154
    global $USER, $SESSION, $THEME, $HEADDATA, $langselectform, $CFG, $viewid;
155
156
157
158
159
160
161
162
163
164
165

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

166
    $sideblocks = array();
167
168
169
170
171
    // 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();
    }
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
    $sideblock_menu = array();
    // Fetch all the core side blocks now to avoid any 'set headdata' before smarty_core() problems
    $authgenerateloginform = auth_generate_login_form();
    $isloginblockvisible = !$USER->is_logged_in() && !get_config('siteclosedforupgrade') && get_config('showloginsideblock');
    $loginsideblock = array(
        'name'   => 'login',
        'weight' => -10,
        'id'     => 'sb-loginbox',
        'data'   => array('loginform' => $authgenerateloginform),
        'visible' => $isloginblockvisible,
        'template' => 'sideblocks/login.tpl',
        'smarty' => array('SHOWLOGINBLOCK' => $isloginblockvisible),
    );
    sideblock_template($loginsideblock, $sideblock_menu);
    sideblock_template(site_menu(), $sideblock_menu);
    sideblock_template(tags_sideblock(), $sideblock_menu);
    sideblock_template(selfsearch_sideblock(), $sideblock_menu);
    sideblock_template(profile_sideblock(), $sideblock_menu);
    sideblock_template(onlineusers_sideblock(), $sideblock_menu);
    sideblock_template(progressbar_sideblock(), $sideblock_menu);
    sideblock_template(ssopeer_sideblock(), $sideblock_menu);
    sideblock_template(quota_sideblock(), $sideblock_menu);
    if (isset($extraconfig['sideblocks']) && is_array($extraconfig['sideblocks'])) {
        foreach ($extraconfig['sideblocks'] as $sideblock) {
            sideblock_template($sideblock, $sideblock_menu);
        }
    }
    // local_sideblocks_update allows sites to customise the sideblocks by munging the $sideblock_menu array.
    if (function_exists('local_sideblocks_update')) {
        local_sideblocks_update($sideblock_menu);
    }
    // Remove those that are not visible before displaying
    foreach ($sideblock_menu as $sbk => $sbv) {
        if (empty($sbv['visible'])) {
            unset($sideblock_menu[$sbk]);
        }
Gregor Anzelj's avatar
Gregor Anzelj committed
208
    }
209
210
    usort($sideblock_menu, "sort_menu_by_weight");

211
212
    $smarty = smarty_core();

213
    $wwwroot = get_config('wwwroot');
Aaron Wells's avatar
Aaron Wells committed
214
    // NOTE: not using jswwwroot - it seems to wreck image paths if you
215
    // drag them around the wysiwyg editor
216
    $jswwwroot = json_encode($wwwroot);
Martyn Smith's avatar
Martyn Smith committed
217

218
219
220
221
222
223
224
225
226
227
    // 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 . '/';
228
                $headers[] = '<script>var fakewwwroot = ' . json_encode($fakewwwroot) . ';</script>';
229
230
231
232
            }
        }
    }

Martyn Smith's avatar
Martyn Smith committed
233
    $theme_list = array();
234
    $adminsection = in_admin_section();
Aaron Wells's avatar
Aaron Wells committed
235

236
237
    if (function_exists('pieform_get_headdata')) {
        $headers = array_merge($headers, pieform_get_headdata());
238
239
240
        if (!defined('PIEFORM_GOT_HEADDATA')) {
          define('PIEFORM_GOT_HEADDATA', 1);
        }
241
    }
242

243
244
245
    // Define the stylesheets array early so that javascript modules can add extras
    $stylesheets = array();

Aaron Wells's avatar
Aaron Wells committed
246
    // Insert the appropriate javascript tags
247
    $javascript_array = array();
248
    $jsroot = $wwwroot . 'js/';
249

250
251
    $langdirection = get_string('thisdirection', 'langconfig');

252
    // Make jQuery accessible with $j
253
    $javascript_array[] = $jsroot . 'jquery/jquery.js';
254
    $headers[] = '<script>$j=jQuery;</script>';
255

256
257
    // If necessary, load MathJax configuration
    if (get_config('mathjax')) {
258
        $headers[] = '<script>'.get_config('mathjaxconfig').'</script>';
259
260
    }

Richard Mansfield's avatar
Richard Mansfield committed
261
    // TinyMCE must be included first for some reason we're not sure about
262
263
264
265
    //
    // 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
266
    if (is_html_editor_enabled()) {
267
268
        $checkarray = array(&$javascript, &$headers);
        $found_tinymce = false;
269
270
271
272
273
274
275
276
277
        $tinymceviewid = 'null';
        if ($inpersonalarea = user_personal_section()) {
            if (defined('SECTION_PAGE') && (SECTION_PAGE == 'view' || SECTION_PAGE == 'blocks' || SECTION_PAGE == 'editlayout')) {
                if (isset($viewid) && $viewid > 0) {
                    $tinymceviewid = $viewid;
                }
            }
        }

278
279
280
281
        foreach ($checkarray as &$check) {
            if (($key = array_search('tinymce', $check)) !== false || ($key = array_search('tinytinymce', $check)) !== false) {
                if (!$found_tinymce) {
                    $found_tinymce = $check[$key];
282
                    $javascript_array[] = $wwwroot . 'artefact/file/js/filebrowser.js';
283
                    $javascript_array[] = $jsroot . 'switchbox.js';
284
285
                    $javascript_array[] = $jsroot . 'tinymce/tinymce.js';
                    $stylesheets = array_merge($stylesheets, array_reverse(array_values($THEME->get_url('style/tinymceskin.css', true))));
286
                    $content_css = json_encode($THEME->get_url('style/tinymce.css'));
287
288
                    $language = current_language();
                    $language = substr($language, 0, ((substr_count($language, '_') > 0) ? 5 : 2));
289
                    if ($language != 'en' && !file_exists(get_config('docroot') . 'js/tinymce/langs/' . $language . '.js')) {
290
291
292
293
294
295
296
297
                        // 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';
                            }
298
                        }
299
300
                    }
                    $extrasetup = isset($extraconfig['tinymcesetup']) ? $extraconfig['tinymcesetup'] : '';
301
                    $extramceconfig = isset($extraconfig['tinymceconfig']) ? $extraconfig['tinymceconfig'] : '';
302

303
                    // Check whether to make the spellchecker available
304
                    if (get_config('tinymcespellcheckerengine')) {
305
                        $spellchecker = ',spellchecker';
306
                        $spellchecker_toolbar = '| spellchecker';
307
                        $spellchecker_config = "gecko_spellcheck : false, spellchecker_rpc_url : \"{$jsroot}tinymce/plugins/spellchecker/spellchecker.php\",";
308
309
                    }
                    else {
310
                        $spellchecker = $spellchecker_toolbar = '';
311
312
                        $spellchecker_config = 'gecko_spellcheck : true,';
                    }
313
314
                    $mathslate = (get_config('mathjax')) ? 'mathslate' : '';
                    $mathslateplugin = !empty($mathslate) ? ',' . $mathslate : '';
315
                    $toolbar = array(
316
                        null,
317
                        '"toolbar_toggle | formatselect | bold italic | bullist numlist | link unlink | imagebrowser | undo redo"',
318
                        '"underline strikethrough subscript superscript | alignleft aligncenter alignright alignjustify | outdent indent | forecolor backcolor | ltr rtl | fullscreen"',
319
                        '"fontselect | fontsizeselect | emoticons nonbreaking charmap ' . $mathslate . ' ' . $spellchecker_toolbar . ' | table | removeformat pastetext | anchor | code"',
320
321
322
323
324
325
                    );

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

326
327
328
329
                    // Language strings required for TinyMCE
                    $pagestrings['mahara'] = isset($pagestrings['mahara']) ? $pagestrings['mahara'] : array();
                    $pagestrings['mahara'][] = 'attachedimage';

330
331
332
333
334
335
336
337
338
339
340
                    $tinymceinitbehatsetup = '';
                    $tinymcebehatsetup = '';
                    if (defined('BEHAT_TEST')) {
                        $tinymceinitbehatsetup = 'window.isEditorInitializing = false;';
                        $tinymcebehatsetup = <<<EOF
        ed.on('PreInit', function(ed) {
            window.isEditorInitializing = true;
        });
EOF;
                    }

341
                    if ($check[$key] == 'tinymce') {
342
343
                        $tinymceconfig = <<<EOF
    theme: "modern",
344
    plugins: "tooltoggle,textcolor,visualblocks,wordcount,link,lists,imagebrowser,table,emoticons{$spellchecker},paste,code,fullscreen,directionality,searchreplace,nonbreaking,charmap{$mathslateplugin},anchor",
345
    skin: 'light',
346
347
    toolbar1: {$toolbar[1]},
    toolbar2: {$toolbar[2]},
348
    toolbar3: {$toolbar[3]},
349
    menubar: false,
350
    fix_list_elements: true,
351
    image_advtab: true,
352
    table_style_by_css: true,
353
    {$spellchecker_config}
354
EOF;
355
356
                    }
                    else {
357
358
359
                        $tinymceconfig = <<<EOF
    selector: "textarea.tinywysiwyg",
    theme: "modern",
360
    skin: 'light',
361
362
    plugins: "fullscreen,autoresize",
    toolbar: {$toolbar[0]},
363
EOF;
364
                    }
365
$samepage = get_string('samepage', 'mahara');
366
                    $headers[] = <<<EOF
367
<script>
368
tinyMCE.init({
369
370
    {$tinymceconfig}
    schema: 'html4',
371
372
373
374
375
376
377
    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|name|scrolling|frameborder|allowfullscreen|webkitallowfullscreen|mozallowfullscreen|longdesc|marginheight|marginwidth|align|title|class|type]"
378
        + ",a[id|class|title|href|name|target]"
379
        + ",button[id|class|title]"
380
    ,urlconverter_callback : "custom_urlconvert",
381
    language: '{$language}',
382
    directionality: "{$tinymce_langdir}",
383
    content_css : {$content_css},
384
    font_formats: 'Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Open Sans=Open Sans;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats;',
385
    remove_script_host: false,
386
    relative_urls: false,
387
388
389
390
391
    target_list: [
        {title: 'None', value: ''},
        {title: "{$samepage}", value: '_self'}, // This one is not translated in tinymce lang files
        {title: 'New window', value: '_blank'}
    ],
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
    link_list: function(success) {
        // Only show the list of links in the normal user section
        if ({$inpersonalarea}) {
            var params = {
                'viewid': {$tinymceviewid}
            }
            sendjsonrequest(config['wwwroot'] + 'json/tinymceviewlist.json.php',  params, 'POST', function(data) {
                if (data.count > 0) {
                    success(JSON.parse(data.data));
                }
                else {
                    success(''); // stop showing list with only option being 'none'
                }
            });
        }
        else {
            success(''); // stop showing list with only option being 'none'
        }
    },

Aaron Wells's avatar
Aaron Wells committed
412
    cache_suffix: '?v={$CFG->cacheversion}',
413
    {$extramceconfig}
414
    setup: function(ed) {
415
        {$tinymcebehatsetup}
416
        ed.on('init', function(ed) {
417
        {$tinymceinitbehatsetup}
418
            if (typeof(editor_to_focus) == 'string' && ed.editorId == editor_to_focus) {
419
                ed.trigger("focus");
420
421
            }
        });
422
423
424
        ed.on('keyup change', function (e) {
            checkTextareaMaxLength(ed.settings.id);
        });
425
426
        ed.on('LoadContent', function(e) {
            // Hide all the 2nd/3rd row menu buttons
427
            jQuery('.mce-toolbar.mce-first').siblings().addClass('hidden');
428
429
430
431
432
433
434
435
436
437
438
            // 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');
            });
439
        });
440
        {$extrasetup}
441
    }
442
});
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459

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

460
function custom_urlconvert (u, n, e) {
461
462
    // Don't convert the url on the skype status buttons.
    if (u.indexOf('skype:') == 0) {
463
      return u;
464
465
    }
    var t = tinyMCE.activeEditor, s = t.settings;
466

467
468
    // 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)
469
470
      return u;

471
472
    // Convert to relative
    if (s.relative_urls)
473
474
      return t.documentBaseURI.toRelative(u);

475
476
    // Convert to absolute
    u = t.documentBaseURI.toAbsolute(u, s.remove_script_host);
477

478
    return u;
479
}
480
481
482
</script>

EOF;
483
484
485
486
487
488
489
                    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]);
490
                }
491
            }
492

493
494
495
496
            // If any page adds jquery explicitly, remove it from the list
            if (($key = array_search('jquery', $check)) !== false) {
                unset($check[$key]);
            }
497
        }
498
    }
499
500
501
502
503
504
505
506
    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]);
        }
    }
Martyn Smith's avatar
Martyn Smith committed
507
    $javascript_array[] = $jsroot . 'keyboardNavigation.js';
508

509
510
511
512
513
    //If necessary, load MathJax path
    if (get_config('mathjax')) {
        $javascript_array[] = get_config('mathjaxpath');
    }

514
    $strings = array();
515
516
517
518
519
520
521
522
523
    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);
        }
524
525
    }

526
    $jsstrings = jsstrings();
Martyn Smith's avatar
Martyn Smith committed
527
    $themepaths = themepaths();
528

Richard Mansfield's avatar
Richard Mansfield committed
529
    foreach ($javascript as $jsfile) {
530
531
532
533
        // 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.
534
        if (strpos($jsfile, '/') === false) {
535
            $javascript_array[] = $jsroot . $jsfile . '.js';
536
            if (isset($jsstrings[$jsfile])) {
537
538
539
                foreach ($jsstrings[$jsfile] as $section => $tags) {
                    foreach ($tags as $tag) {
                        $strings[$tag] = get_raw_string($tag, $section);
540
541
542
                    }
                }
            }
Martyn Smith's avatar
Martyn Smith committed
543
544
            if (isset($themepaths[$jsfile])) {
                foreach ($themepaths[$jsfile] as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
545
                    $theme_list[$themepath] = $THEME->get_url($themepath);
Martyn Smith's avatar
Martyn Smith committed
546
547
                }
            }
548
        }
549
        else if (stripos($jsfile, 'http://') === false && stripos($jsfile, 'https://') === false) {
550
            // A local .js file with a fully specified path
551
            $javascript_array[] = $wwwroot . $jsfile;
552
553
554
555
556
557
            // 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
558
            $bits = explode('/', $jsfile);
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
            $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'));
576
577
                if (is_callable(array($pluginclass, 'jsstrings'))) {
                    $tempstrings = call_static_method($pluginclass, 'jsstrings', $name);
578
579
580
                    foreach ($tempstrings as $section => $tags) {
                        foreach ($tags as $tag) {
                            $strings[$tag] = get_raw_string($tag, $section);
581
582
                        }
                    }
Richard Mansfield's avatar
Richard Mansfield committed
583
                }
584
585
586
587
                if (is_callable(array($pluginclass, 'jshelp'))) {
                    $tempstrings = call_static_method($pluginclass, 'jshelp', $name);
                    foreach ($tempstrings as $section => $tags) {
                        foreach ($tags as $tag) {
588
                            $strings[$tag . '.help'] = get_help_icon($plugintype, $pluginname, null, null,
589
590
591
592
                                                                     null, $tag);
                        }
                    }
                }
Martyn Smith's avatar
Martyn Smith committed
593
594
595
                if (is_callable(array($pluginclass, 'themepaths'))) {
                    $tmpthemepaths = call_static_method($pluginclass, 'themepaths', $name);
                    foreach ($tmpthemepaths as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
596
                        $theme_list[$themepath] = $THEME->get_url($themepath);
Martyn Smith's avatar
Martyn Smith committed
597
598
                    }
                }
Martyn Smith's avatar
Martyn Smith committed
599
            }
Martyn Smith's avatar
Martyn Smith committed
600
        }
601
602
603
604
        else {
            // A remote .js file
            $javascript_array[] = $jsfile;
        }
605
    }
606
607

    $javascript_array[] = $jsroot . 'mahara.js';
608
    $javascript_array[] = $jsroot . 'formchangechecker.js';
609
    $javascript_array[] = $jsroot . 'textareamaxlengthchecker.js';
610

611
612
613
614
615
616
    // Load some event handler functions for checking if all AJAX requests have completed
    // when running behat tests
    if (defined('BEHAT_TEST')) {
        $javascript_array[] = get_config('wwwroot') . 'testing/frameworks/behat/page_status.js';
    }

617
618
619
    foreach ($jsstrings['mahara'] as $section => $tags) {
        foreach ($tags as $tag) {
            $strings[$tag] = get_raw_string($tag, $section);
620
621
        }
    }
622
623
    if (isset($extraconfig['themepaths']) && is_array($extraconfig['themepaths'])) {
        foreach ($extraconfig['themepaths'] as $themepath) {
Nigel McNie's avatar
Nigel McNie committed
624
            $theme_list[$themepath] = $THEME->get_url($themepath);
625
626
        }
    }
627

628
    $stringjs = '<script>';
629
    $stringjs .= 'var strings = ' . json_encode($strings) . ';';
630
    $stringjs .= "\nfunction plural(n) { return " . get_raw_string('pluralrule', 'langconfig') . "; }\n";
631
632
    $stringjs .= '</script>';

633

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

637
    $smarty->assign('STRINGJS', $stringjs);
638

639
    $stylesheets = get_stylesheets_for_current_page($stylesheets, $extraconfig);
640

Son Nguyen's avatar
Son Nguyen committed
641
642
643
644
    // Disable CSS transforms, transitions, and animations when running behat tests
    if (defined('BEHAT_TEST')) {
        $stylesheets[] = get_config('wwwroot') . 'testing/frameworks/behat/no_transitions.css';
    }
645
    $smarty->assign('STYLESHEETLIST', $stylesheets);
646
647
    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
648
        $smarty->assign('THEMELIST', json_encode(array_merge((array)json_decode($smarty->get_template_vars('THEMELIST')),  $theme_list)));
649
    }
650

651
652
653
654
655
    $dropdownmenu = get_config('dropdownmenu');
    // disable drop-downs if overridden at institution level
    $sitethemeprefs = get_config('sitethemeprefs');
    $institutions = $USER->institutions;
    if (!empty($institutions)) {
656
657
658
659
        if (count($institutions) == 1) {
            $i = reset($institutions);
            if ($i->theme == $THEME->basename && $USER->institutiontheme->institutionname == $i->institution) {
                $dropdownmenu = $i->dropdownmenu;
660
            }
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
        }
        else {
            foreach ($institutions as $i) {
                if (!empty($sitethemeprefs)) {
                    if (!empty($USER->accountprefs['theme']) && $USER->accountprefs['theme'] == $THEME->basename . '/' . $i->institution) {
                        $dropdownmenu = $i->dropdownmenu;
                        break;
                    }
                }
                else {
                    if ((!empty($USER->accountprefs['theme']) && $USER->accountprefs['theme'] == $THEME->basename . '/' . $i->institution)
                        || (empty($USER->accountprefs) && $i->theme == $THEME->basename && $USER->institutiontheme->institutionname == $i->institution)) {
                        $dropdownmenu = $i->dropdownmenu;
                        break;
                    }
676
677
678
679
680
681
                }
            }
        }
    }

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

684
685
    if ($dropdownmenu) {
        $smarty->assign('DROPDOWNMENU', $dropdownmenu);
686
        $javascript_array[] = $jsroot . 'dropdown-nav.js';
687
    }
688

689
    $smarty->assign('MOBILE', $SESSION->get('mobile'));
690
    $smarty->assign('HANDHELD_DEVICE', $SESSION->get('handheld_device'));
691
692
693
694
695
696
697
698
699
    if (defined('FILEBROWSERS') ||
        (defined('SECTION_PAGE') && SECTION_PAGE == 'blocks')) {
        // Need to add the headers for select2 here so filebrowser has correct language
        require_once(get_config('libroot') . 'form/elements/autocomplete.php');
        $select2lang = pieform_element_autocomplete_language();
        $select2headdata = pieform_element_autocomplete_get_headdata();
        $headers = array_merge($headers, $select2headdata);
        $smarty->assign('select2_language', $select2lang);
    }
700
701
702
    $maxuploadsize = get_max_upload_size(false);
    $smarty->assign('maxuploadsize', $maxuploadsize);
    $smarty->assign('maxuploadsizepretty', display_size($maxuploadsize));
703

704
705
706
707
    $sitename = get_config('sitename');
    if (!$sitename) {
       $sitename = 'Mahara';
    }
708
    $smarty->assign('sitename', $sitename);
709

710
711
712
713
    $sitelogocustom = false;
    if (get_config('installed')) {
        $sitelogocustom = (int) (get_field('institution', 'logo', 'name', 'mahara') || $THEME->headerlogo);
    }
714
    $smarty->assign('sitelogocustom', $sitelogocustom);
715
    $sitelogo = $THEME->header_logo();
716
    $sitelogo = append_version_number($sitelogo);
717
718
    $sitelogosmall = $THEME->header_logo_small();
    $sitelogosmall = ($sitelogosmall ? append_version_number($sitelogosmall) : null);
719
    $smarty->assign('sitelogo', $sitelogo);
720
    $smarty->assign('sitelogosmall', $sitelogosmall);
721
722
    $smarty->assign('sitelogo4facebook', $THEME->facebook_logo());
    $smarty->assign('sitedescription4facebook', get_string('facebookdescription', 'mahara'));
723

Martyn Smith's avatar
Martyn Smith committed
724
    if (defined('TITLE')) {
725
        $smarty->assign('PAGETITLE', TITLE . ' - ' . $sitename);
Martyn Smith's avatar
Martyn Smith committed
726
727
    }
    else {
728
        $smarty->assign('PAGETITLE', $sitename);
Martyn Smith's avatar
Martyn Smith committed
729
    }
730
731
732
733
734
735
736
737
738
    if (defined('PAGEHEADING')) {
        $smarty->assign('PAGEHEADING', PAGEHEADING);
    }
    else {
        if (defined('TITLE')) {
            $smarty->assign('PAGEHEADING', TITLE);
        }
    }

739
740
741
    if (defined('SUBSECTIONHEADING')) {
        $smarty->assign('SUBSECTIONHEADING', SUBSECTIONHEADING);
    }
Martyn Smith's avatar
Martyn Smith committed
742

743
    $smarty->assign('PRODUCTIONMODE', get_config('productionmode'));
744
745
746
    if (defined('SITEOUTOFSYNC')) {
        $smarty->assign('SITEOUTOFSYNC', SITEOUTOFSYNC);
    }
747
    if (function_exists('local_header_top_content')) {
748
        $sitetop = (isset($sitetop) ? $sitetop : '') . local_header_top_content();
749
750
751
    }
    if (isset($sitetop)) {
        $smarty->assign('SITETOP', $sitetop);
752
    }
753
754
755
    if (defined('PUBLIC')) {
        $smarty->assign('PUBLIC', true);
    }
756
757
758
    if (defined('ADMIN')) {
        $smarty->assign('ADMIN', true);
    }
759
760
761
    if (defined('INSTITUTIONALADMIN')) {
        $smarty->assign('INSTITUTIONALADMIN', true);
    }
762
763
764
765
766
767
    if (defined('STAFF')) {
        $smarty->assign('STAFF', true);
    }
    if (defined('INSTITUTIONALSTAFF')) {
        $smarty->assign('INSTITUTIONALSTAFF', true);
    }
768

769
    $smarty->assign('LOGGEDIN', $USER->is_logged_in());
770
    $smarty->assign('loggedout', !$USER->is_logged_in());
771
772
773
774
775
776
777
    $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);
778
    if ($USER->is_logged_in()) {
779
        global $SELECTEDSUBNAV; // It's evil, but rightnav & mainnav stuff are now in different templates.
780
781
782
783
        $smarty->assign('MAINNAV', main_nav());
        $is_admin = $USER->get('admin') || $USER->is_institutional_admin() || $USER->get('staff') || $USER->is_institutional_staff();
        if ($is_admin) {
            $smarty->assign('MAINNAVADMIN', main_nav('adminnav'));
784
        }
785
        $mainnavsubnav = $SELECTEDSUBNAV;
786
        $smarty->assign('RIGHTNAV', right_nav());
787
        $smarty->assign('MESSAGEBOX', message_nav());
788
789
790
791
792
793
794
795
796
        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);
        }
797
    }
798
    else {
799
        $smarty->assign('languageform', $langselectform);
800
    }
801
    $smarty->assign('FOOTERMENU', footer_menu());
802

803
    $smarty->assign('USER', $USER);
804
    $smarty->assign('SESSKEY', $USER->get('sesskey'));
805
    $smarty->assign('CC_ENABLED', get_config('cookieconsent_enabled'));
806
    $javascript_array = append_version_number($javascript_array);
807
    $smarty->assign('JAVASCRIPT', $javascript_array);
808
    $smarty->assign('RELEASE', get_config('release'));
809
    $smarty->assign('SERIES', get_config('series'));
810
    $smarty->assign('CACHEVERSION', get_config('cacheversion', 0));
811
    if (get_config('siteclosedforupgrade')) {
812
        $smarty->assign('SITECLOSED', 'logindisabled');
813
    }
814
    else if (get_config('siteclosedbyadmin')) {
815
        $smarty->assign('SITECLOSED', 'loginallowed');
816
    }
817

818
819
    if ((!isset($extraconfig['pagehelp']) || $extraconfig['pagehelp'] !== false)
        and $help = has_page_help()) {
820
821
822
        $smarty->assign('PAGEHELPNAME', $help[0]);
        $smarty->assign('PAGEHELPICON', $help[1]);
    }
823
    if (defined('GROUP')) {
824
        require_once('group.php');
825
826
827
828
829
830
        if ($group = group_current_group()) {
            $smarty->assign('GROUP', $group);
            if (!defined('NOGROUPMENU')) {
                $smarty->assign('SUBPAGENAV', group_get_menu_tabs());
                $smarty->assign('PAGEHEADING', $group->name);
            }
831
        }
832
    }
833

834
835
836
837
838
839
    if (defined('APPS')) {
       if (!defined('NOAPPSMENU')) {
            $smarty->assign('SUBPAGENAV', apps_get_menu_tabs());
        }
    }

840
    // ---------- sideblock smarty stuff ----------
841
    $sidebars = !isset($extraconfig['sidebars']) || $extraconfig['sidebars'] !== false;
842
843
844
845
846
    if ($sidebars && !defined('INSTALLER')) {
        foreach ($sideblock_menu as $sideblock) {
            if (!empty($sideblock['visible']) && !empty($sideblock['smarty'])) {
                foreach ($sideblock['smarty'] as $ks => $vs) {
                    $smarty->assign($ks, $vs);
847
848
                }
            }
849
        }
Martyn Smith's avatar
Martyn Smith committed
850

Aaron Wells's avatar
Aaron Wells committed
851
852
        // Place all sideblocks on the right. If this structure is munged
        // appropriately, you can put blocks on the left. In future versions of
853
        // Mahara, we'll make it easy to do this.
854
855
        $sidebars = $sidebars && !empty($sideblock_menu);
        $sideblocks = array('left' => array(), 'right' => $sideblock_menu);
856

857
        $smarty->assign('userauthinstance', $SESSION->get('authinstance'));
858
        $smarty->assign('MNETUSER', $SESSION->get('mnetuser'));
859
        $smarty->assign('SIDEBLOCKS', $sideblocks);
860
        $smarty->assign('SIDEBARS', $sidebars);
861

862
863
    }

864
865
866
    if (is_array($HEADDATA) && !empty($HEADDATA)) {
        $headers = array_merge($HEADDATA, $headers);
    }
867
    $smarty->assign('HEADERS', $headers);
868

869
870
    if ($USER->get('parentuser')) {
        $smarty->assign('USERMASQUERADING', true);
871
        $smarty->assign('masqueradedetails', get_string('youaremasqueradingas', 'mahara', display_name($USER)));
872
        $smarty->assign('becomeyoulink', hsc($wwwroot) . 'admin/users/changeuser.php?restore=1');
873
        $smarty->assign('becomeyouagain', get_string('becomeadminagain', 'admin', $USER->get('parentuser')->name));
874
    }
Martyn Smith's avatar
Martyn Smith committed
875

876
877
    // Define additional html content
    if (get_config('installed')) {
878
879
880
881
        $additionalhtmlitems = array(
            'ADDITIONALHTMLHEAD'      => get_config('additionalhtmlhead'),
            'ADDITIONALHTMLTOPOFBODY' => get_config('additionalhtmltopofbody'),
            'ADDITIONALHTMLFOOTER'    => get_config('additionalhtmlfooter')
882
883
        );
        if ($additionalhtmlitems) {
884
885
            foreach ($additionalhtmlitems as $name=>$content) {
                $smarty->assign($name, $content);
886
887
888
            }
        }
    }
889
890
891
892
893
894

    // If Cookie Consent is enabled, than define conent
    if (get_config('cookieconsent_enabled')) {
        require_once('cookieconsent.php');
        $smarty->assign('COOKIECONSENTCODE', get_cookieconsent_code());
    }
895
    // Render the session messages
896
897
898
899
    $messages = array();
    $messages = array_merge($messages, insert_messages('loginbox'));
    $messages = array_merge($messages, insert_messages('messages'));
    $smarty->assign('messages', $messages);
900
901
902
    return $smarty;
}

903
904
905
906

/**
 * Manages theme configuration.
 *
Aaron Wells's avatar
Aaron Wells committed
907
 * Does its best to give the user _a_ theme, even if it's not the theme they
908
909
910
911
912
913
914
915
916
 * 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 = '';

917
918
919
920
921
    /**
     * A user may have had the header logo overridden by an institution
     */
    public $headerlogo;

922
923
924
925
926
    /**
     * A user may have had the small header logo added by an institution
     */
    public $headerlogosmall;

927
928
929
930
931
    /**
     * Additional stylesheets to display after the basename theme's stylesheets
     */
    public $addedstylesheets;

932
933
934
935
936
937
938
939
940
941
    /**
     * A human-readable version of the theme name
     */
    public $displayname = '';

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

942
943
944
945
946
947
948
949
950
951
    /**
     * Directories where to look for templates by default
     */
    public $templatedirs = array();

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

952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
    /**
     * 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
985
986
     * 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
987
988
     * that's a user ID and ask for the theme for that user.
     *
Aaron Wells's avatar
Aaron Wells committed
989
     * If the theme they want doesn't exist, the object is initialised for the
990
991
992
993
994
995
996
997
     * 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;
998
            $themedata = null;
999
1000
        }
        else if ($arg instanceof User) {
1001
            $themedata = $arg->get_themedata();
1002
        }
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
        else if ($arg instanceof View) {
            $themename = $arg->get('theme');
            $themedata = null;
            $userid = $arg->get('owner');
            if ($userid) {
                $user = new User();
                $user->find_by_id($userid);
                $themedata = $user->get_themedata();
                $themedata->viewbasename = $themedata->basename;
                unset($themedata->basename);
            }
        }
1015
1016
1017
        else if (is_int($arg)) {
            $user = new User();
            $user->find_by_id($arg);
1018
            $themedata = $user->get_themedata();
1019
1020
1021
1022
1023
        }
        else {
            throw new SystemException("Argument to Theme::__construct was not a theme name, user object or user ID");
        }

1024
        if (isset($themedata) && isset($themedata->basename)) {
1025
1026
1027
            $themename = $themedata->basename;
        }

1028
        if (empty($themename)) {
1029
            // Theme to show to when no theme has been suggested
1030
1031
1032
            if (!$themename = get_config('theme')) {
                $themename = 'raw';
            }
1033
        }
1034
1035

        // check the validity of the name
1036
        if (!$this->name_is_valid($themename)) {
1037
1038
            throw new SystemException("Theme name is in invalid form: '$themename'");
        }
1039
1040

        $this->init_theme($themename, $themedata);
1041
1042
1043
1044
1045
1046
1047
1048
    }