Proyectos de Subversion Moodle

Rev

Rev 1468 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * This file contains the moodle_page class. There is normally a single instance
19
 * of this class in the $PAGE global variable. This class is a central repository
20
 * of information about the page we are building up to send back to the user.
21
 *
22
 * @package core
23
 * @category page
24
 * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
25
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
 */
27
 
1441 ariadna 28
use core\exception\moodle_exception;
1 efrain 29
use core\navigation\views\primary;
30
use core\navigation\views\secondary;
31
use core\navigation\output\primary as primaryoutput;
32
use core\output\activity_header;
1441 ariadna 33
use core\output\xhtml_container_stack;
1 efrain 34
 
35
/**
36
 * $PAGE is a central store of information about the current page we are
37
 * generating in response to the user's request.
38
 *
39
 * It does not do very much itself
40
 * except keep track of information, however, it serves as the access point to
41
 * some more significant components like $PAGE->theme, $PAGE->requires,
42
 * $PAGE->blocks, etc.
43
 *
44
 * @copyright 2009 Tim Hunt
45
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46
 * @since Moodle 2.0
47
 * @package core
48
 * @category page
49
 *
50
 * The following properties are alphabetical. Please keep it that way so that its
51
 * easy to maintain.
52
 *
53
 * @property-read string $activityname The type of activity we are in, for example 'forum' or 'quiz'.
54
 *      Will be null if this page is not within a module.
55
 * @property-read stdClass $activityrecord The row from the activities own database table (for example
56
 *      the forum or quiz table) that this page belongs to. Will be null
57
 *      if this page is not within a module.
58
 * @property-read activity_header $activityheader The activity header for the page, representing standard components
59
 *      displayed within the header
60
 * @property-read array $alternativeversions Mime type => object with ->url and ->title.
61
 * @property-read block_manager $blocks The blocks manager object for this page.
62
 * @property-read array $blockmanipulations
63
 * @property-read string $bodyclasses A string to use within the class attribute on the body tag.
64
 * @property-read string $bodyid A string to use as the id of the body tag.
65
 * @property-read string $button The HTML to go where the Turn editing on button normally goes.
66
 * @property-read bool $cacheable Defaults to true. Set to false to stop the page being cached at all.
67
 * @property-read array $categories An array of all the categories the page course belongs to,
68
 *      starting with the immediately containing category, and working out to
69
 *      the top-level category. This may be the empty array if we are in the
70
 *      front page course.
71
 * @property-read mixed $category The category that the page course belongs to.
72
 * @property-read cm_info $cm The course_module that this page belongs to. Will be null
73
 *      if this page is not within a module. This is a full cm object, as loaded
74
 *      by get_coursemodule_from_id or get_coursemodule_from_instance,
75
 *      so the extra modname and name fields are present.
76
 * @property-read context $context The main context to which this page belongs.
77
 * @property-read stdClass $course The current course that we are inside - a row from the
78
 *      course table. (Also available as $COURSE global.) If we are not inside
79
 *      an actual course, this will be the site course.
80
 * @property-read string $devicetypeinuse The name of the device type in use
81
 * @property-read string $docspath The path to the Documentation for this page.
82
 * @property-read string $focuscontrol The id of the HTML element to be focused when the page has loaded.
83
 * @property-read bool $headerprinted True if the page header has already been printed.
84
 * @property-read string $heading The main heading that should be displayed at the top of the <body>.
85
 * @property-read string $headingmenu The menu (or actions) to display in the heading
86
 * @property-read array $layout_options An arrays with options for the layout file.
87
 * @property-read array $legacythemeinuse True if the legacy browser theme is in use.
88
 * @property-read navbar $navbar The navbar object used to display the navbar
89
 * @property-read secondary $secondarynav The secondary navigation object
90
 *      used to display the secondarynav in boost
91
 * @property-read primary $primarynav The primary navigation object used to display the primary nav in boost
92
 * @property-read primaryoutput $primarynavcombined The primary navigation object used to display the primary nav in boost
93
 * @property-read global_navigation $navigation The navigation structure for this page.
94
 * @property-read xhtml_container_stack $opencontainers Tracks XHTML tags on this page that have been opened but not closed.
95
 *      mainly for internal use by the rendering code.
96
 * @property-read string $pagelayout The general type of page this is. For example 'normal', 'popup', 'home'.
97
 *      Allows the theme to display things differently, if it wishes to.
98
 * @property-read string $pagetype The page type string, should be used as the id for the body tag in the theme.
99
 * @property-read int $periodicrefreshdelay The periodic refresh delay to use with meta refresh
100
 * @property-read page_requirements_manager $requires Tracks the JavaScript, CSS files, etc. required by this page.
101
 * @property-read string $requestip The IP address of the current request, null if unknown.
102
 * @property-read string $requestorigin The type of request 'web', 'ws', 'cli', 'restore', etc.
103
 * @property-read settings_navigation $settingsnav The settings navigation
104
 * @property-read int $state One of the STATE_... constants
105
 * @property-read string $subpage The subpage identifier, if any.
106
 * @property-read theme_config $theme The theme for this page.
107
 * @property-read string $title The title that should go in the <head> section of the HTML of this page.
108
 * @property-read moodle_url $url The moodle url object for this page.
109
 */
1469 ariadna 110
class moodle_page
111
{
1 efrain 112
 
113
    /** The state of the page before it has printed the header **/
114
    const STATE_BEFORE_HEADER = 0;
115
 
116
    /** The state the page is in temporarily while the header is being printed **/
117
    const STATE_PRINTING_HEADER = 1;
118
 
119
    /** The state the page is in while content is presumably being printed **/
120
    const STATE_IN_BODY = 2;
121
 
122
    /**
123
     * The state the page is when the footer has been printed and its function is
124
     * complete.
125
     */
126
    const STATE_DONE = 3;
127
 
128
    /**
129
     * The separator used for separating page title elements.
130
     */
131
    const TITLE_SEPARATOR = ' | ';
132
 
133
    /**
134
     * @var int The current state of the page. The state a page is within
135
     * determines what actions are possible for it.
136
     */
137
    protected $_state = self::STATE_BEFORE_HEADER;
138
 
139
    /**
140
     * @var stdClass The course currently associated with this page.
141
     * If not has been provided the front page course is used.
142
     */
143
    protected $_course = null;
144
 
145
    /**
146
     * @var cm_info If this page belongs to a module, this is the cm_info module
147
     * description object.
148
     */
149
    protected $_cm = null;
150
 
151
    /**
152
     * @var stdClass If $_cm is not null, then this will hold the corresponding
153
     * row from the modname table. For example, if $_cm->modname is 'quiz', this
154
     * will be a row from the quiz table.
155
     */
156
    protected $_module = null;
157
 
158
    /**
159
     * @var context The context that this page belongs to.
160
     */
161
    protected $_context = null;
162
 
163
    /**
164
     * @var array This holds any categories that $_course belongs to, starting with the
165
     * particular category it belongs to, and working out through any parent
166
     * categories to the top level. These are loaded progressively, if needed.
167
     * There are three states. $_categories = null initially when nothing is
168
     * loaded; $_categories = array($id => $cat, $parentid => null) when we have
169
     * loaded $_course->category, but not any parents; and a complete array once
170
     * everything is loaded.
171
     */
172
    protected $_categories = null;
173
 
174
    /**
175
     * @var array An array of CSS classes that should be added to the body tag in HTML.
176
     */
177
    protected $_bodyclasses = array();
178
 
179
    /**
180
     * @var string The title for the page. Used within the title tag in the HTML head.
181
     */
182
    protected $_title = '';
183
 
184
    /**
185
     * @var string The string to use as the heading of the page. Shown near the top of the
186
     * page within most themes.
187
     */
188
    protected $_heading = '';
189
 
190
    /**
191
     * @var string The pagetype is used to describe the page and defaults to a representation
192
     * of the physical path to the page e.g. my-index, mod-quiz-attempt
193
     */
194
    protected $_pagetype = null;
195
 
196
    /**
197
     * @var string The pagelayout to use when displaying this page. The
198
     * pagelayout needs to have been defined by the theme in use, or one of its
199
     * parents. By default base is used however standard is the more common layout.
200
     * Note that this gets automatically set by core during operations like
201
     * require_login.
202
     */
203
    protected $_pagelayout = 'base';
204
 
205
    /**
206
     * @var array List of theme layout options, these are ignored by core.
207
     * To be used in individual theme layout files only.
208
     */
209
    protected $_layout_options = null;
210
 
211
    /**
212
     * @var string An optional arbitrary parameter that can be set on pages where the context
213
     * and pagetype is not enough to identify the page.
214
     */
215
    protected $_subpage = '';
216
 
217
    /**
218
     * @var string Set a different path to use for the 'Documentation for this page' link.
219
     * By default, it uses the path of the file for instance mod/quiz/attempt.
220
     */
221
    protected $_docspath = null;
222
 
223
    /**
224
     * @var string A legacy class that will be added to the body tag
225
     */
226
    protected $_legacyclass = null;
227
 
228
    /**
229
     * @var moodle_url The URL for this page. This is mandatory and must be set
230
     * before output is started.
231
     */
232
    protected $_url = null;
233
 
234
    /**
235
     * @var array An array of links to alternative versions of this page.
236
     * Primarily used for RSS versions of the current page.
237
     */
238
    protected $_alternateversions = array();
239
 
240
    /**
241
     * @var block_manager The blocks manager for this page. It is responsible for
242
     * the blocks and there content on this page.
243
     */
244
    protected $_blocks = null;
245
 
246
    /**
247
     * @var page_requirements_manager Page requirements manager. It is responsible
248
     * for all JavaScript and CSS resources required by this page.
249
     */
250
    protected $_requires = null;
251
 
252
    /** @var page_requirements_manager Saves the requirement manager object used before switching to to fragments one. */
253
    protected $savedrequires = null;
254
 
255
    /**
256
     * @var string The capability required by the user in order to edit blocks
257
     * and block settings on this page.
258
     */
259
    protected $_blockseditingcap = 'moodle/site:manageblocks';
260
 
261
    /**
262
     * @var bool An internal flag to record when block actions have been processed.
263
     * Remember block actions occur on the current URL and it is important that
264
     * even they are never executed more than once.
265
     */
266
    protected $_block_actions_done = false;
267
 
268
    /**
269
     * @var array An array of any other capabilities the current user must have
270
     * in order to editing the page and/or its content (not just blocks).
271
     */
272
    protected $_othereditingcaps = array();
273
 
274
    /**
275
     * @var bool Sets whether this page should be cached by the browser or not.
276
     * If it is set to true (default) the page is served with caching headers.
277
     */
278
    protected $_cacheable = true;
279
 
280
    /**
281
     * @var string Can be set to the ID of an element on the page, if done that
282
     * element receives focus when the page loads.
283
     */
284
    protected $_focuscontrol = '';
285
 
286
    /**
287
     * @var string HTML to go where the turn on editing button is located. This
288
     * is nearly a legacy item and not used very often any more.
289
     */
290
    protected $_button = '';
291
 
292
    /**
293
     * @var theme_config The theme to use with this page. This has to be properly
294
     * initialised via {@link moodle_page::initialise_theme_and_output()} which
295
     * happens magically before any operation that requires it.
296
     */
297
    protected $_theme = null;
298
 
299
    /**
300
     * @var global_navigation Contains the global navigation structure.
301
     */
302
    protected $_navigation = null;
303
 
304
    /**
305
     * @var settings_navigation Contains the settings navigation structure.
306
     */
307
    protected $_settingsnav = null;
308
 
309
    /**
310
     * @var flat_navigation Contains a list of nav nodes, most closely related to the current page.
311
     */
312
    protected $_flatnav = null;
313
 
314
    /**
315
     * @var secondary Contains the nav nodes that will appear
316
     * in the secondary navigation.
317
     */
318
    protected $_secondarynav = null;
319
 
320
    /**
321
     * @var primary Contains the nav nodes that will appear
322
     * in the primary navigation.
323
     */
324
    protected $_primarynav = null;
325
 
326
    /**
327
     * @var primaryoutput Contains the combined nav nodes that will appear
328
     * in the primary navigation. Includes - primarynav, langmenu, usermenu
329
     */
330
    protected $_primarynavcombined = null;
331
 
332
    /**
333
     * @var navbar Contains the navbar structure.
334
     */
335
    protected $_navbar = null;
336
 
337
    /**
338
     * @var string The menu (or actions) to display in the heading.
339
     */
340
    protected $_headingmenu = null;
341
 
342
    /**
343
     * @var array stack trace. Then the theme is initialised, we save the stack
344
     * trace, for use in error messages.
345
     */
346
    protected $_wherethemewasinitialised = null;
347
 
348
    /**
349
     * @var xhtml_container_stack Tracks XHTML tags on this page that have been
350
     * opened but not closed.
351
     */
352
    protected $_opencontainers;
353
 
354
    /**
355
     * @var int Sets the page to refresh after a given delay (in seconds) using
356
     * meta refresh in {@link standard_head_html()} in outputlib.php
357
     * If set to null(default) the page is not refreshed
358
     */
359
    protected $_periodicrefreshdelay = null;
360
 
361
    /**
362
     * @var array Associative array of browser shortnames (as used by check_browser_version)
363
     * and their minimum required versions
364
     */
365
    protected $_legacybrowsers = array('MSIE' => 6.0);
366
 
367
    /**
368
     * @var string Is set to the name of the device type in use.
369
     * This will we worked out when it is first used.
370
     */
371
    protected $_devicetypeinuse = null;
372
 
373
    /**
374
     * @var bool Used to determine if HTTPS should be required for login.
375
     */
376
    protected $_https_login_required = false;
377
 
378
    /**
379
     * @var bool Determines if popup notifications allowed on this page.
380
     * Code such as the quiz module disables popup notifications in situations
381
     * such as upgrading or completing a quiz.
382
     */
383
    protected $_popup_notification_allowed = true;
384
 
385
    /**
386
     * @var bool Is the settings menu being forced to display on this page (activities / resources only).
387
     * This is only used by themes that use the settings menu.
388
     */
389
    protected $_forcesettingsmenu = false;
390
 
391
    /**
392
     * @var array Array of header actions HTML to add to the page header actions menu.
393
     */
394
    protected $_headeractions = [];
395
 
396
    /**
397
     * @var bool Should the region main settings menu be rendered in the header.
398
     */
399
    protected $_regionmainsettingsinheader = false;
400
 
401
    /**
402
     * @var bool Should the secondary menu be rendered.
403
     */
404
    protected $_hassecondarynavigation = true;
405
 
406
    /**
407
     * @var bool Should the secondary menu be rendered as a tablist as opposed to a menubar.
408
     */
409
    protected $_hastablistsecondarynavigation = false;
410
 
411
    /**
412
     * @var string the key of the secondary node to be activated.
413
     */
414
    protected $_activekeysecondary = null;
415
 
416
    /**
417
     * @var string the key of the primary node to be activated.
418
     */
419
    protected $_activenodeprimary = null;
420
 
421
    /**
422
     * @var activity_header The activity header for the page.
423
     */
424
    protected $_activityheader;
425
 
426
    /**
427
     * @var bool The value of displaying the navigation overflow.
428
     */
429
    protected $_navigationoverflow = true;
430
 
431
    /**
432
     * @var bool Whether to override/remove all editing capabilities for blocks on the page.
433
     */
434
    protected $_forcelockallblocks = false;
435
 
436
    /**
437
     * @var bool Indicates whether the course index drawer should be shown.
438
     */
439
    protected bool $_showcourseindex = true;
440
 
441
    /**
442
     * Force the settings menu to be displayed on this page. This will only force the
443
     * settings menu on an activity / resource page that is being displayed on a theme that
444
     * uses a settings menu.
445
     *
446
     * @param bool $forced default of true, can be sent false to turn off the force.
447
     */
1469 ariadna 448
    public function force_settings_menu($forced = true)
449
    {
1 efrain 450
        $this->_forcesettingsmenu = $forced;
451
    }
452
 
453
    /**
454
     * Check to see if the settings menu is forced to display on this activity / resource page.
455
     * This only applies to themes that use the settings menu.
456
     *
457
     * @return bool True if the settings menu is forced to display.
458
     */
1469 ariadna 459
    public function is_settings_menu_forced()
460
    {
1 efrain 461
        return $this->_forcesettingsmenu;
462
    }
463
 
464
    // Magic getter methods =============================================================
465
    // Due to the __get magic below, you normally do not call these as $PAGE->magic_get_x
466
    // methods, but instead use the $PAGE->x syntax.
467
 
468
    /**
469
     * Please do not call this method directly, use the ->state syntax. {@link moodle_page::__get()}.
470
     * @return integer one of the STATE_XXX constants. You should not normally need
471
     * to use this in your code. It is intended for internal use by this class
472
     * and its friends like print_header, to check that everything is working as
473
     * expected. Also accessible as $PAGE->state.
474
     */
1469 ariadna 475
    protected function magic_get_state()
476
    {
1 efrain 477
        return $this->_state;
478
    }
479
 
480
    /**
481
     * Please do not call this method directly, use the ->headerprinted syntax. {@link moodle_page::__get()}.
482
     * @return bool has the header already been printed?
483
     */
1469 ariadna 484
    protected function magic_get_headerprinted()
485
    {
1 efrain 486
        return $this->_state >= self::STATE_IN_BODY;
487
    }
488
 
489
    /**
490
     * Please do not call this method directly, use the ->course syntax. {@link moodle_page::__get()}.
491
     * @return stdClass the current course that we are inside - a row from the
492
     * course table. (Also available as $COURSE global.) If we are not inside
493
     * an actual course, this will be the site course.
494
     */
1469 ariadna 495
    protected function magic_get_course()
496
    {
1 efrain 497
        global $SITE;
498
        if (is_null($this->_course)) {
499
            return $SITE;
500
        }
501
        return $this->_course;
502
    }
503
 
504
    /**
505
     * Please do not call this method directly, use the ->cm syntax. {@link moodle_page::__get()}.
506
     * @return cm_info the course_module that this page belongs to. Will be null
507
     * if this page is not within a module. This is a full cm object, as loaded
508
     * by get_coursemodule_from_id or get_coursemodule_from_instance,
509
     * so the extra modname and name fields are present.
510
     */
1469 ariadna 511
    protected function magic_get_cm()
512
    {
1 efrain 513
        return $this->_cm;
514
    }
515
 
516
    /**
517
     * Please do not call this method directly, use the ->activityrecord syntax. {@link moodle_page::__get()}.
518
     * @return stdClass the row from the activities own database table (for example
519
     * the forum or quiz table) that this page belongs to. Will be null
520
     * if this page is not within a module.
521
     */
1469 ariadna 522
    protected function magic_get_activityrecord()
523
    {
1 efrain 524
        if (is_null($this->_module) && !is_null($this->_cm)) {
525
            $this->load_activity_record();
526
        }
527
        return $this->_module;
528
    }
529
 
530
    /**
531
     * Please do not call this method directly, use the ->activityname syntax. {@link moodle_page::__get()}.
532
     * @return string the The type of activity we are in, for example 'forum' or 'quiz'.
533
     * Will be null if this page is not within a module.
534
     */
1469 ariadna 535
    protected function magic_get_activityname()
536
    {
1 efrain 537
        if (is_null($this->_cm)) {
538
            return null;
539
        }
540
        return $this->_cm->modname;
541
    }
542
 
543
    /**
544
     * Please do not call this method directly, use the ->category syntax. {@link moodle_page::__get()}.
545
     * @return stdClass|null the category that the page course belongs to. If there isn't one
546
     * (that is, if this is the front page course) returns null.
547
     */
1469 ariadna 548
    protected function magic_get_category()
549
    {
1 efrain 550
        $this->ensure_category_loaded();
551
        if (!empty($this->_categories)) {
552
            return reset($this->_categories);
553
        } else {
554
            return null;
555
        }
556
    }
557
 
558
    /**
559
     * Please do not call this method directly, use the ->categories syntax. {@link moodle_page::__get()}.
560
     * @return array an array of all the categories the page course belongs to,
561
     * starting with the immediately containing category, and working out to
562
     * the top-level category. This may be the empty array if we are in the
563
     * front page course.
564
     */
1469 ariadna 565
    protected function magic_get_categories()
566
    {
1 efrain 567
        $this->ensure_categories_loaded();
568
        return $this->_categories;
569
    }
570
 
571
    /**
572
     * Please do not call this method directly, use the ->context syntax. {@link moodle_page::__get()}.
573
     * @return context the main context to which this page belongs.
574
     */
1469 ariadna 575
    protected function magic_get_context()
576
    {
1 efrain 577
        global $CFG;
578
        if (is_null($this->_context)) {
579
            if (CLI_SCRIPT or NO_MOODLE_COOKIES) {
580
                // Cli scripts work in system context, do not annoy devs with debug info.
581
                // Very few scripts do not use cookies, we can safely use system as default context there.
582
            } else if (AJAX_SCRIPT && $CFG->debugdeveloper) {
583
                // Throw exception inside AJAX script in developer mode, otherwise the debugging message may be missed.
584
                throw new coding_exception('$PAGE->context was not set. You may have forgotten '
1469 ariadna 585
                    . 'to call require_login() or $PAGE->set_context()');
1 efrain 586
            } else {
587
                debugging('Coding problem: $PAGE->context was not set. You may have forgotten '
1469 ariadna 588
                    . 'to call require_login() or $PAGE->set_context(). The page may not display '
589
                    . 'correctly as a result');
1 efrain 590
            }
591
            $this->_context = context_system::instance();
592
        }
593
        return $this->_context;
594
    }
595
 
596
    /**
597
     * Please do not call this method directly, use the ->pagetype syntax. {@link moodle_page::__get()}.
598
     * @return string e.g. 'my-index' or 'mod-quiz-attempt'.
599
     */
1469 ariadna 600
    protected function magic_get_pagetype()
601
    {
1 efrain 602
        global $CFG;
603
        if (is_null($this->_pagetype) || isset($CFG->pagepath)) {
604
            $this->initialise_default_pagetype();
605
        }
606
        return $this->_pagetype;
607
    }
608
 
609
    /**
610
     * Please do not call this method directly, use the ->pagetype syntax. {@link moodle_page::__get()}.
611
     * @return string The id to use on the body tag, uses {@link magic_get_pagetype()}.
612
     */
1469 ariadna 613
    protected function magic_get_bodyid()
614
    {
615
        return 'page-' . $this->pagetype;
1 efrain 616
    }
617
 
618
    /**
619
     * Please do not call this method directly, use the ->pagelayout syntax. {@link moodle_page::__get()}.
620
     * @return string the general type of page this is. For example 'standard', 'popup', 'home'.
621
     *      Allows the theme to display things differently, if it wishes to.
622
     */
1469 ariadna 623
    protected function magic_get_pagelayout()
624
    {
1 efrain 625
        return $this->_pagelayout;
626
    }
627
 
628
    /**
629
     * Please do not call this method directly, use the ->layout_options syntax. {@link moodle_page::__get()}.
630
     * @return array returns arrays with options for layout file
631
     */
1469 ariadna 632
    protected function magic_get_layout_options()
633
    {
1 efrain 634
        if (!is_array($this->_layout_options)) {
1441 ariadna 635
            $this->_layout_options = $this->theme->pagelayout_options($this->pagelayout);
1 efrain 636
        }
637
        return $this->_layout_options;
638
    }
639
 
640
    /**
641
     * Please do not call this method directly, use the ->subpage syntax. {@link moodle_page::__get()}.
642
     * @return string The subpage identifier, if any.
643
     */
1469 ariadna 644
    protected function magic_get_subpage()
645
    {
1 efrain 646
        return $this->_subpage;
647
    }
648
 
649
    /**
650
     * Please do not call this method directly, use the ->bodyclasses syntax. {@link moodle_page::__get()}.
651
     * @return string the class names to put on the body element in the HTML.
652
     */
1469 ariadna 653
    protected function magic_get_bodyclasses()
654
    {
1 efrain 655
        return implode(' ', array_keys($this->_bodyclasses));
656
    }
657
 
658
    /**
659
     * Please do not call this method directly, use the ->title syntax. {@link moodle_page::__get()}.
660
     * @return string the title that should go in the <head> section of the HTML of this page.
661
     */
1469 ariadna 662
    protected function magic_get_title()
663
    {
1 efrain 664
        return $this->_title;
665
    }
666
 
667
    /**
668
     * Please do not call this method directly, use the ->heading syntax. {@link moodle_page::__get()}.
669
     * @return string the main heading that should be displayed at the top of the <body>.
670
     */
1469 ariadna 671
    protected function magic_get_heading()
672
    {
1 efrain 673
        return $this->_heading;
674
    }
675
 
676
    /**
677
     * Please do not call this method directly, use the ->heading syntax. {@link moodle_page::__get()}.
678
     * @return string The menu (or actions) to display in the heading
679
     */
1469 ariadna 680
    protected function magic_get_headingmenu()
681
    {
1 efrain 682
        return $this->_headingmenu;
683
    }
684
 
685
    /**
686
     * Please do not call this method directly, use the ->docspath syntax. {@link moodle_page::__get()}.
687
     * @return string the path to the Documentation for this page.
688
     */
1469 ariadna 689
    protected function magic_get_docspath()
690
    {
1 efrain 691
        if (is_string($this->_docspath)) {
692
            return $this->_docspath;
693
        } else {
694
            return str_replace('-', '/', $this->pagetype);
695
        }
696
    }
697
 
698
    /**
699
     * Please do not call this method directly, use the ->url syntax. {@link moodle_page::__get()}.
700
     * @return moodle_url the clean URL required to load the current page. (You
701
     * should normally use this in preference to $ME or $FULLME.)
702
     */
1469 ariadna 703
    protected function magic_get_url()
704
    {
1 efrain 705
        global $FULLME;
706
        if (is_null($this->_url)) {
1469 ariadna 707
            debugging('This page did not call $PAGE->set_url(...). Using ' . s($FULLME), DEBUG_DEVELOPER);
1 efrain 708
            $this->_url = new moodle_url($FULLME);
709
            // Make sure the guessed URL cannot lead to dangerous redirects.
710
            $this->_url->remove_params('sesskey');
711
        }
712
        return new moodle_url($this->_url); // Return a clone for safety.
713
    }
714
 
715
    /**
716
     * The list of alternate versions of this page.
717
     * @return array mime type => object with ->url and ->title.
718
     */
1469 ariadna 719
    protected function magic_get_alternateversions()
720
    {
1 efrain 721
        return $this->_alternateversions;
722
    }
723
 
724
    /**
725
     * Please do not call this method directly, use the ->blocks syntax. {@link moodle_page::__get()}.
726
     * @return block_manager the blocks manager object for this page.
727
     */
1469 ariadna 728
    protected function magic_get_blocks()
729
    {
1 efrain 730
        global $CFG;
731
        if (is_null($this->_blocks)) {
732
            if (!empty($CFG->blockmanagerclass)) {
733
                if (!empty($CFG->blockmanagerclassfile)) {
734
                    require_once($CFG->blockmanagerclassfile);
735
                }
736
                $classname = $CFG->blockmanagerclass;
737
            } else {
738
                $classname = 'block_manager';
739
            }
740
            $this->_blocks = new $classname($this);
741
        }
742
        return $this->_blocks;
743
    }
744
 
745
    /**
746
     * Please do not call this method directly, use the ->requires syntax. {@link moodle_page::__get()}.
747
     * @return page_requirements_manager tracks the JavaScript, CSS files, etc. required by this page.
748
     */
1469 ariadna 749
    protected function magic_get_requires()
750
    {
1 efrain 751
        if (is_null($this->_requires)) {
752
            $this->_requires = new page_requirements_manager();
753
        }
754
        return $this->_requires;
755
    }
756
 
757
    /**
758
     * Please do not call this method directly, use the ->cacheable syntax. {@link moodle_page::__get()}.
759
     * @return bool can this page be cached by the user's browser.
760
     */
1469 ariadna 761
    protected function magic_get_cacheable()
762
    {
1 efrain 763
        return $this->_cacheable;
764
    }
765
 
766
    /**
767
     * Please do not call this method directly, use the ->focuscontrol syntax. {@link moodle_page::__get()}.
768
     * @return string the id of the HTML element to be focused when the page has loaded.
769
     */
1469 ariadna 770
    protected function magic_get_focuscontrol()
771
    {
1 efrain 772
        return $this->_focuscontrol;
773
    }
774
 
775
    /**
776
     * Please do not call this method directly, use the ->button syntax. {@link moodle_page::__get()}.
777
     * @return string the HTML to go where the Turn editing on button normally goes.
778
     */
1469 ariadna 779
    protected function magic_get_button()
780
    {
1 efrain 781
        return $this->_button;
782
    }
783
 
784
    /**
785
     * Please do not call this method directly, use the ->theme syntax. {@link moodle_page::__get()}.
786
     * @return theme_config the initialised theme for this page.
787
     */
1469 ariadna 788
    protected function magic_get_theme()
789
    {
1 efrain 790
        if (is_null($this->_theme)) {
791
            $this->initialise_theme_and_output();
792
        }
793
        return $this->_theme;
794
    }
795
 
796
    /**
797
     * Returns an array of minipulations or false if there are none to make.
798
     *
799
     * @since Moodle 2.5.1 2.6
800
     * @return bool|array
801
     */
1469 ariadna 802
    protected function magic_get_blockmanipulations()
803
    {
1 efrain 804
        if (!right_to_left()) {
805
            return false;
806
        }
807
        if (is_null($this->_theme)) {
808
            $this->initialise_theme_and_output();
809
        }
810
        return $this->_theme->blockrtlmanipulations;
811
    }
812
 
813
    /**
814
     * Please do not call this method directly, use the ->devicetypeinuse syntax. {@link moodle_page::__get()}.
815
     * @return string The device type being used.
816
     */
1469 ariadna 817
    protected function magic_get_devicetypeinuse()
818
    {
1 efrain 819
        if (empty($this->_devicetypeinuse)) {
820
            $this->_devicetypeinuse = core_useragent::get_user_device_type();
821
        }
822
        return $this->_devicetypeinuse;
823
    }
824
 
825
    /**
826
     * Please do not call this method directly use the ->periodicrefreshdelay syntax
827
     * {@link moodle_page::__get()}
828
     * @return int The periodic refresh delay to use with meta refresh
829
     */
1469 ariadna 830
    protected function magic_get_periodicrefreshdelay()
831
    {
1 efrain 832
        return $this->_periodicrefreshdelay;
833
    }
834
 
835
    /**
836
     * Please do not call this method directly use the ->opencontainers syntax. {@link moodle_page::__get()}
837
     * @return xhtml_container_stack tracks XHTML tags on this page that have been opened but not closed.
838
     *      mainly for internal use by the rendering code.
839
     */
1469 ariadna 840
    protected function magic_get_opencontainers()
841
    {
1 efrain 842
        if (is_null($this->_opencontainers)) {
843
            $this->_opencontainers = new xhtml_container_stack();
844
        }
845
        return $this->_opencontainers;
846
    }
847
 
848
    /**
849
     * Return the navigation object
850
     * @return global_navigation
851
     */
1469 ariadna 852
    protected function magic_get_navigation()
853
    {
1 efrain 854
        if ($this->_navigation === null) {
855
            $this->_navigation = new global_navigation($this);
856
        }
857
        return $this->_navigation;
858
    }
859
 
860
    /**
861
     * Return a navbar object
862
     * @return navbar
863
     */
1469 ariadna 864
    protected function magic_get_navbar()
865
    {
1 efrain 866
        if ($this->_navbar === null) {
867
            $this->_navbar = new navbar($this);
868
        }
869
        return $this->_navbar;
870
    }
871
 
872
    /**
873
     * Returns the settings navigation object
874
     * @return settings_navigation
875
     */
1469 ariadna 876
    protected function magic_get_settingsnav()
877
    {
1 efrain 878
        if ($this->_settingsnav === null) {
879
            $this->_settingsnav = new settings_navigation($this);
880
            $this->_settingsnav->initialise();
881
        }
882
        return $this->_settingsnav;
883
    }
884
 
885
    /**
886
     * Returns the flat navigation object
887
     * @return flat_navigation
888
     */
1469 ariadna 889
    protected function magic_get_flatnav()
890
    {
1 efrain 891
        if ($this->_flatnav === null) {
892
            $this->_flatnav = new flat_navigation($this);
893
            $this->_flatnav->initialise();
894
        }
895
        return $this->_flatnav;
896
    }
897
 
898
    /**
899
     * Returns the activity header object
900
     * @return activity_header
901
     */
1469 ariadna 902
    protected function magic_get_activityheader(): activity_header
903
    {
1 efrain 904
        global $USER;
905
        if ($this->_activityheader === null) {
906
            $class = activity_header::class;
907
            // Try and load a custom class first.
908
            if (class_exists("mod_{$this->activityname}\\output\\activity_header")) {
909
                $class = "mod_{$this->activityname}\\output\\activity_header";
910
            }
911
 
912
            $this->_activityheader = new $class($this, $USER);
913
        }
914
        return $this->_activityheader;
915
    }
916
 
917
    /**
918
     * Returns the secondary navigation object
919
     *
920
     * @return secondary
921
     */
1469 ariadna 922
    protected function magic_get_secondarynav()
923
    {
1 efrain 924
        if ($this->_secondarynav === null) {
925
            $class = 'core\navigation\views\secondary';
1441 ariadna 926
 
927
            // Check whether activity defines its own secondary navigation.
1 efrain 928
            if (class_exists("mod_{$this->activityname}\\navigation\\views\\secondary")) {
929
                $class = "mod_{$this->activityname}\\navigation\\views\\secondary";
930
            }
931
 
932
            $this->_secondarynav = new $class($this);
933
            $this->_secondarynav->initialise();
934
        }
935
        return $this->_secondarynav;
936
    }
937
 
938
    /**
939
     * Returns the primary navigation object
940
     * @return primary
941
     */
1469 ariadna 942
    protected function magic_get_primarynav()
943
    {
1 efrain 944
        if ($this->_primarynav === null) {
945
            $this->_primarynav = new primary($this);
946
            $this->_primarynav->initialise();
947
        }
948
        return $this->_primarynav;
949
    }
950
 
951
    /**
952
     * Returns the primary navigation object
953
     * @return primaryoutput
954
     */
1469 ariadna 955
    protected function magic_get_primarynavcombined()
956
    {
1 efrain 957
        if ($this->_primarynavcombined === null) {
958
            $this->_primarynavcombined = new primaryoutput($this);
959
        }
960
        return $this->_primarynavcombined;
961
    }
962
 
963
    /**
964
     * Returns request IP address.
965
     *
966
     * @return string IP address or null if unknown
967
     */
1469 ariadna 968
    protected function magic_get_requestip()
969
    {
1 efrain 970
        return getremoteaddr(null);
971
    }
972
 
973
    /**
974
     * Returns the origin of current request.
975
     *
976
     * Note: constants are not required because we need to use these values in logging and reports.
977
     *
978
     * @return string 'web', 'ws', 'cli', 'restore', etc.
979
     */
1469 ariadna 980
    protected function magic_get_requestorigin()
981
    {
1 efrain 982
        if (class_exists('restore_controller', false) && restore_controller::is_executing()) {
983
            return 'restore';
984
        }
985
 
986
        if (WS_SERVER) {
987
            return 'ws';
988
        }
989
 
990
        if (CLI_SCRIPT) {
991
            return 'cli';
992
        }
993
 
994
        return 'web';
995
    }
996
 
997
    /**
998
     * PHP overloading magic to make the $PAGE->course syntax work by redirecting
999
     * it to the corresponding $PAGE->magic_get_course() method if there is one, and
1000
     * throwing an exception if not.
1001
     *
1002
     * @param string $name property name
1003
     * @return mixed
1004
     * @throws coding_exception
1005
     */
1469 ariadna 1006
    public function __get($name)
1007
    {
1 efrain 1008
        $getmethod = 'magic_get_' . $name;
1009
        if (method_exists($this, $getmethod)) {
1010
            return $this->$getmethod();
1011
        } else {
1012
            throw new coding_exception('Unknown property ' . $name . ' of $PAGE.');
1013
        }
1014
    }
1015
 
1016
    /**
1017
     * PHP overloading magic to catch obvious coding errors.
1018
     *
1019
     * This method has been created to catch obvious coding errors where the
1020
     * developer has tried to set a page property using $PAGE->key = $value.
1021
     * In the moodle_page class all properties must be set using the appropriate
1022
     * $PAGE->set_something($value) method.
1023
     *
1024
     * @param string $name property name
1025
     * @param mixed $value Value
1026
     * @return void Throws exception if field not defined in page class
1027
     * @throws coding_exception
1028
     */
1469 ariadna 1029
    public function __set($name, $value)
1030
    {
1 efrain 1031
        if (method_exists($this, 'set_' . $name)) {
1032
            throw new coding_exception('Invalid attempt to modify page object', "Use \$PAGE->set_$name() instead.");
1033
        } else {
1034
            throw new coding_exception('Invalid attempt to modify page object', "Unknown property $name");
1035
        }
1036
    }
1037
 
1038
    // Other information getting methods ==========================================.
1039
 
1040
    /**
1041
     * Returns instance of page renderer
1042
     *
1043
     * @param string $component name such as 'core', 'mod_forum' or 'qtype_multichoice'.
1044
     * @param string $subtype optional subtype such as 'news' resulting to 'mod_forum_news'
1045
     * @param string $target one of rendering target constants
1046
     * @return renderer_base
1047
     */
1469 ariadna 1048
    public function get_renderer($component, $subtype = null, $target = null)
1049
    {
1 efrain 1050
        if ($this->pagelayout === 'maintenance') {
1051
            // If the page is using the maintenance layout then we're going to force target to maintenance.
1052
            // This leads to a special core renderer that is designed to block access to API's that are likely unavailable for this
1053
            // page layout.
1054
            $target = RENDERER_TARGET_MAINTENANCE;
1055
        }
1056
        return $this->magic_get_theme()->get_renderer($this, $component, $subtype, $target);
1057
    }
1058
 
1059
    /**
1060
     * Checks to see if there are any items on the navbar object
1061
     *
1062
     * @return bool true if there are, false if not
1063
     */
1469 ariadna 1064
    public function has_navbar()
1065
    {
1 efrain 1066
        if ($this->_navbar === null) {
1067
            $this->_navbar = new navbar($this);
1068
        }
1069
        return $this->_navbar->has_items();
1070
    }
1071
 
1072
    /**
1073
     * Switches from the regular requirements manager to the fragment requirements manager to
1074
     * capture all necessary JavaScript to display a chunk of HTML such as an mform. This is for use
1075
     * by the get_fragment() web service and not for use elsewhere.
1076
     */
1469 ariadna 1077
    public function start_collecting_javascript_requirements()
1078
    {
1 efrain 1079
        // Check that the requirements manager has not already been switched.
1080
        if (get_class($this->_requires) == 'fragment_requirements_manager') {
1081
            throw new coding_exception('JavaScript collection has already been started.');
1082
        }
1083
        // The header needs to have been called to flush out the generic JavaScript for the page. This allows only
1084
        // JavaScript for the fragment to be collected. _wherethemewasinitialised is set when header() is called.
1085
        if (!empty($this->_wherethemewasinitialised)) {
1086
            // Change the current requirements manager over to the fragment manager to capture JS.
1087
            $this->savedrequires = $this->_requires;
1088
            $this->_requires = new fragment_requirements_manager();
1089
        } else {
1090
            throw new coding_exception('$OUTPUT->header() needs to be called before collecting JavaScript requirements.');
1091
        }
1092
    }
1093
 
1094
    /**
1095
     * Switches back from collecting fragment JS requirement to the original requirement manager
1096
     */
1469 ariadna 1097
    public function end_collecting_javascript_requirements()
1098
    {
1 efrain 1099
        if ($this->savedrequires === null) {
1100
            throw new coding_exception('JavaScript collection has not been started.');
1101
        }
1102
        $this->_requires = $this->savedrequires;
1103
        $this->savedrequires = null;
1104
    }
1105
 
1106
    /**
1107
     * Should the current user see this page in editing mode.
1108
     * That is, are they allowed to edit this page, and are they currently in
1109
     * editing mode.
1110
     * @return bool
1111
     */
1469 ariadna 1112
    public function user_is_editing()
1113
    {
1 efrain 1114
        global $USER;
1115
        return !empty($USER->editing) && $this->user_allowed_editing();
1116
    }
1117
 
1118
    /**
1119
     * Does the user have permission to edit blocks on this page.
1120
     * Can be forced to false by calling the force_lock_all_blocks() method.
1121
     *
1122
     * @return bool
1123
     */
1469 ariadna 1124
    public function user_can_edit_blocks()
1125
    {
1 efrain 1126
        return $this->_forcelockallblocks ? false : has_capability($this->_blockseditingcap, $this->_context);
1127
    }
1128
 
1129
    /**
1130
     * Does the user have permission to see this page in editing mode.
1131
     * @return bool
1132
     */
1469 ariadna 1133
    public function user_allowed_editing()
1134
    {
1 efrain 1135
        return has_any_capability($this->all_editing_caps(), $this->_context);
1136
    }
1137
 
1138
    /**
1139
     * Get a description of this page. Normally displayed in the footer in developer debug mode.
1140
     * @return string
1141
     */
1469 ariadna 1142
    public function debug_summary()
1143
    {
1 efrain 1144
        $summary = '';
1145
        $summary .= 'General type: ' . $this->pagelayout . '. ';
1146
        if (!during_initial_install()) {
1147
            $summary .= 'Context ' . $this->context->get_context_name() . ' (context id ' . $this->_context->id . '). ';
1148
        }
1149
        $summary .= 'Page type ' . $this->pagetype .  '. ';
1150
        if ($this->subpage) {
1151
            $summary .= 'Sub-page ' . $this->subpage .  '. ';
1152
        }
1441 ariadna 1153
 
1154
        // Display deprecated icons in the console (if any).
1155
        $summary .= <<< EOF
1156
            <script type="text/javascript">
1157
            //<![CDATA[
1158
            document.querySelectorAll('.icon.deprecated').forEach((icon) => {
1159
                window.console.warn("Deprecated icon found: " + icon.className);
1160
            });
1161
            //]]>
1162
            </script>
1163
        EOF;
1164
 
1 efrain 1165
        return $summary;
1166
    }
1167
 
1168
    // Setter methods =============================================================.
1169
 
1170
    /**
1171
     * Set the state.
1172
     *
1173
     * The state must be one of that STATE_... constants, and the state is only allowed to advance one step at a time.
1174
     *
1175
     * @param int $state The new state.
1176
     * @throws coding_exception
1177
     */
1469 ariadna 1178
    public function set_state($state)
1179
    {
1 efrain 1180
        if ($state != $this->_state + 1 || $state > self::STATE_DONE) {
1181
            throw new coding_exception('Invalid state passed to moodle_page::set_state. We are in state ' .
1469 ariadna 1182
                $this->_state . ' and state ' . $state . ' was requested.');
1 efrain 1183
        }
1184
 
1185
        if ($state == self::STATE_PRINTING_HEADER) {
1186
            $this->starting_output();
1187
        }
1188
 
1189
        $this->_state = $state;
1190
    }
1191
 
1192
    /**
1193
     * Set the current course. This sets both $PAGE->course and $COURSE. It also
1194
     * sets the right theme and locale.
1195
     *
1196
     * Normally you don't need to call this function yourself, require_login will
1197
     * call it for you if you pass a $course to it. You can use this function
1198
     * on pages that do need to call require_login().
1199
     *
1200
     * Sets $PAGE->context to the course context, if it is not already set.
1201
     *
1202
     * @param stdClass $course the course to set as the global course.
1203
     * @throws coding_exception
1204
     */
1469 ariadna 1205
    public function set_course($course)
1206
    {
1 efrain 1207
        global $COURSE, $PAGE, $CFG, $SITE;
1208
 
1209
        if (empty($course->id)) {
1210
            throw new coding_exception('$course passed to moodle_page::set_course does not look like a proper course object.');
1211
        }
1212
 
1213
        $this->ensure_theme_not_set();
1214
 
1215
        if (!empty($this->_course->id) && $this->_course->id != $course->id) {
1216
            $this->_categories = null;
1217
        }
1218
 
1469 ariadna 1219
        $this->_course = clone ($course);
1 efrain 1220
 
1221
        if ($this === $PAGE) {
1222
            $COURSE = $this->_course;
1223
            moodle_setlocale();
1224
        }
1225
 
1226
        if (!$this->_context) {
1227
            $this->set_context(context_course::instance($this->_course->id));
1228
        }
1229
 
1230
        // Notify course format that this page is set for the course.
1231
        if ($this->_course->id != $SITE->id) {
1469 ariadna 1232
            require_once($CFG->dirroot . '/course/lib.php');
1 efrain 1233
            $courseformat = course_get_format($this->_course);
1469 ariadna 1234
            $this->add_body_class('format-' . $courseformat->get_format());
1 efrain 1235
            $courseformat->page_set_course($this);
1236
        } else {
1237
            $this->add_body_class('format-site');
1238
        }
1239
    }
1240
 
1241
    /**
1242
     * Set the main context to which this page belongs.
1243
     *
1244
     * @param ?context $context a context object. You normally get this with context_xxxx::instance().
1245
     */
1469 ariadna 1246
    public function set_context($context)
1247
    {
1 efrain 1248
        if ($context === null) {
1249
            // Extremely ugly hack which sets context to some value in order to prevent warnings,
1250
            // use only for core error handling!!!!
1251
            if (!$this->_context) {
1252
                $this->_context = context_system::instance();
1253
            }
1254
            return;
1255
        }
1256
        // Ideally we should set context only once.
1257
        if (isset($this->_context) && $context->id !== $this->_context->id) {
1258
            $current = $this->_context->contextlevel;
1259
            if ($current == CONTEXT_SYSTEM or $current == CONTEXT_COURSE) {
1260
                // Hmm - not ideal, but it might produce too many warnings due to the design of require_login.
1469 ariadna 1261
            } else if (
1262
                $current == CONTEXT_MODULE and ($parentcontext = $context->get_parent_context()) and
1263
                $this->_context->id == $parentcontext->id
1264
            ) {
1 efrain 1265
                // Hmm - most probably somebody did require_login() and after that set the block context.
1266
            } else {
1267
                // We do not want devs to do weird switching of context levels on the fly because we might have used
1268
                // the context already such as in text filter in page title.
1269
                debugging("Coding problem: unsupported modification of PAGE->context from {$current} to {$context->contextlevel}");
1270
            }
1271
        }
1272
 
1273
        $this->_context = $context;
1274
    }
1275
 
1276
    /**
1277
     * The course module that this page belongs to (if it does belong to one).
1278
     *
1279
     * @param stdClass|cm_info $cm a record from course_modules table or cm_info from get_fast_modinfo().
1280
     * @param stdClass $course
1281
     * @param stdClass $module
1282
     * @return void
1283
     * @throws coding_exception
1284
     */
1469 ariadna 1285
    public function set_cm($cm, $course = null, $module = null)
1286
    {
1 efrain 1287
        global $DB, $CFG, $SITE;
1288
 
1289
        if (!isset($cm->id) || !isset($cm->course)) {
1290
            throw new coding_exception('Invalid $cm. It has to be instance of cm_info or record from the course_modules table.');
1291
        }
1292
 
1293
        if (!$this->_course || $this->_course->id != $cm->course) {
1294
            if (!$course) {
1295
                $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
1296
            }
1297
            if ($course->id != $cm->course) {
1298
                throw new coding_exception('The course you passed to $PAGE->set_cm does not correspond to the $cm.');
1299
            }
1300
            $this->set_course($course);
1301
        }
1302
 
1303
        // Make sure we have a $cm from get_fast_modinfo as this contains activity access details.
1304
        if (!($cm instanceof cm_info)) {
1305
            $modinfo = get_fast_modinfo($this->_course);
1306
            $cm = $modinfo->get_cm($cm->id);
1307
        }
1308
        $this->_cm = $cm;
1309
 
1310
        // Unfortunately the context setting is a mess.
1311
        // Let's try to work around some common block problems and show some debug messages.
1312
        if (empty($this->_context) or $this->_context->contextlevel != CONTEXT_BLOCK) {
1313
            $context = context_module::instance($cm->id);
1314
            $this->set_context($context);
1315
        }
1316
 
1317
        if ($module) {
1318
            $this->set_activity_record($module);
1319
        }
1320
 
1321
        // Notify course format that this page is set for the course module.
1322
        if ($this->_course->id != $SITE->id) {
1469 ariadna 1323
            require_once($CFG->dirroot . '/course/lib.php');
1 efrain 1324
            course_get_format($this->_course)->page_set_cm($this);
1325
        }
1326
    }
1327
 
1328
    /**
1329
     * Sets the activity record. This could be a row from the main table for a
1330
     * module. For instance if the current module (cm) is a forum this should be a row
1331
     * from the forum table.
1332
     *
1333
     * @param stdClass $module A row from the main database table for the module that this page belongs to.
1334
     * @throws coding_exception
1335
     */
1469 ariadna 1336
    public function set_activity_record($module)
1337
    {
1 efrain 1338
        if (is_null($this->_cm)) {
1339
            throw new coding_exception('You cannot call $PAGE->set_activity_record until after $PAGE->cm has been set.');
1340
        }
1341
        if ($module->id != $this->_cm->instance || $module->course != $this->_course->id) {
1342
            throw new coding_exception('The activity record does not seem to correspond to the cm that has been set.');
1343
        }
1344
        $this->_module = $module;
1345
    }
1346
 
1347
    /**
1348
     * Sets the pagetype to use for this page.
1349
     *
1350
     * Normally you do not need to set this manually, it is automatically created
1351
     * from the script name. However, on some pages this is overridden.
1352
     * For example the page type for course/view.php includes the course format,
1353
     * for example 'course-view-weeks'. This gets used as the id attribute on
1354
     * <body> and also for determining which blocks are displayed.
1355
     *
1356
     * @param string $pagetype e.g. 'my-index' or 'mod-quiz-attempt'.
1357
     */
1469 ariadna 1358
    public function set_pagetype($pagetype)
1359
    {
1 efrain 1360
        $this->_pagetype = $pagetype;
1361
    }
1362
 
1363
    /**
1364
     * Sets the layout to use for this page.
1365
     *
1366
     * The page layout determines how the page will be displayed, things such as
1367
     * block regions, content areas, etc are controlled by the layout.
1368
     * The theme in use for the page will determine that the layout contains.
1369
     *
1370
     * This properly defaults to 'base', so you only need to call this function if
1371
     * you want something different. The exact range of supported layouts is specified
1372
     * in the standard theme.
1373
     *
1374
     * For an idea of the common page layouts see
1375
     * {@link https://docs.moodle.org/dev/Themes_overview#Layouts}
1376
     * But please keep in mind that it may be (and normally is) out of date.
1377
     * The only place to find an accurate up-to-date list of the page layouts
1378
     * available for your version of Moodle is {@link theme/base/config.php}
1379
     *
1380
     * @param string $pagelayout the page layout this is. For example 'popup', 'home'.
1381
     */
1469 ariadna 1382
    public function set_pagelayout($pagelayout)
1383
    {
1 efrain 1384
        global $SESSION;
1385
 
1386
        if (!empty($SESSION->forcepagelayout)) {
1387
            $this->_pagelayout = $SESSION->forcepagelayout;
1388
        } else {
1389
            // Uncomment this to debug theme pagelayout issues like missing blocks.
1390
            // if (!empty($this->_wherethemewasinitialised) && $pagelayout != $this->_pagelayout)
1391
            //     debugging('Page layout has already been set and cannot be changed.', DEBUG_DEVELOPER);
1392
            $this->_pagelayout = $pagelayout;
1393
        }
1394
    }
1395
 
1396
    /**
1397
     * If context->id and pagetype are not enough to uniquely identify this page,
1398
     * then you can set a subpage id as well. For example, the tags page sets
1399
     *
1400
     * @param string $subpage an arbitrary identifier that, along with context->id
1401
     *      and pagetype, uniquely identifies this page.
1402
     */
1469 ariadna 1403
    public function set_subpage($subpage)
1404
    {
1 efrain 1405
        if (empty($subpage)) {
1406
            $this->_subpage = '';
1407
        } else {
1408
            $this->_subpage = $subpage;
1409
        }
1410
    }
1411
 
1412
    /**
1413
     * Force set secondary_nav. Useful in cases where we dealing with non course modules. e.g. blocks, tools.
1414
     * @param secondary $nav
1415
     */
1469 ariadna 1416
    public function set_secondarynav(secondary $nav)
1417
    {
1 efrain 1418
        $this->_secondarynav = $nav;
1419
    }
1420
 
1421
    /**
1422
     * Adds a CSS class to the body tag of the page.
1423
     *
1424
     * @param string $class add this class name ot the class attribute on the body tag.
1425
     * @throws coding_exception
1426
     */
1469 ariadna 1427
    public function add_body_class($class)
1428
    {
1 efrain 1429
        if ($this->_state > self::STATE_BEFORE_HEADER) {
1430
            throw new coding_exception('Cannot call moodle_page::add_body_class after output has been started.');
1431
        }
1432
        $this->_bodyclasses[$class] = 1;
1433
    }
1434
 
1435
    /**
1436
     * Adds an array of body classes to the body tag of this page.
1437
     *
1438
     * @param array $classes this utility method calls add_body_class for each array element.
1439
     */
1469 ariadna 1440
    public function add_body_classes($classes)
1441
    {
1 efrain 1442
        foreach ($classes as $class) {
1443
            $this->add_body_class($class);
1444
        }
1445
    }
1446
 
1447
    /**
1448
     * Sets the title for the page.
1449
     *
1450
     * This is normally used within the title tag in the head of the page.
1451
     *
1452
     * Some tips for providing a meaningful page title:
1453
     * - The page title must be accurate and informative.
1454
     * - If the page causes a change of context (e.g. a search functionality), it should describe the result or change of context
1455
     *   to the user.
1456
     * - It should be concise.
1457
     * - If possible, it should uniquely identify the page.
1458
     * - The most identifying information should come first. (e.g. Submit assignment | Assignment | Moodle)
1459
     *
1460
     * For more information, see
1461
     * {@link https://www.w3.org/WAI/WCAG21/Understanding/page-titled Understanding Success Criterion 2.4.2: Page Titled}
1462
     *
1463
     * @param string $title the title that should go in the <head> section of the HTML of this page.
1464
     * @param bool $appendsitename Appends site name at the end of the given title. It is encouraged to append the site name as this
1465
     *                              especially helps with accessibility. If it's necessary to override this, please keep in mind
1466
     *                              to ensure that the title provides a concise summary of the page being displayed.
1467
     */
1469 ariadna 1468
    public function set_title($title, bool $appendsitename = true)
1469
    {
1 efrain 1470
        global $CFG;
1471
 
1472
        $title = format_string($title);
1473
        $title = strip_tags($title);
1474
        $title = str_replace('"', '&quot;', $title);
1475
 
1476
        if ($appendsitename) {
1477
            // Append the site name at the end of the page title.
1478
            $sitenamedisplay = 'shortname';
1479
            if (!empty($CFG->sitenameintitle)) {
1480
                $sitenamedisplay = $CFG->sitenameintitle;
1481
            }
1482
            $site = get_site();
1483
            if (empty(trim($site->{$sitenamedisplay} ?? ''))) {
1484
                // If for some reason the site name is not yet set, fall back to 'Moodle'.
1485
                $title .= self::TITLE_SEPARATOR . 'Moodle';
1486
            } else {
1487
                $title .= self::TITLE_SEPARATOR . format_string($site->{$sitenamedisplay});
1488
            }
1489
        }
1490
 
1491
        $this->_title = $title;
1492
    }
1493
 
1494
    /**
1495
     * Sets the heading to use for the page.
1496
     * This is normally used as the main heading at the top of the content.
1497
     *
1498
     * @param string $heading the main heading that should be displayed at the top of the <body>.
1499
     * @param bool $applyformatting apply format_string() - by default true.
1500
     * @param bool $clean whether the heading should be cleaned or not when no formatting is applied - by default true.
1501
     */
1469 ariadna 1502
    public function set_heading($heading, bool $applyformatting = true, bool $clean = true)
1503
    {
1 efrain 1504
        $this->_heading = $applyformatting ? format_string($heading) : ($clean ? clean_text($heading) : $heading);
1505
    }
1506
 
1507
    /**
1508
     * Sets some HTML to use next to the heading {@link moodle_page::set_heading()}
1509
     *
1510
     * @param string $menu The menu/content to show in the heading
1511
     */
1469 ariadna 1512
    public function set_headingmenu($menu)
1513
    {
1 efrain 1514
        $this->_headingmenu = $menu;
1515
    }
1516
 
1517
    /**
1518
     * Set the course category this page belongs to manually.
1519
     *
1520
     * This automatically sets $PAGE->course to be the site course. You cannot
1521
     * use this method if you have already set $PAGE->course - in that case,
1522
     * the category must be the one that the course belongs to. This also
1523
     * automatically sets the page context to the category context.
1524
     *
1525
     * @param int $categoryid The id of the category to set.
1526
     * @throws coding_exception
1527
     */
1469 ariadna 1528
    public function set_category_by_id($categoryid)
1529
    {
1 efrain 1530
        global $SITE;
1531
        if (!is_null($this->_course)) {
1532
            throw new coding_exception('Course has already been set. You cannot change the category now.');
1533
        }
1534
        if (is_array($this->_categories)) {
1535
            throw new coding_exception('Course category has already been set. You cannot to change it now.');
1536
        }
1537
        $this->ensure_theme_not_set();
1538
        $this->set_course($SITE);
1539
        $this->load_category($categoryid);
1540
        $this->set_context(context_coursecat::instance($categoryid));
1541
    }
1542
 
1543
    /**
1544
     * Set a different path to use for the 'Documentation for this page' link.
1545
     *
1546
     * By default, it uses the pagetype, which is normally the same as the
1547
     * script name. So, for example, for mod/quiz/attempt.php, pagetype is
1548
     * mod-quiz-attempt, and so docspath is mod/quiz/attempt.
1549
     *
1550
     * @param string $path the path to use at the end of the moodle docs URL.
1551
     */
1469 ariadna 1552
    public function set_docs_path($path)
1553
    {
1 efrain 1554
        $this->_docspath = $path;
1555
    }
1556
 
1557
    /**
1558
     * You should call this method from every page to set the URL that should be used to return to this page.
1559
     *
1560
     * Used, for example, by the blocks editing UI to know where to return the
1561
     * user after an action.
1562
     * For example, course/view.php does:
1563
     *      $id = optional_param('id', 0, PARAM_INT);
1564
     *      $PAGE->set_url('/course/view.php', array('id' => $id));
1565
     *
1566
     * @param moodle_url|string $url URL relative to $CFG->wwwroot or {@link moodle_url} instance
1567
     * @param array $params parameters to add to the URL
1568
     * @throws coding_exception
1569
     */
1469 ariadna 1570
    public function set_url($url, ?array $params = null)
1571
    {
1 efrain 1572
        global $CFG;
1573
 
1574
        if (is_string($url) && strpos($url, 'http') !== 0) {
1575
            if (strpos($url, '/') === 0) {
1576
                // Add the wwwroot to the relative url.
1577
                $url = $CFG->wwwroot . $url;
1578
            } else {
1579
                throw new coding_exception('Invalid parameter $url, has to be full url or in shortened form starting with /.');
1580
            }
1581
        }
1582
 
1583
        $this->_url = new moodle_url($url, $params);
1584
 
1585
        $fullurl = $this->_url->out_omit_querystring();
1586
        if (strpos($fullurl, "$CFG->wwwroot/") !== 0) {
1587
            debugging('Most probably incorrect set_page() url argument, it does not match the wwwroot!');
1588
        }
1589
        $shorturl = str_replace("$CFG->wwwroot/", '', $fullurl);
1590
 
1591
        if (is_null($this->_pagetype)) {
1592
            $this->initialise_default_pagetype($shorturl);
1593
        }
1594
    }
1595
 
1596
    /**
1597
     * Make sure page URL does not contain the given URL parameter.
1598
     *
1599
     * This should not be necessary if the script has called set_url properly.
1600
     * However, in some situations like the block editing actions; when the URL
1601
     * has been guessed, it will contain dangerous block-related actions.
1602
     * Therefore, the blocks code calls this function to clean up such parameters
1603
     * before doing any redirect.
1604
     *
1605
     * @param string $param the name of the parameter to make sure is not in the
1606
     * page URL.
1607
     */
1469 ariadna 1608
    public function ensure_param_not_in_url($param)
1609
    {
1 efrain 1610
        $this->_url->remove_params($param);
1611
    }
1612
 
1613
    /**
1614
     * Sets an alternative version of this page.
1615
     *
1616
     * There can be alternate versions of some pages (for example an RSS feed version).
1617
     * Call this method for each alternative version available.
1618
     * For each alternative version a link will be included in the <head> tag.
1619
     *
1620
     * @param string $title The title to give the alternate version.
1621
     * @param string|moodle_url $url The URL of the alternate version.
1622
     * @param string $mimetype The mime-type of the alternate version.
1623
     * @throws coding_exception
1624
     */
1469 ariadna 1625
    public function add_alternate_version($title, $url, $mimetype)
1626
    {
1 efrain 1627
        if ($this->_state > self::STATE_BEFORE_HEADER) {
1628
            throw new coding_exception('Cannot call moodle_page::add_alternate_version after output has been started.');
1629
        }
1630
        $alt = new stdClass;
1631
        $alt->title = $title;
1632
        $alt->url = $url;
1633
        $this->_alternateversions[$mimetype] = $alt;
1634
    }
1635
 
1636
    /**
1637
     * Specify a form control should be focused when the page has loaded.
1638
     *
1639
     * @param string $controlid the id of the HTML element to be focused.
1640
     */
1469 ariadna 1641
    public function set_focuscontrol($controlid)
1642
    {
1 efrain 1643
        $this->_focuscontrol = $controlid;
1644
    }
1645
 
1646
    /**
1647
     * Specify a fragment of HTML that goes where the 'Turn editing on' button normally goes.
1648
     *
1649
     * @param string $html the HTML to display there.
1650
     */
1469 ariadna 1651
    public function set_button($html)
1652
    {
1 efrain 1653
        $this->_button = $html;
1654
    }
1655
 
1656
    /**
1657
     * Set the capability that allows users to edit blocks on this page.
1658
     *
1659
     * Normally the default of 'moodle/site:manageblocks' is used, but a few
1660
     * pages like the My Moodle page need to use a different capability
1661
     * like 'moodle/my:manageblocks'.
1662
     *
1663
     * @param string $capability a capability.
1664
     */
1469 ariadna 1665
    public function set_blocks_editing_capability($capability)
1666
    {
1 efrain 1667
        $this->_blockseditingcap = $capability;
1668
    }
1669
 
1670
    /**
1671
     * Some pages let you turn editing on for reasons other than editing blocks.
1672
     * If that is the case, you can pass other capabilities that let the user
1673
     * edit this page here.
1674
     *
1675
     * @param string|array $capability either a capability, or an array of capabilities.
1676
     */
1469 ariadna 1677
    public function set_other_editing_capability($capability)
1678
    {
1 efrain 1679
        if (is_array($capability)) {
1680
            $this->_othereditingcaps = array_unique($this->_othereditingcaps + $capability);
1681
        } else {
1682
            $this->_othereditingcaps[] = $capability;
1683
        }
1684
    }
1685
 
1686
    /**
1687
     * Sets whether the browser should cache this page or not.
1688
     *
1689
     * @param bool $cacheable can this page be cached by the user's browser.
1690
     */
1469 ariadna 1691
    public function set_cacheable($cacheable)
1692
    {
1 efrain 1693
        $this->_cacheable = $cacheable;
1694
    }
1695
 
1696
    /**
1697
     * Sets the page to periodically refresh
1698
     *
1699
     * This function must be called before $OUTPUT->header has been called or
1700
     * a coding exception will be thrown.
1701
     *
1702
     * @param int $delay Sets the delay before refreshing the page, if set to null refresh is cancelled.
1703
     * @throws coding_exception
1704
     */
1469 ariadna 1705
    public function set_periodic_refresh_delay($delay = null)
1706
    {
1 efrain 1707
        if ($this->_state > self::STATE_BEFORE_HEADER) {
1708
            throw new coding_exception('You cannot set a periodic refresh delay after the header has been printed');
1709
        }
1710
        if ($delay === null) {
1711
            $this->_periodicrefreshdelay = null;
1712
        } else if (is_int($delay)) {
1713
            $this->_periodicrefreshdelay = $delay;
1714
        }
1715
    }
1716
 
1717
    /**
1718
     * Force this page to use a particular theme.
1719
     *
1720
     * Please use this cautiously.
1721
     * It is only intended to be used by the themes selector admin page.
1722
     *
1723
     * @param string $themename the name of the theme to use.
1724
     */
1469 ariadna 1725
    public function force_theme($themename)
1726
    {
1 efrain 1727
        $this->ensure_theme_not_set();
1728
        $this->_theme = theme_config::load($themename);
1729
    }
1730
 
1731
    /**
1732
     * Reload theme settings.
1733
     *
1734
     * This is used when we need to reset settings
1735
     * because they are now double cached in theme.
1736
     */
1469 ariadna 1737
    public function reload_theme()
1738
    {
1 efrain 1739
        if (!is_null($this->_theme)) {
1740
            $this->_theme = theme_config::load($this->_theme->name);
1741
        }
1742
    }
1743
 
1744
    /**
1745
     * Remove access to editing/moving on all blocks on a page.
1746
     * This overrides any capabilities and is intended only for pages where no user (including admins) should be able to
1747
     * modify blocks on the page (eg My Courses).
1748
     *
1749
     * @return void
1750
     */
1469 ariadna 1751
    public function force_lock_all_blocks(): void
1752
    {
1 efrain 1753
        $this->_forcelockallblocks = true;
1754
    }
1755
 
1756
    /**
1757
     * Allows to 'serialize' the edited page information and store it in the session cache
1758
     *
1759
     * Due to Moodle architectural decision and non-SPA approach, a lot of page setup is
1760
     * happening in the actual page php file, for example, setting course/cm/context,
1761
     * setting layout and pagetype, requiring capabilities, setting specific block editing
1762
     * capabilities.
1763
     *
1764
     * When storing this information in the session cache we can pass the pagehash (cache key)
1765
     * as an argument to web services in AJAX requests and retrieve all data associated with
1766
     * the page without actually executing PHP code on that page.
1767
     *
1768
     * @return string|null
1769
     */
1469 ariadna 1770
    public function get_edited_page_hash(): ?string
1771
    {
1 efrain 1772
        global $SESSION;
1773
        if (!$this->user_is_editing()) {
1774
            return null;
1775
        }
1469 ariadna 1776
 
1468 ariadna 1777
        // Check if session is still active to prevent mutation after close
1469 ariadna 1778
        // If we're in shutdown phase or session was already closed, don't modify session
1779
        if (
1780
            defined('ABORT_AFTER_CONFIG') || !session_id() || session_status() !== PHP_SESSION_ACTIVE ||
1781
            !\core\session\manager::is_session_active()
1782
        ) {
1468 ariadna 1783
            return null;
1784
        }
1469 ariadna 1785
 
1 efrain 1786
        $url = new moodle_url($this->url);
1787
        $url->set_anchor(null);
1788
        $data = [
1789
            'contextid' => $this->context->id,
1790
            'url' => $url->out_as_local_url(false),
1791
        ];
1792
        if (($cm = $this->cm) && $cm->id) {
1793
            $data['cmid'] = $cm->id;
1794
        } else if (($course = $this->course) && $course->id) {
1795
            $data['courseid'] = $course->id;
1796
        }
1797
        $keys = ['pagelayout', 'pagetype', 'subpage'];
1798
        foreach ($keys as $key) {
1799
            if ("{$this->$key}" !== "") {
1800
                $data[$key] = $this->$key;
1801
            }
1802
        }
1803
        if ($this->_blockseditingcap !== 'moodle/site:manageblocks') {
1804
            $data['bcap'] = $this->_blockseditingcap;
1805
        }
1806
        if (!empty($this->_othereditingcaps)) {
1807
            $data['caps'] = $this->_othereditingcaps;
1808
        }
1809
        if ($this->_forcelockallblocks) {
1810
            $data['forcelock'] = true;
1811
        }
1812
        $hash = md5(json_encode($data + ['sesskey' => sesskey()]));
1813
        $SESSION->editedpages = ($SESSION->editedpages ?? []);
1814
        $SESSION->editedpages[$hash] = $data;
1815
        return $hash;
1816
    }
1817
 
1818
    /**
1819
     * Retrieves a page that is being edited from the session cache
1820
     *
1821
     * {@see self::get_edited_page_hash()}
1822
     *
1823
     * @param string $hash
1824
     * @param int $strictness
1825
     * @return self|null
1826
     */
1469 ariadna 1827
    public static function retrieve_edited_page(string $hash, $strictness = IGNORE_MISSING): ?self
1828
    {
1 efrain 1829
        global $CFG, $SESSION;
1830
        $data = $SESSION->editedpages[$hash] ?? null;
1469 ariadna 1831
        if (
1832
            !$data || !is_array($data)
1833
            || $hash !== md5(json_encode($data + ['sesskey' => sesskey()]))
1834
        ) {
1 efrain 1835
            // This can happen if the session cache becomes corrupt or the user logged out and back
1836
            // in in another window and changed their session. Refreshing the page will generate
1837
            // and store the correct page hash.
1838
            if ($strictness === MUST_EXIST) {
1839
                throw new moodle_exception('editedpagenotfound');
1840
            }
1841
            return null;
1842
        }
1843
 
1844
        if (!empty($CFG->moodlepageclass)) {
1845
            if (!empty($CFG->moodlepageclassfile)) {
1846
                require_once($CFG->moodlepageclassfile);
1847
            }
1848
            $classname = $CFG->moodlepageclass;
1849
        } else {
1850
            $classname = self::class;
1851
        }
1852
        /** @var moodle_page $page */
1853
        $page = new $classname();
1854
        $page->set_context(context::instance_by_id($data['contextid']));
1855
        if (array_key_exists('cmid', $data)) {
1856
            [$course, $cm] = get_course_and_cm_from_cmid($data['cmid']);
1857
            $page->set_cm($cm, $course);
1858
        } else if (array_key_exists('courseid', $data)) {
1859
            $page->set_course(get_course($data['courseid']));
1860
        }
1861
        $page->set_url(new moodle_url($data['url']));
1862
        $keys = ['pagelayout', 'pagetype', 'subpage'];
1863
        foreach ($keys as $key) {
1864
            if (array_key_exists($key, $data)) {
1865
                $func = "set_{$key}";
1866
                $page->$func($data[$key]);
1867
            }
1868
        }
1869
        if (array_key_exists('bcap', $data)) {
1870
            $page->set_blocks_editing_capability($data['bcap']);
1871
        }
1872
        if (array_key_exists('caps', $data)) {
1873
            foreach ($data['caps'] as $cap) {
1874
                $page->set_other_editing_capability($cap);
1875
            }
1876
        }
1877
        if (array_key_exists('forcelock', $data)) {
1878
            $page->force_lock_all_blocks();
1879
        }
1880
        $page->blocks->add_custom_regions_for_pagetype($page->pagetype);
1881
        return $page;
1882
    }
1883
 
1884
    // Initialisation methods =====================================================
1885
    // These set various things up in a default way.
1886
 
1887
    /**
1888
     * This method is called when the page first moves out of the STATE_BEFORE_HEADER
1889
     * state. This is our last change to initialise things.
1890
     */
1469 ariadna 1891
    protected function starting_output()
1892
    {
1 efrain 1893
        global $CFG;
1894
 
1895
        if (!during_initial_install()) {
1896
            $this->blocks->load_blocks();
1897
            if (empty($this->_block_actions_done)) {
1898
                $this->_block_actions_done = true;
1899
                if ($this->blocks->process_url_actions($this)) {
1900
                    redirect($this->url->out(false));
1901
                }
1902
            }
1903
            $this->blocks->create_all_block_instances();
1904
        }
1905
 
1906
        // If maintenance mode is on, change the page header.
1907
        if (!empty($CFG->maintenance_enabled)) {
1908
            $this->set_button('<a href="' . $CFG->wwwroot . '/' . $CFG->admin .
1469 ariadna 1909
                '/settings.php?section=maintenancemode">' . get_string('maintenancemode', 'admin') .
1910
                '</a> ' . $this->button);
1 efrain 1911
 
1912
            $this->set_title(get_string('maintenancemode', 'admin'));
1913
        }
1914
 
1915
        $this->initialise_standard_body_classes();
1916
    }
1917
 
1918
    /**
1919
     * Method for use by Moodle core to set up the theme. Do not
1920
     * use this in your own code.
1921
     *
1922
     * Make sure the right theme for this page is loaded. Tell our
1923
     * blocks_manager about the theme block regions, and then, if
1924
     * we are $PAGE, set up the global $OUTPUT.
1925
     *
1926
     * @return void
1927
     */
1469 ariadna 1928
    public function initialise_theme_and_output()
1929
    {
1 efrain 1930
        global $OUTPUT, $PAGE, $SITE, $CFG;
1931
 
1932
        if (!empty($this->_wherethemewasinitialised)) {
1933
            return;
1934
        }
1935
 
1936
        if (!during_initial_install()) {
1937
            // Detect PAGE->context mess.
1938
            $this->magic_get_context();
1939
        }
1940
 
1941
        if (!$this->_course && !during_initial_install()) {
1942
            $this->set_course($SITE);
1943
        }
1944
 
1945
        if (is_null($this->_theme)) {
1946
            $themename = $this->resolve_theme();
1947
            $this->_theme = theme_config::load($themename);
1948
        }
1949
 
1950
        $this->_theme->setup_blocks($this->pagelayout, $this->blocks);
1951
 
1952
        if ($this === $PAGE) {
1953
            $target = null;
1954
            if ($this->pagelayout === 'maintenance') {
1955
                // If the page is using the maintenance layout then we're going to force target to maintenance.
1956
                // This leads to a special core renderer that is designed to block access to API's that are likely unavailable for this
1957
                // page layout.
1958
                $target = RENDERER_TARGET_MAINTENANCE;
1959
            }
1960
            $OUTPUT = $this->get_renderer('core', null, $target);
1961
        }
1962
 
1963
        if (!during_initial_install()) {
1964
            $filtermanager = filter_manager::instance();
1965
            $filtermanager->setup_page_for_globally_available_filters($this);
1966
        }
1967
 
1968
        $this->_wherethemewasinitialised = debug_backtrace();
1969
    }
1970
 
1971
    /**
1972
     * For diagnostic/debugging purposes, find where the theme setup was triggered.
1973
     *
1974
     * @return null|array null if theme not yet setup. Stacktrace if it was.
1975
     */
1469 ariadna 1976
    public function get_where_theme_was_initialised()
1977
    {
1 efrain 1978
        return $this->_wherethemewasinitialised;
1979
    }
1980
 
1981
    /**
1982
     * Reset the theme and output for a new context. This only makes sense from
1983
     * external::validate_context(). Do not cheat.
1984
     */
1469 ariadna 1985
    public function reset_theme_and_output()
1986
    {
1 efrain 1987
        global $COURSE, $SITE;
1988
 
1469 ariadna 1989
        $COURSE = clone ($SITE);
1 efrain 1990
        $this->_theme = null;
1991
        $this->_wherethemewasinitialised = null;
1992
        $this->_course = null;
1993
        $this->_cm = null;
1994
        $this->_module = null;
1995
        $this->_context = null;
1996
    }
1997
 
1998
    /**
1999
     * Work out the theme this page should use.
2000
     *
2001
     * This depends on numerous $CFG settings, and the properties of this page.
2002
     *
2003
     * @return string the name of the theme that should be used on this page.
2004
     */
1469 ariadna 2005
    protected function resolve_theme()
2006
    {
1 efrain 2007
        global $CFG, $USER, $SESSION;
2008
 
2009
        if (empty($CFG->themeorder)) {
2010
            $themeorder = array('course', 'category', 'session', 'user', 'cohort', 'site');
2011
        } else {
2012
            $themeorder = $CFG->themeorder;
2013
            // Just in case, make sure we always use the site theme if nothing else matched.
2014
            $themeorder[] = 'site';
2015
        }
2016
 
2017
        $mnetpeertheme = '';
2018
        $mnetvarsok = isset($CFG->mnet_localhost_id) && isset($USER->mnethostid);
2019
        if (isloggedin() and $mnetvarsok and $USER->mnethostid != $CFG->mnet_localhost_id) {
1469 ariadna 2020
            require_once($CFG->dirroot . '/mnet/peer.php');
1 efrain 2021
            $mnetpeer = new mnet_peer();
2022
            $mnetpeer->set_id($USER->mnethostid);
2023
            if ($mnetpeer->force_theme == 1 && $mnetpeer->theme != '') {
2024
                $mnetpeertheme = $mnetpeer->theme;
2025
            }
2026
        }
2027
 
2028
        foreach ($themeorder as $themetype) {
2029
 
2030
            switch ($themetype) {
2031
                case 'course':
2032
                    if (!empty($CFG->allowcoursethemes) && !empty($this->_course->theme)) {
2033
                        return $this->_course->theme;
2034
                    }
1469 ariadna 2035
                    break;
1 efrain 2036
 
2037
                case 'category':
2038
                    if (!empty($CFG->allowcategorythemes) && !empty($this->_course)) {
2039
                        $categories = $this->categories;
2040
                        foreach ($categories as $category) {
2041
                            if (!empty($category->theme)) {
2042
                                return $category->theme;
2043
                            }
2044
                        }
2045
                    }
1469 ariadna 2046
                    break;
1 efrain 2047
 
2048
                case 'session':
2049
                    if (!empty($SESSION->theme)) {
2050
                        return $SESSION->theme;
2051
                    }
1469 ariadna 2052
                    break;
1 efrain 2053
 
2054
                case 'user':
2055
                    if (!empty($CFG->allowuserthemes) && !empty($USER->theme)) {
2056
                        if ($mnetpeertheme) {
2057
                            return $mnetpeertheme;
2058
                        } else {
2059
                            return $USER->theme;
2060
                        }
2061
                    }
1469 ariadna 2062
                    break;
1 efrain 2063
 
2064
                case 'cohort':
2065
                    if (!empty($CFG->allowcohortthemes) && !empty($USER->cohorttheme)) {
2066
                        return $USER->cohorttheme;
2067
                    }
1469 ariadna 2068
                    break;
1 efrain 2069
 
2070
                case 'site':
2071
                    if ($mnetpeertheme) {
2072
                        return $mnetpeertheme;
2073
                    }
2074
 
2075
                    // Use theme if it is set in config.
2076
                    if (!empty($CFG->theme)) {
2077
                        return $CFG->theme;
2078
                    }
2079
                    // Use the overall default theme.
2080
                    return theme_config::DEFAULT_THEME;
2081
            }
2082
        }
2083
 
2084
        // We should most certainly have resolved a theme by now. Something has gone wrong.
2085
        debugging('Error resolving the theme to use for this page.', DEBUG_DEVELOPER);
2086
        return theme_config::DEFAULT_THEME;
2087
    }
2088
 
2089
 
2090
    /**
2091
     * Sets ->pagetype from the script name. For example, if the script that was
2092
     * run is mod/quiz/view.php, ->pagetype will be set to 'mod-quiz-view'.
2093
     *
2094
     * @param string $script the path to the script that should be used to
2095
     * initialise ->pagetype. If not passed the $SCRIPT global will be used.
2096
     * If legacy code has set $CFG->pagepath that will be used instead, and a
2097
     * developer warning issued.
2098
     */
1469 ariadna 2099
    protected function initialise_default_pagetype($script = null)
2100
    {
1 efrain 2101
        global $CFG, $SCRIPT;
2102
 
2103
        if (isset($CFG->pagepath)) {
2104
            debugging('Some code appears to have set $CFG->pagepath. That was a horrible deprecated thing. ' .
1469 ariadna 2105
                'Don\'t do it! Try calling $PAGE->set_pagetype() instead.');
1 efrain 2106
            $script = $CFG->pagepath;
2107
            unset($CFG->pagepath);
2108
        }
2109
 
2110
        if (is_null($script)) {
2111
            $script = ltrim($SCRIPT ?? '', '/');
2112
            $len = strlen($CFG->admin);
2113
            if (substr($script, 0, $len) == $CFG->admin) {
2114
                $script = 'admin' . substr($script, $len);
2115
            }
2116
        }
2117
 
2118
        $path = str_replace('.php', '', $script);
2119
        if (substr($path, -1) == '/') {
2120
            $path .= 'index';
2121
        }
2122
 
2123
        if (empty($path) || $path == 'index') {
2124
            $this->_pagetype = 'site-index';
2125
        } else {
2126
            $this->_pagetype = str_replace('/', '-', $path);
2127
        }
2128
    }
2129
 
2130
    /**
2131
     * Initialises the CSS classes that will be added to body tag of the page.
2132
     *
2133
     * The function is responsible for adding all of the critical CSS classes
2134
     * that describe the current page, and its state.
2135
     * This includes classes that describe the following for example:
2136
     *    - Current language
2137
     *    - Language direction
2138
     *    - YUI CSS initialisation
2139
     *    - Pagelayout
2140
     * These are commonly used in CSS to target specific types of pages.
2141
     */
1469 ariadna 2142
    protected function initialise_standard_body_classes()
2143
    {
1 efrain 2144
        global $CFG, $USER;
2145
 
2146
        $pagetype = $this->pagetype;
2147
        if ($pagetype == 'site-index') {
2148
            $this->_legacyclass = 'course';
2149
        } else if (substr($pagetype, 0, 6) == 'admin-') {
2150
            $this->_legacyclass = 'admin';
2151
        }
2152
        $this->add_body_class($this->_legacyclass);
2153
 
2154
        $pathbits = explode('-', trim($pagetype));
2155
        for ($i = 1; $i < count($pathbits); $i++) {
2156
            $this->add_body_class('path-' . join('-', array_slice($pathbits, 0, $i)));
2157
        }
2158
 
2159
        $this->add_body_classes(core_useragent::get_browser_version_classes());
2160
        $this->add_body_class('dir-' . get_string('thisdirection', 'langconfig'));
2161
        $this->add_body_class('lang-' . current_language());
2162
        $this->add_body_class('yui-skin-sam'); // Make YUI happy, if it is used.
2163
        $this->add_body_class('yui3-skin-sam'); // Make YUI3 happy, if it is used.
2164
        $this->add_body_class($this->url_to_class_name($CFG->wwwroot));
2165
 
2166
        // Extra class describing current page layout.
2167
        $this->add_body_class('pagelayout-' . $this->_pagelayout);
2168
 
2169
        if (!during_initial_install()) {
2170
            $this->add_body_class('course-' . $this->_course->id);
2171
            $this->add_body_class('context-' . $this->_context->id);
2172
        }
2173
 
2174
        if (!empty($this->_cm)) {
2175
            $this->add_body_class('cmid-' . $this->_cm->id);
2176
            $this->add_body_class('cm-type-' . $this->_cm->modname);
2177
        }
2178
 
2179
        if (!empty($CFG->allowcategorythemes) && !empty($this->_course)) {
2180
            $this->ensure_category_loaded();
2181
            foreach ($this->_categories as $catid => $notused) {
2182
                $this->add_body_class('category-' . $catid);
2183
            }
2184
        } else {
2185
            $catid = 0;
2186
            if (is_array($this->_categories)) {
2187
                $catids = array_keys($this->_categories);
2188
                $catid = reset($catids);
2189
            } else if (!empty($this->_course->category)) {
2190
                $catid = $this->_course->category;
2191
            }
2192
            if ($catid) {
2193
                $this->add_body_class('category-' . $catid);
2194
            }
2195
        }
2196
 
2197
        if (!isloggedin()) {
2198
            $this->add_body_class('notloggedin');
2199
        }
2200
 
2201
        if ($this->user_is_editing()) {
2202
            $this->add_body_class('editing');
2203
            if (optional_param('bui_moveid', false, PARAM_INT)) {
2204
                $this->add_body_class('blocks-moving');
2205
            }
2206
        }
2207
 
2208
        if (!empty($CFG->blocksdrag)) {
2209
            $this->add_body_class('drag');
2210
        }
2211
 
2212
        if ($this->_devicetypeinuse != 'default') {
2213
            $this->add_body_class($this->_devicetypeinuse . 'theme');
2214
        }
2215
 
2216
        if (!empty($CFG->themedesignermode)) {
2217
            $this->add_body_class('themedesignermode');
2218
        }
2219
 
2220
        // Add class for behat site to apply behat related fixes.
2221
        if (defined('BEHAT_SITE_RUNNING')) {
2222
            $this->add_body_class('behat-site');
2223
        }
2224
    }
2225
 
2226
    /**
2227
     * Loads the activity record for the current CM object associated with this
2228
     * page.
2229
     *
2230
     * This will load {@link moodle_page::$_module} with a row from the related
2231
     * module table in the database.
2232
     * For instance if {@link moodle_page::$_cm} is a forum then a row from the
2233
     * forum table will be loaded.
2234
     */
1469 ariadna 2235
    protected function load_activity_record()
2236
    {
1 efrain 2237
        global $DB;
2238
        if (is_null($this->_cm)) {
2239
            return;
2240
        }
2241
        $this->_module = $DB->get_record($this->_cm->modname, array('id' => $this->_cm->instance));
2242
    }
2243
 
2244
    /**
2245
     * This function ensures that the category of the current course has been
2246
     * loaded, and if not, the function loads it now.
2247
     *
2248
     * @return void
2249
     * @throws coding_exception
2250
     */
1469 ariadna 2251
    protected function ensure_category_loaded()
2252
    {
1 efrain 2253
        if (is_array($this->_categories)) {
2254
            return; // Already done.
2255
        }
2256
        if (is_null($this->_course)) {
2257
            throw new coding_exception('Attempt to get the course category for this page before the course was set.');
2258
        }
2259
        if ($this->_course->category == 0) {
2260
            $this->_categories = array();
2261
        } else {
2262
            $this->load_category($this->_course->category);
2263
        }
2264
    }
2265
 
2266
    /**
2267
     * Loads the requested category into the pages categories array.
2268
     *
2269
     * @param int $categoryid
2270
     * @throws moodle_exception
2271
     */
1469 ariadna 2272
    protected function load_category($categoryid)
2273
    {
1 efrain 2274
        global $DB;
2275
        $category = $DB->get_record('course_categories', array('id' => $categoryid));
2276
        if (!$category) {
1441 ariadna 2277
            throw new moodle_exception('unknowncategory', a: $categoryid);
1 efrain 2278
        }
2279
        $this->_categories[$category->id] = $category;
2280
        $parentcategoryids = explode('/', trim($category->path, '/'));
2281
        array_pop($parentcategoryids);
2282
        foreach (array_reverse($parentcategoryids) as $catid) {
2283
            $this->_categories[$catid] = null;
2284
        }
2285
    }
2286
 
2287
    /**
2288
     * Ensures that the category the current course is within, as well as all of
2289
     * its parent categories, have been loaded.
2290
     *
2291
     * @return void
2292
     */
1469 ariadna 2293
    protected function ensure_categories_loaded()
2294
    {
1 efrain 2295
        global $DB;
2296
        $this->ensure_category_loaded();
2297
        if (!is_null(end($this->_categories))) {
2298
            return; // Already done.
2299
        }
2300
        $idstoload = array_keys($this->_categories);
2301
        array_shift($idstoload);
2302
        $categories = $DB->get_records_list('course_categories', 'id', $idstoload);
2303
        foreach ($idstoload as $catid) {
2304
            $this->_categories[$catid] = $categories[$catid];
2305
        }
2306
    }
2307
 
2308
    /**
2309
     * Ensure the theme has not been loaded yet. If it has an exception is thrown.
2310
     *
2311
     * @throws coding_exception
2312
     */
1469 ariadna 2313
    protected function ensure_theme_not_set()
2314
    {
1 efrain 2315
        // This is explicitly allowed for webservices though which may process many course contexts in a single request.
2316
        if (WS_SERVER) {
2317
            return;
2318
        }
2319
 
2320
        if (!is_null($this->_theme)) {
1469 ariadna 2321
            throw new coding_exception(
2322
                'The theme has already been set up for this page ready for output. ' .
1 efrain 2323
                    'Therefore, you can no longer change the theme, or anything that might affect what ' .
2324
                    'the current theme is, for example, the course.',
1469 ariadna 2325
                'Stack trace when the theme was set up: ' . format_backtrace($this->_wherethemewasinitialised)
2326
            );
1 efrain 2327
        }
2328
    }
2329
 
2330
    /**
2331
     * Converts the provided URL into a CSS class that be used within the page.
2332
     * This is primarily used to add the wwwroot to the body tag as a CSS class.
2333
     *
2334
     * @param string $url
2335
     * @return string
2336
     */
1469 ariadna 2337
    protected function url_to_class_name($url)
2338
    {
1 efrain 2339
        $bits = parse_url($url);
2340
        $class = str_replace('.', '-', $bits['host']);
2341
        if (!empty($bits['port'])) {
2342
            $class .= '--' . $bits['port'];
2343
        }
2344
        if (!empty($bits['path'])) {
2345
            $path = trim($bits['path'], '/');
2346
            if ($path) {
2347
                $class .= '--' . str_replace('/', '-', $path);
2348
            }
2349
        }
2350
        return $class;
2351
    }
2352
 
2353
    /**
2354
     * Combines all of the required editing caps for the page and returns them
2355
     * as an array.
2356
     *
2357
     * @return array
2358
     */
1469 ariadna 2359
    protected function all_editing_caps()
2360
    {
1 efrain 2361
        $caps = $this->_othereditingcaps;
2362
        $caps[] = $this->_blockseditingcap;
2363
        return $caps;
2364
    }
2365
 
2366
    /**
2367
     * Returns true if the page URL has beem set.
2368
     *
2369
     * @return bool
2370
     */
1469 ariadna 2371
    public function has_set_url()
2372
    {
2373
        return ($this->_url !== null);
1 efrain 2374
    }
2375
 
2376
    /**
2377
     * Gets set when the block actions for the page have been processed.
2378
     *
2379
     * @param bool $setting
2380
     */
1469 ariadna 2381
    public function set_block_actions_done($setting = true)
2382
    {
1 efrain 2383
        $this->_block_actions_done = $setting;
2384
    }
2385
 
2386
    /**
2387
     * Are popup notifications allowed on this page?
2388
     * Popup notifications may be disallowed in situations such as while upgrading or completing a quiz
2389
     *
2390
     * @return bool true if popup notifications may be displayed
2391
     */
1469 ariadna 2392
    public function get_popup_notification_allowed()
2393
    {
1 efrain 2394
        return $this->_popup_notification_allowed;
2395
    }
2396
 
2397
    /**
2398
     * Allow or disallow popup notifications on this page. Popups are allowed by default.
2399
     *
2400
     * @param bool $allowed true if notifications are allowed. False if not allowed. They are allowed by default.
2401
     */
1469 ariadna 2402
    public function set_popup_notification_allowed($allowed)
2403
    {
1 efrain 2404
        $this->_popup_notification_allowed = $allowed;
2405
    }
2406
 
2407
    /**
2408
     * Returns the block region having made any required theme manipulations.
2409
     *
2410
     * @since Moodle 2.5.1 2.6
2411
     * @param string $region
2412
     * @return string
2413
     */
1469 ariadna 2414
    public function apply_theme_region_manipulations($region)
2415
    {
1 efrain 2416
        if ($this->blockmanipulations && isset($this->blockmanipulations[$region])) {
2417
            $regionwas = $region;
2418
            $regionnow = $this->blockmanipulations[$region];
2419
            if ($this->blocks->is_known_region($regionwas) && $this->blocks->is_known_region($regionnow)) {
2420
                // Both the before and after regions are known so we can swap them over.
2421
                return $regionnow;
2422
            }
2423
            // We didn't know about both, we won't swap them over.
2424
            return $regionwas;
2425
        }
2426
        return $region;
2427
    }
2428
 
2429
    /**
2430
     * Add a report node and a specific report to the navigation.
2431
     *
2432
     * @param int $userid The user ID that we are looking to add this report node to.
2433
     * @param array $nodeinfo Name and url of the final node that we are creating.
2434
     */
1469 ariadna 2435
    public function add_report_nodes($userid, $nodeinfo)
2436
    {
1 efrain 2437
        global $USER;
2438
        // Try to find the specific user node.
2439
        $newusernode = $this->navigation->find('user' . $userid, null);
2440
        $reportnode = null;
2441
        $navigationnodeerror =
1469 ariadna 2442
            'Could not find the navigation node requested. Please check that the node you are looking for exists.';
1 efrain 2443
        if ($userid != $USER->id  || $this->context->contextlevel == CONTEXT_COURSE) {
2444
            // Within a course context we need to properly indicate how we have come to the page,
2445
            // regardless of whether it's currently logged in user or not.
2446
            // Check that we have a valid node.
2447
            if (empty($newusernode)) {
2448
                // Throw an error if we ever reach here.
2449
                throw new coding_exception($navigationnodeerror);
2450
            }
2451
            // Add 'Reports' to the user node.
2452
            $reportnode = $newusernode->add(get_string('reports'));
2453
        } else {
2454
            // We are looking at our own profile.
2455
            $myprofilenode = $this->settingsnav->find('myprofile', null);
2456
            // Check that we do end up with a valid node.
2457
            if (empty($myprofilenode)) {
2458
                // Throw an error if we ever reach here.
2459
                throw new coding_exception($navigationnodeerror);
2460
            }
2461
            // Add 'Reports' to our node.
2462
            $reportnode = $myprofilenode->add(get_string('reports'));
2463
        }
2464
        // Finally add the report to the navigation tree.
1469 ariadna 2465
        $reportnode->add(
2466
            $nodeinfo['name'],
2467
            $nodeinfo['url'],
2468
            navigation_node::TYPE_CUSTOM,
2469
            null,
2470
            null,
2471
            new pix_icon('i/report', $nodeinfo['name'])
2472
        );
1 efrain 2473
    }
2474
 
2475
    /**
2476
     * Add some HTML to the list of actions to render in the header actions menu.
2477
     *
2478
     * @param string $html The HTML to add.
2479
     */
1469 ariadna 2480
    public function add_header_action(string $html): void
2481
    {
1 efrain 2482
        $this->_headeractions[] = $html;
2483
    }
2484
 
2485
    /**
2486
     * Get the list of HTML for actions to render in the header actions menu.
2487
     *
2488
     * @return string[]
2489
     */
1469 ariadna 2490
    public function get_header_actions(): array
2491
    {
1 efrain 2492
        return $this->_headeractions;
2493
    }
2494
 
2495
    /**
2496
     * Set the flag to indicate if the region main settings should be rendered as an action
2497
     * in the header actions menu rather than at the top of the content.
2498
     *
2499
     * @param bool $value If the settings should be in the header.
2500
     */
1469 ariadna 2501
    public function set_include_region_main_settings_in_header_actions(bool $value): void
2502
    {
1 efrain 2503
        $this->_regionmainsettingsinheader = $value;
2504
    }
2505
 
2506
    /**
2507
     * Check if the  region main settings should be rendered as an action in the header actions
2508
     * menu rather than at the top of the content.
2509
     *
2510
     * @return bool
2511
     */
1469 ariadna 2512
    public function include_region_main_settings_in_header_actions(): bool
2513
    {
1 efrain 2514
        return $this->_regionmainsettingsinheader;
2515
    }
2516
 
2517
    /**
2518
     * Set the flag to indicate if the secondary navigation should be rendered.
2519
     *
2520
     * @param bool $hassecondarynavigation If the secondary navigation should be rendered.
2521
     * @param bool $istablist When true, the navigation bar should be rendered and behave with a tablist ARIA role.
2522
     *                        If false, it's rendered with a menubar ARIA role. Defaults to false.
2523
     */
1469 ariadna 2524
    public function set_secondary_navigation(bool $hassecondarynavigation, bool $istablist = false): void
2525
    {
1 efrain 2526
        $this->_hassecondarynavigation = $hassecondarynavigation;
2527
        $this->_hastablistsecondarynavigation = $istablist;
2528
    }
2529
 
2530
    /**
2531
     * Check if the secondary navigation should be rendered.
2532
     *
2533
     * @return bool
2534
     */
1469 ariadna 2535
    public function has_secondary_navigation(): bool
2536
    {
1 efrain 2537
        return $this->_hassecondarynavigation;
2538
    }
2539
 
2540
    /**
2541
     * Check if the secondary navigation should be rendered with a tablist as opposed to a menubar.
2542
     *
2543
     * @return bool
2544
     */
1469 ariadna 2545
    public function has_tablist_secondary_navigation(): bool
2546
    {
1 efrain 2547
        return $this->_hastablistsecondarynavigation;
2548
    }
2549
 
2550
    /**
2551
     * Set the key of the secondary nav node to be activated.
2552
     *
2553
     * @param string $navkey the key of the secondary nav node to be activated.
2554
     */
1469 ariadna 2555
    public function set_secondary_active_tab(string $navkey): void
2556
    {
1 efrain 2557
        $this->_activekeysecondary = $navkey;
2558
    }
2559
 
2560
    /**
2561
     * The key of secondary nav node to activate.
2562
     *
2563
     * @return string|null get the key of the secondary node to activate.
2564
     */
1469 ariadna 2565
    public function get_secondary_active_tab(): ?string
2566
    {
1 efrain 2567
        return $this->_activekeysecondary;
2568
    }
2569
 
2570
    /**
2571
     * Set the key of the primary nav node to be activated.
2572
     *
2573
     * @param string $navkey
2574
     */
1469 ariadna 2575
    public function set_primary_active_tab(string $navkey): void
2576
    {
1 efrain 2577
        $this->_activenodeprimary = $navkey;
2578
    }
2579
 
2580
    /**
2581
     * The key of the primary nav node to activate.
2582
     *
2583
     * @return string|null get the key of the primary nav node to activate.
2584
     */
1469 ariadna 2585
    public function get_primary_activate_tab(): ?string
2586
    {
1 efrain 2587
        return $this->_activenodeprimary;
2588
    }
2589
 
2590
    /**
2591
     * Sets the navigation overflow state. This allows developers to turn off the overflow menu if they perhaps are using
2592
     * some other navigation to show settings.
2593
     *
2594
     * @param bool  $state  The state of whether to show the navigation overflow.
2595
     */
1469 ariadna 2596
    public function set_navigation_overflow_state(bool $state): void
2597
    {
1 efrain 2598
        $this->_navigationoverflow = $state;
2599
    }
2600
 
2601
    /**
2602
     * Gets the navigation overflow state.
2603
     *
2604
     * @return bool The navigation overflow state.
2605
     */
1469 ariadna 2606
    public function get_navigation_overflow_state(): bool
2607
    {
1 efrain 2608
        return $this->_navigationoverflow;
2609
    }
2610
 
2611
    /**
2612
     * Set the status for displaying the course index.
2613
     *
2614
     * @param bool $state
2615
     *     - `true` (default) if the course index should be shown.
2616
     *     - `false` if the course index should be hidden.
2617
     */
1469 ariadna 2618
    public function set_show_course_index(bool $state): void
2619
    {
1 efrain 2620
        $this->_showcourseindex = $state;
2621
    }
2622
 
2623
    /**
2624
     * Get the current status for displaying the course index.
2625
     *
2626
     * @return bool
2627
     */
1469 ariadna 2628
    public function get_show_course_index(): bool
2629
    {
1 efrain 2630
        return $this->_showcourseindex;
2631
    }
2632
}