aboutsummaryrefslogtreecommitdiff
path: root/lib/classes/PageLayout.php
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:07:19 +0200
committerJan-Hendrik Willms <tleilax+github@gmail.com>2021-07-22 16:19:12 +0200
commita3da1483a9e689846179159355badfec8073dbec (patch)
tree770dcca6bdf5f6f2a11b0e7fcbbeda6919a3fc52 /lib/classes/PageLayout.php
current code from svn, revision 62608
Diffstat (limited to 'lib/classes/PageLayout.php')
-rw-r--r--lib/classes/PageLayout.php743
1 files changed, 743 insertions, 0 deletions
diff --git a/lib/classes/PageLayout.php b/lib/classes/PageLayout.php
new file mode 100644
index 0000000..e40b22b
--- /dev/null
+++ b/lib/classes/PageLayout.php
@@ -0,0 +1,743 @@
+<?php
+# Lifter010: TODO
+/**
+ * PageLayout.php - configure the global page layout of Stud.IP
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * @author Elmar Ludwig
+ * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
+ * @category Stud.IP
+ */
+
+/**
+ * The PageLayout class provides utility functions to control the
+ * global page layout of Stud.IP. This includes the page title, the
+ * included CSS style sheets and JavaScript files. It replaces the
+ * "traditional" way of manipulating the page header via special
+ * global variables (like $CURRENT_PAGE and $_include_stylesheet).
+ *
+ * Each Stud.IP page should at least set the page title and help
+ * keyword (if a help page exists).
+ */
+class PageLayout
+{
+ /**
+ * current page title (defaults to $UNI_NAME_CLEAN)
+ */
+ private static $title;
+
+ /**
+ * current help keyword (defaults to 'Basis.Allgemeines')
+ */
+ private static $help_keyword;
+
+ /**
+ * base item path for tab view (defaults to active item in top nav)
+ */
+ private static $tab_navigation_path = false;
+
+ /**
+ * base item for tab view (defaults to active item in top nav)
+ */
+ private static $tab_navigation = false;
+
+ /**
+ * array of HTML HEAD elements (initialized with default set)
+ */
+ private static $head_elements = [];
+
+ /**
+ * extra HTML text included in the BODY element (initially empty)
+ */
+ private static $body_elements = '';
+
+ /**
+ * id of the body tag
+ */
+ private static $body_element_id = false;
+
+ /**
+ * determines whether the navigation header is displayed or not
+ */
+ private static $display_header = true;
+
+ /*
+ * Custom quicksearch on the page
+ */
+ private static $customQuicksearch;
+
+ /**
+ * Allow full screen mode toggle
+ */
+ private static $allow_full_screen = false;
+
+ /**
+ * Compatibility lookup table (old file -> new file/squeeze package)
+ *
+ * This is used for often used but "deprecated" assets that got
+ * renamed or moved into a squeeze package.
+ */
+ private static $compatibility_lookup = [
+ 'jquery/jquery.tablesorter.js' => 'tablesorter', // @since 3.4
+ ];
+
+ /**
+ * Initialize default page layout. This should only be called once
+ * from phplib_local.inc.php. Don't use this otherwise.
+ */
+ public static function initialize()
+ {
+ // Get software version
+ $v = StudipVersion::getStudipVersion(false);
+
+ // set favicon
+ self::addHeadElement('link', ['rel' => 'apple-touch-icon', 'sizes' => '180x180', 'href' => Assets::image_path('apple-touch-icon.png')]);
+ self::addHeadElement('link', ['rel' => 'icon', 'type' => 'image/svg+xml', 'href' => Assets::image_path('favicon.svg')]);
+ self::addHeadElement('link', ['rel' => 'icon', 'type' => 'image/png', 'sizes' => '64x64', 'href' => Assets::image_path('favicon-64x64.png')]);
+ self::addHeadElement('link', ['rel' => 'icon', 'type' => 'image/png', 'sizes' => '32x32', 'href' => Assets::image_path('favicon-32x32.png')]);
+ self::addHeadElement('link', ['rel' => 'icon', 'type' => 'image/png', 'sizes' => '16x16', 'href' => Assets::image_path('favicon-16x16.png')]);
+ self::addHeadElement('link', ['rel' => 'manifest', 'href' => Assets::image_path('manifest.json')]);
+ self::addHeadElement('link', ['rel' => 'mask-icon', 'href' => Assets::image_path('safari-pinned-tab.svg')]);
+ self::addHeadElement('link', ['rel' => 'preload', 'href' => Assets::url('fonts/LatoLatin/LatoLatin-Regular.woff2'), 'as' => 'font', 'type' => 'font/woff2', 'crossorigin' => 'anonymous']);
+ self::addHeadElement('link', ['rel' => 'preload', 'href' => Assets::url('fonts/LatoLatin/LatoLatin-Bold.woff2'), 'as' => 'font', 'type' => 'font/woff2', 'crossorigin' => 'anonymous']);
+ self::addHeadElement('meta', ['name' => 'TileColor', 'content' => '#2b5797']);
+ self::addHeadElement('meta', ['name' => 'TileImage', 'content' => Assets::image_path('mstile-144x144.png')]);
+ self::addHeadElement('meta', ['name' => 'msapplication-config', 'content' => Assets::image_path('browserconfig.xml')]);
+ self::addHeadElement('meta', ['name' => 'theme-color', 'content' => '#ffffff']);
+
+ // set initial width for mobile devices
+ self::addHeadElement('meta', ['name' => 'viewport', 'content' => 'width=device-width, initial-scale=1.0']);
+
+ self::addHeadElement('link', [
+ 'rel' => 'help',
+ 'href' => format_help_url('Basis.VerschiedenesFormat'),
+ 'class' => 'text-format',
+ 'title' => _('Hilfe zur Textformatierung')
+ ]);
+
+ self::addStylesheet('studip-base.css?v=' . $v, ['media' => 'screen']);
+ self::addScript('studip-base.js?v=' . $v);
+
+ self::addStylesheet('print.css?v=' . $v, ['media' => 'print']);
+
+ if (Config::get()->WYSIWYG) {
+ self::addStylesheet('studip-wysiwyg.css?v=' . $v);
+ self::addScript('studip-wysiwyg.js?v=' . $v);
+ }
+ }
+
+ /**
+ * Set the page title to the given text.
+ * @param string $title Page title
+ */
+ public static function setTitle($title)
+ {
+ self::$title = $title;
+ }
+
+ /**
+ * Returns whether a title has been set
+ * @return bool
+ */
+ public static function hasTitle()
+ {
+ return isset(self::$title);
+ }
+
+ /**
+ * Get the current page title (defaults to $UNI_NAME_CLEAN).
+ * @return string
+ */
+ public static function getTitle()
+ {
+ return isset(self::$title) ? self::$title :
+ (isset($GLOBALS['_html_head_title']) ? $GLOBALS['_html_head_title'] :
+ (isset($GLOBALS['CURRENT_PAGE']) ? $GLOBALS['CURRENT_PAGE'] :
+ Config::get()->UNI_NAME_CLEAN));
+ }
+
+ /**
+ * Set the help keyword to the given string.
+ */
+ public static function setHelpKeyword($help_keyword)
+ {
+ self::$help_keyword = $help_keyword;
+ }
+
+ /**
+ * Get the current help keyword (defaults to 'Basis.Allgemeines').
+ */
+ public static function getHelpKeyword()
+ {
+ return isset(self::$help_keyword) ? self::$help_keyword : 'Basis.Allgemeines';
+ }
+
+ /**
+ * Select which tabs (if any) should be displayed on the page. The
+ * argument specifies a navigation item in the tree whose children
+ * will form the first level of tabs. If $path is NULL, no tabs are
+ * displayed. The default setting is to use the active element in
+ * the top navigation.
+ *
+ * @param string $path path of navigation item for tabs or NULL
+ */
+ public static function setTabNavigation($path)
+ {
+ self::$tab_navigation_path = $path;
+ self::$tab_navigation = isset($path) ? Navigation::getItem($path) : NULL;
+ }
+
+ /**
+ * Returns the base navigation object (not its path) for the tabs.
+ * May return NULL if tab display is disabled.
+ */
+ public static function getTabNavigation()
+ {
+ if (self::$tab_navigation === false) {
+ self::$tab_navigation = Navigation::hasItem('/') ? Navigation::getItem('/')->activeSubNavigation() : null;
+ }
+
+ return self::$tab_navigation;
+ }
+
+ /**
+ * Returns the base navigation path for the tabs.
+ * May return NULL if tab display is disabled.
+ */
+ public static function getTabNavigationPath()
+ {
+ if (self::$tab_navigation_path === false && self::getTabNavigation()) {
+ foreach (self::getTabNavigation() as $subpath => $navigation) {
+ if ($navigation->isActive()) {
+ self::$tab_navigation_path = $subpath;
+ }
+ }
+ }
+ return self::$tab_navigation_path;
+ }
+
+ /**
+ * Add a STYLE element to the HTML HEAD section.
+ *
+ * @param string $content element contents
+ * @param string $media media types
+ */
+ public static function addStyle($content, $media = '')
+ {
+ $attr = [];
+ if($media) {
+ $attr = ['media' => $media];
+ }
+ self::addHeadElement('style', $attr, $content);
+ }
+
+ /**
+ * Add a style sheet LINK element to the HTML HEAD section.
+ *
+ * @param string $source style sheet URL or file in assets folder
+ * @param array $attributes additional attributes for LINK element
+ */
+ public static function addStylesheet($source, $attributes = [])
+ {
+ $attributes['rel'] = 'stylesheet';
+ $attributes['href'] = Assets::stylesheet_path($source);
+
+ self::addHeadElement('link', $attributes);
+ }
+
+ /**
+ * Remove a style sheet LINK element from the HTML HEAD section.
+ *
+ * @param string $source style sheet URL or file in assets folder
+ * @param array $attributes additional attributes for LINK element
+ */
+ public static function removeStylesheet($source, $attributes = [])
+ {
+ $attributes['rel'] = 'stylesheet';
+ $attributes['href'] = Assets::stylesheet_path($source);
+
+ self::removeHeadElement('link', $attributes);
+ }
+
+ /**
+ * Add a JavaScript SCRIPT element to the HTML HEAD section.
+ *
+ * @param string $source URL of JS file or file in assets folder
+ * @param array $attributes Additional parameters for the script tag
+ */
+ public static function addScript($source, $attributes = [])
+ {
+ // Check for compatibility lookup entry and rename file resp.
+ // add according squeeze package (if lookup element does not
+ // end in '.js').
+ if (array_key_exists($source, self::$compatibility_lookup)) {
+ $source = self::$compatibility_lookup[$source];
+ if (!preg_match('/\.js$/', $source)) {
+ self::addSqueezePackage($source);
+ return;
+ }
+ }
+
+ $attributes['src'] = Assets::javascript_path($source);
+
+ self::addHeadElement('script', $attributes, '');
+ }
+
+ /**
+ * Remove a JavaScript SCRIPT element from the HTML HEAD section.
+ *
+ * @param string $source URL of JS file or file in assets folder
+ * @param array $attributes Additional parameters for the script tag
+ */
+ public static function removeScript($source, $attributes = [])
+ {
+ $attributes['src'] = Assets::javascript_path($source);
+
+ self::removeHeadElement('script', $attributes);
+ }
+
+ /**
+ * Add an extra HTML element to the HTML HEAD section. This can be
+ * used to include RSS/ATOM feed links, META tags or other stuff.
+ * If $content is NULL, no closing tag is generated. If the element
+ * needs a closing tag (like SCRIPT) but should not have contents,
+ * pass the empty string as the third parameter.
+ *
+ * @param string $name element name (e.g. 'meta')
+ * @param array $attributes additional attributes for the element
+ * @param string $content element contents, if any
+ */
+ public static function addHeadElement($name, $attributes = [], $content = NULL)
+ {
+ self::$head_elements[] = compact('name', 'attributes', 'content');
+ }
+
+ /**
+ * Remove HTML elements from the HTML HEAD section. This method will
+ * remove all elements matching the given name and all the attributes.
+ *
+ * For example, to remove all META elements:
+ * PageLayout::removeHeadElement('meta');
+ *
+ * Remove all style sheet LINK elements:
+ * PageLayout::removeHeadElement('link', array('rel' => 'stylesheet'));
+ *
+ * Remove a particular style sheet LINK by href:
+ * PageLayout::removeHeadElement('link', array('href' => '...'));
+ */
+ public static function removeHeadElement($name, $attributes = [])
+ {
+ $result = [];
+
+ foreach (self::$head_elements as $element) {
+ $remove = false;
+
+ // match element name
+ if ($name === $element['name']) {
+ $remove = true;
+
+ // match element attributes
+ foreach ($attributes as $key => $value) {
+ if (!isset($element['attributes'][$key]) ||
+ $element['attributes'][$key] !== $value) {
+ $remove = false;
+ break;
+ }
+ }
+ }
+
+ if (!$remove) {
+ $result[] = $element;
+ }
+ }
+
+ self::$head_elements = $result;
+ }
+
+ /**
+ * Insert a (conditional) comment in the header. To preserve execution
+ * order, this method utilizes addHeadElement() in a more or less hackish
+ * way.
+ *
+ * @param string $content comment content
+ */
+ public static function addComment($content)
+ {
+ self::addHeadElement(sprintf('!--%s--', $content));
+ }
+
+ /**
+ * Remove a (conditional) comment from the header.
+ *
+ * @param string $content comment content
+ */
+ public static function removeComment($content)
+ {
+ self::removeHeadElement(sprintf('!--%s--', $content));
+ }
+
+ /**
+ * Return all HTML HEAD elements as a string.
+ *
+ * @return string HTML fragment
+ */
+ public static function getHeadElements()
+ {
+ $result = '';
+
+ $package_elements = [];
+
+ if (isset($GLOBALS['_include_stylesheet'])) {
+ unset($package_elements['base-style.css']);
+ self::addStylesheet($GLOBALS['_include_stylesheet'], ['media' => 'screen, print']);
+ }
+
+ $head_elements = array_merge($package_elements, self::$head_elements);
+
+ foreach ($head_elements as $element) {
+ $result .= '<' . $element['name'];
+
+ foreach ($element['attributes'] as $key => $value) {
+ $result .= sprintf(' %s="%s"', $key, htmlReady($value));
+ }
+
+ $result .= ">\n";
+
+ if (isset($element['content'])) {
+ $result .= $element['content'];
+ $result .= '</' . $element['name'] . ">\n";
+ }
+ }
+
+ if (isset($GLOBALS['_include_extra_stylesheet'])) {
+ $result .= Assets::stylesheet($GLOBALS['_include_extra_stylesheet']);
+ }
+
+ if (isset($GLOBALS['_include_additional_header'])) {
+ $result .= $GLOBALS['_include_additional_header'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Add an extra HTML fragment at the start of the HTML BODY section.
+ *
+ * @param string $html HTML fragment to include in BODY
+ */
+ public static function addBodyElements($html)
+ {
+ self::$body_elements .= $html;
+ }
+
+ /**
+ * Return all HTML BODY fragments as a string.
+ *
+ * @return string HTML fragment
+ */
+ public static function getBodyElements()
+ {
+ $result = self::$body_elements;
+
+ if (isset($GLOBALS['_include_additional_html'])) {
+ $result .= $GLOBALS['_include_additional_html'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Disable output of the navigation header for this page.
+ */
+ public static function disableHeader()
+ {
+ self::$display_header = false;
+ }
+
+ /**
+ * Return whether output of the navigation header is enabled.
+ */
+ public static function isHeaderEnabled()
+ {
+ return self::$display_header && !$GLOBALS['_NOHEADER'];
+ }
+
+ /**
+ * Sets the id of the html body element.
+ * The given id is stripped of all non alpha-numeric characters
+ * (except for -).
+ *
+ * @param String $id Id of the body element
+ */
+ public static function setBodyElementId($id)
+ {
+ self::$body_element_id = preg_replace('/[^\w-]+/', '_', $id);
+ }
+
+ /**
+ * Returns whether an id for the body element has been set.
+ *
+ * @return bool
+ */
+ public static function hasBodyElementId()
+ {
+ return self::$body_element_id !== false;
+ }
+
+ /**
+ * Gets the id of the body element.
+ * If non was set, it is dynamically generated base on the name of
+ * the current PHP script, with the suffix removed and all
+ * non-alphanumeric characters replaced with '_'.
+ *
+ * @return String containing the body element id
+ */
+ public static function getBodyElementId()
+ {
+ // Return specific or dynamically generated body element id
+ return self::$body_element_id
+ ?: preg_replace('/\W/', '_', basename($_SERVER['PHP_SELF'], '.php'));
+ }
+
+ /**
+ * Registers a MessageBox object for display the next time a layout
+ * is rendered. Note: This will only work for pages that use layout
+ * templates.
+ *
+ * @param MessageBox message object to display
+ */
+ public static function postMessage(LayoutMessage $message, $id = null)
+ {
+ if ($id === null ) {
+ $_SESSION['messages'][] = $message;
+ } else {
+ $_SESSION['messages'][$id] = $message;
+ }
+ }
+
+ /**
+ * Convenience method: Post a success message box.
+ *
+ * @param String $message Success message to diplay
+ * @param Array $details Additional details (optional)
+ * @param bool $close_details Show the details closed (optional,
+ * defaults to false)
+ */
+ public static function postSuccess($message, $details = [], $close_details = false)
+ {
+ self::postMessage(MessageBox::success($message, $details, $close_details));
+ }
+
+ /**
+ * Convenience method: Post an error message box.
+ *
+ * @param String $message Error message to diplay
+ * @param Array $details Additional details (optional)
+ * @param bool $close_details Show the details closed (optional,
+ * defaults to false)
+ */
+ public static function postError($message, $details = [], $close_details = false)
+ {
+ self::postMessage(MessageBox::error($message, $details, $close_details));
+ }
+
+ /**
+ * Convenience method: Post an info message box.
+ *
+ * @param String $message Info message to diplay
+ * @param Array $details Additional details (optional)
+ * @param bool $close_details Show the details closed (optional,
+ * defaults to false)
+ */
+ public static function postInfo($message, $details = [], $close_details = false)
+ {
+ self::postMessage(MessageBox::info($message, $details, $close_details));
+ }
+
+ /**
+ * Convenience method: Post a warning message box.
+ *
+ * @param String $message Warning message to diplay
+ * @param Array $details Additional details (optional)
+ * @param bool $close_details Show the details closed (optional,
+ * defaults to false)
+ */
+ public static function postWarning($message, $details = [], $close_details = false)
+ {
+ self::postMessage(MessageBox::warning($message, $details, $close_details));
+ }
+
+ /**
+ * Convenience method: Post an exception message box.
+ *
+ * @param String $message Exception message to diplay
+ * @param Array $details Additional details (optional)
+ * @param bool $close_details Show the details closed (optional,
+ * defaults to false)
+ */
+ public static function postException($message, $details = [], $close_details = false)
+ {
+ self::postMessage(MessageBox::exception($message, $details, $close_details));
+ }
+
+ /**
+ * Convenience method: Post a question to confirm an action.
+ *
+ * Be aware, that this will output the question as html. So you should
+ * either know what you are doing, use htmlReady() or post the QuestionBox
+ * for yourself using self::postMessage().
+ *
+ * @param String $question Question to confirm
+ * @param Array $approve_params Parameters to send when approving
+ * @param Array $disapprove_params Parameters to send when disapproving
+ * @return QuestionBox to allow further settings like urls and such
+ * @see QuestionBox
+ * @since Stud.IP 4.2
+ */
+ public static function postQuestion($question, $accept_url = '', $decline_url = '')
+ {
+ $qbox = QuestionBox::createHTML($question, $accept_url, $decline_url);
+ self::postMessage($qbox, 'question-box');
+ return $qbox;
+ }
+
+ /**
+ * Clears all messages pending for display.
+ */
+ public static function clearMessages()
+ {
+ unset($_SESSION['messages']);
+ }
+
+ /**
+ * Returns the list of pending messages and clears the list.
+ *
+ * @return array list of MessageBox objects
+ */
+ public static function getMessages()
+ {
+ $messages = [];
+
+ if (isset($_SESSION['messages'])) {
+ $messages = $_SESSION['messages'];
+ self::clearMessages();
+ }
+
+ return $messages;
+ }
+
+ /**
+ * Return the names of the squeeze packages to use.
+ *
+ * Per default the squeeze package "base" is included.
+ *
+ * @return array an array containing the names of the packages
+ */
+ public static function getSqueezePackages()
+ {
+ return [];
+ }
+
+ /**
+ * Set the names of the squeeze packages to use
+ *
+ * @code
+ * # use as many arguments as you want
+ * PageLayout::setSqueezePackages("base");
+ * PageLayout::setSqueezePackages("base", "admin");
+ * PageLayout::setSqueezePackages("base", "admin", "upload");
+ * # PageLayout::setSqueezePackages(...);
+ * @endcode
+ *
+ * @param ...
+ * a variable-length argument list containing the names of the packages
+ */
+ public static function setSqueezePackages($package/*, ...*/)
+ {
+ $packages = func_get_args();
+ foreach ($packages as $package) {
+ self::addSqueezePackage($package);
+ }
+ }
+
+ /**
+ * Add a squeeze package to the list of squeeze packages to use
+ *
+ * @code
+ * PageLayout::addSqueezePackage("admin");
+ * @endcode
+ *
+ * @param string $package the name of the package
+ */
+ public static function addSqueezePackage($package)
+ {
+ $oldPackages = ["admission", "base", "statusgroups", "wysiwyg"];
+ $oldCssPackages = ["base", "statusgroups", "wysiwyg"];
+
+ // tablesorter loads on demand
+
+ // Get software version
+ $v = StudipVersion::getStudipVersion(false);
+
+ if (in_array($package, $oldPackages)) {
+ self::addScript('studip-' . $package . '.js?v=' . $v);
+ }
+ if (in_array($package, $oldCssPackages)) {
+ self::addStylesheet('studip-' . $package . '.css?v=' . $v);
+ }
+ }
+
+ /**
+ * Modifies the Quicksearch of Stud.IP
+ *
+ * @param $html HTML code for the custom quicksearch
+ */
+ public static function addCustomQuicksearch($html)
+ {
+ self::$customQuicksearch = $html;
+ }
+
+ /**
+ * Check if a custom quicksearch was added to the layout
+ *
+ * @return bool TRUE if there is a custom quicksearch
+ */
+ public static function hasCustomQuicksearch()
+ {
+ return isset(self::$customQuicksearch);
+ }
+
+ /**
+ * Retrieves the code of the custom quicksearch
+ *
+ * @return mixed Quicksearch code
+ */
+ public static function getCustomQuicksearch()
+ {
+ return self::$customQuicksearch;
+ }
+
+ /**
+ * Defines whether full screen mode toggle is allowed or not.
+ *
+ * @param boolean $state
+ */
+ public static function allowFullscreenMode($state = true)
+ {
+ self::$allow_full_screen = $state;
+ }
+
+ /**
+ * Returns whether full screen mode toggle is allowed.
+ *
+ * @return boolean
+ */
+ public static function isFullscreenModeAllowed()
+ {
+ return self::$allow_full_screen;
+ }
+}