├── .gitignore ├── update.php ├── package.yml ├── .travis.yml ├── fragments └── core │ ├── minibar │ ├── minibar_backend.php │ ├── minibar_frontend.php │ └── minibar_element.php │ └── bottom.php ├── scss ├── styles.scss └── minibar.scss ├── .github └── workflows │ └── publish-to-redaxo.yml ├── lib ├── element │ ├── time.php │ ├── lazy.php │ ├── element.php │ ├── scheme.php │ ├── debug.php │ ├── syslog.php │ ├── system.php │ ├── article.php │ └── url2_yform.php ├── system_setting_minibar_inpopup.php ├── system_setting_hide_empty_metainfos.php ├── api_minibar.php ├── system_setting_minibar.php └── minibar.php ├── LICENSE.md ├── assets └── minibar.js ├── lang ├── es_es.lang ├── sv_se.lang ├── en_gb.lang └── de_de.lang ├── .php_cs.dist ├── boot.php ├── extensions └── extension_metainfo.php ├── README.md └── WIDGETS.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .php_cs.cache 3 | -------------------------------------------------------------------------------- /update.php: -------------------------------------------------------------------------------- 1 | setConfig('compile', false); 4 | -------------------------------------------------------------------------------- /package.yml: -------------------------------------------------------------------------------- 1 | package: minibar 2 | version: '2.5.1' 3 | author: 'Friends Of REDAXO' 4 | supportpage: https://github.com/FriendsOfREDAXO/minibar 5 | 6 | load: late 7 | requires: 8 | redaxo: ^5.16.0-beta1 9 | php: 10 | version: '>=8.1' 11 | 12 | default_config: 13 | compile: true 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - '7.1' 5 | 6 | cache: 7 | directories: 8 | - $HOME/.composer/cache 9 | 10 | before_install: 11 | - phpenv config-rm xdebug.ini || echo "xdebug not available" 12 | 13 | script: 14 | - composer require --dev friendsofredaxo/linter 15 | - vendor/bin/rexlint 16 | 17 | -------------------------------------------------------------------------------- /fragments/core/minibar/minibar_backend.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | elements as $element) { 5 | $this->subfragment('core/minibar/minibar_element.php', [ 6 | 'element' => $element, 7 | ]); 8 | } 9 | ?> 10 |
11 |
12 | -------------------------------------------------------------------------------- /scss/styles.scss: -------------------------------------------------------------------------------- 1 | $minibar-height: 36px; 2 | $fa-font-path: "../be_style/webfonts"; 3 | $fa-css-prefix: "rex-minibar-icon--fa"; 4 | 5 | @import '../../be_style/plugins/redaxo/scss/variables'; 6 | // @import '../../be_style/plugins/redaxo/scss/variables-dark'; // TODO: import in future minibar versions! 7 | @import "../../be_style/vendor/font-awesome/scss/fontawesome"; 8 | @import "../../be_style/vendor/font-awesome/scss/solid"; 9 | 10 | @import "minibar"; 11 | -------------------------------------------------------------------------------- /.github/workflows/publish-to-redaxo.yml: -------------------------------------------------------------------------------- 1 | name: Publish release 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | 8 | jobs: 9 | redaxo_publish: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: FriendsOfREDAXO/installer-action@v1 14 | with: 15 | myredaxo-username: ${{ secrets.MYREDAXO_USERNAME }} 16 | myredaxo-api-key: ${{ secrets.MYREDAXO_API_KEY }} 17 | description: ${{ github.event.release.body }} 18 | 19 | -------------------------------------------------------------------------------- /fragments/core/bottom.php: -------------------------------------------------------------------------------- 1 | 2 | isPopup()): ?> 3 | 9 | 10 | get(); ?> 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/element/time.php: -------------------------------------------------------------------------------- 1 | 12 | 13 | '.rex_i18n::msg('footer_scripttime', rex::getProperty('timer')->getFormattedDelta(rex_timer::SEC)).' 14 | 15 | '; 16 | } 17 | 18 | public function getOrientation() 19 | { 20 | return rex_minibar_element::RIGHT; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /fragments/core/minibar/minibar_frontend.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | elements as $element) { 11 | $this->subfragment('core/minibar/minibar_element.php', [ 12 | 'element' => $element, 13 | ]); 14 | } 15 | ?> 16 |
17 |
18 | -------------------------------------------------------------------------------- /lib/system_setting_minibar_inpopup.php: -------------------------------------------------------------------------------- 1 | setAttribute('class', 'form-control selectpicker'); 17 | $field->setLabel(rex_i18n::msg('minibar_system_setting_inpopup')); 18 | $select = $field->getSelect(); 19 | $select->addOption(rex_i18n::msg('minibar_inpopup_enabled'), self::ENABLED); 20 | $select->addOption(rex_i18n::msg('minibar_inpopup_disabled'), self::DISABLED); 21 | $select->setSelected(rex_config::get('minibar', 'inpopup_enabled', self::DISABLED)); 22 | return $field; 23 | } 24 | 25 | public function setValue($value) 26 | { 27 | $value = (int) $value; 28 | rex_config::set('minibar', 'inpopup_enabled', $value); 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/system_setting_hide_empty_metainfos.php: -------------------------------------------------------------------------------- 1 | setAttribute('class', 'form-control selectpicker'); 17 | $field->setLabel(rex_i18n::msg('minibar_system_setting_hide_empty_metainfos')); 18 | $select = $field->getSelect(); 19 | $select->addOption(rex_i18n::msg('minibar_metainfo_show'), self::SHOW); 20 | $select->addOption(rex_i18n::msg('minibar_metainfo_hide'), self::HIDE); 21 | $select->setSelected(rex_config::get('minibar', 'hide_empty_metainfos', self::SHOW)); 22 | return $field; 23 | } 24 | 25 | public function setValue($value) 26 | { 27 | $value = (int) $value; 28 | rex_config::set('minibar', 'hide_empty_metainfos', $value); 29 | return true; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/element/lazy.php: -------------------------------------------------------------------------------- 1 | renderFirstView(); 14 | } 15 | return $this->renderComplete(); 16 | } 17 | 18 | public static function isFirstView() 19 | { 20 | $apiFn = rex_api_function::factory(); 21 | return !($apiFn instanceof rex_api_minibar); 22 | } 23 | 24 | /** 25 | * Returns the initial/light-weight html representation of this element. 26 | * 27 | * @return string 28 | */ 29 | abstract protected function renderFirstView(); 30 | 31 | /** 32 | * Returns the full html for this element. 33 | * This method will be called asynchronously after user starts interacting with the initial element. 34 | * 35 | * @return string 36 | */ 37 | abstract protected function renderComplete(); 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Friends Of REDAXO 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/minibar.js: -------------------------------------------------------------------------------- 1 | $(document).on('rex:ready', function (event, container) { 2 | var minibar = $(container).find('.rex-minibar'); 3 | if (minibar.length) { 4 | $('body > .rex-minibar').replaceWith(minibar); 5 | } 6 | 7 | /** 8 | * Für den Scheme-Umschalter (Light/Dark-Mode) 9 | */ 10 | if (typeof rex === 'object' && rex.theme && redaxo.theme && !redaxo.minibar ) { 11 | redaxo.minibar = function (theme) { 12 | if( theme == 'reset' ){ 13 | theme = rex.theme; 14 | } 15 | if( theme == redaxo.theme.current ){ 16 | return; 17 | } 18 | if( theme === 'auto' ) { 19 | document.body.classList.remove('rex-theme-dark'); 20 | document.body.classList.remove('rex-theme-light'); 21 | return; 22 | } 23 | document.body.classList.remove('rex-theme-'+redaxo.theme.current); 24 | document.body.classList.add('rex-theme-'+theme) 25 | }; 26 | let node = document.getElementById('mb-8d502110-db8a-4355-baaa-a612778fb4aa'); 27 | if( node ) { 28 | node.innerHTML = rex.theme; 29 | } 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /lib/element/element.php: -------------------------------------------------------------------------------- 1 | setVisibility($visibility); 15 | 16 | if (rex::isBackend()) { 17 | rex_response::sendRedirect(rex_url::currentBackendPage([], false)); 18 | } 19 | 20 | rex_response::sendRedirect(rex_getUrl('', '', [], '&')); 21 | } 22 | 23 | $lazyElement = rex_get('lazy_element', 'string'); 24 | if ($lazyElement) { 25 | $minibar = rex_minibar::getInstance(); 26 | $element = $minibar->elementByClass($lazyElement); 27 | if ($element) { 28 | $fragment = new rex_fragment([ 29 | 'element' => $element, 30 | ]); 31 | rex_response::setStatus(rex_response::HTTP_OK); 32 | rex_response::sendContent($fragment->parse('core/minibar/minibar_element.php')); 33 | exit(); 34 | } 35 | } 36 | } 37 | 38 | protected function requiresCsrfProtection() 39 | { 40 | return true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/system_setting_minibar.php: -------------------------------------------------------------------------------- 1 | setAttribute('class', 'form-control selectpicker'); 19 | $field->setLabel(rex_i18n::msg('minibar_system_setting')); 20 | $select = $field->getSelect(); 21 | $select->addOption(rex_i18n::msg('minibar_enabled_everywhere'), self::ENABLED_EVERYWHERE); 22 | $select->addOption(rex_i18n::msg('minibar_enabled_frontend'), self::ENABLED_FRONTEND); 23 | $select->addOption(rex_i18n::msg('minibar_enabled_backend'), self::ENABLED_BACKEND); 24 | $select->addOption(rex_i18n::msg('minibar_disabled'), self::DISABLED); 25 | $select->setSelected(rex_config::get('minibar', 'enabled', self::ENABLED_EVERYWHERE)); 26 | return $field; 27 | } 28 | 29 | public function setValue($value) 30 | { 31 | $value = (int) $value; 32 | rex_config::set('minibar', 'enabled', $value); 33 | return true; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lang/sv_se.lang: -------------------------------------------------------------------------------- 1 | user_minibar = Visa minibar i frontend? 2 | user_minibar_note = Ger ett verktygsfält med mer information 3 | minibar_database = Databas 4 | minibar_documentation_link_label = Läs dokumentationen 5 | minibar_help = Hjälp 6 | minibar_slack_link_label = Slack Channel 7 | minibar_errorreport = Anmäl problem hos GitHub 8 | 9 | metainfo_minibar_article_title = Den aktuella artikelns metadata 10 | metainfo_minibar_clang_title = Aktuella språkets metadata 11 | 12 | structure_article_name = Artikelnamn 13 | structure_path = Bana 14 | structure_status = Status 15 | structure_info = Informationer 16 | 17 | structure_content_minibar_article_edit = Bearbeta i backend 18 | structure_content_minibar_article_show = Visa artikel 19 | 20 | minibar_system_setting = Visa minibar 21 | minibar_enabled_everywhere = Frontend och backend 22 | minibar_enabled_frontend = endast i frontend 23 | minibar_enabled_backend = endast i backend 24 | minibar_disabled = deaktiverad 25 | 26 | minibar_system_setting_inpopup = Minibar i backend popups 27 | minibar_inpopup_enabled = visa 28 | minibar_inpopup_disabled = dölja 29 | 30 | minibar_system_setting_hide_empty_metainfos = Visa tomma metadata i minibar 31 | minibar_metainfo_hide = dölja 32 | minibar_metainfo_show = visa 33 | minibar_system_info = System 34 | 35 | minibar_debug_header = Debug-modus är aktiverad 36 | minibar_debug_info = Info 37 | minibar_debug_info_text = Felsökning bör inte aktiveras i live-system.
Kontakta en administratör om det behövs 38 | minibar_debug_links = Länkar 39 | minibar_debug_system_settings = Systeminställningar 40 | minibar_debug_start_debug = Öppna debug addon 41 | -------------------------------------------------------------------------------- /fragments/core/minibar/minibar_element.php: -------------------------------------------------------------------------------- 1 | element; 6 | 7 | $class = 'rex-minibar-element '; 8 | $class .= rex_string::normalize(get_class($element), '-'); 9 | $class .= ($element->getOrientation() == rex_minibar_element::RIGHT ? ' rex-minibar-element-right' : ''); 10 | $class .= ($element->isDanger() ? ' rex-minibar-status-danger' : ''); 11 | $class .= ($element->isWarning() ? ' rex-minibar-status-warning' : ''); 12 | $class .= ($element->isPrimary() ? ' rex-minibar-status-primary' : ''); 13 | 14 | $onmouseover = ''; 15 | if ($element instanceof rex_minibar_lazy_element && rex_minibar_lazy_element::isFirstView()) { 16 | $elementId = get_class($element); 17 | $context = rex_context::restore(); 18 | $url = $context->getUrl(['lazy_element' => $elementId, 'article_id' => rex_article::getCurrentId(), 'current_lang'=> rex_clang::getCurrentId()] + rex_api_minibar::getUrlParams()); 19 | $onmouseover = << 19 | 20 | 💡 '.rex_i18n::msg('minibar_scheme_title').' 21 | 22 | 23 |
24 |
25 |
26 | '.rex_i18n::msg('minibar_scheme_default').': 27 |
28 | 31 | 34 | 37 | 40 |
41 |
'; 42 | } 43 | 44 | public function getOrientation() 45 | { 46 | return rex_minibar_element::RIGHT; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | exclude('fragments') 6 | ->in($src) 7 | ; 8 | return PhpCsFixer\Config::create() 9 | ->setUsingCache(true) 10 | ->setRiskyAllowed(true) 11 | ->setRules([ 12 | '@Symfony' => true, 13 | '@Symfony:risky' => true, 14 | '@PHP71Migration' => true, 15 | '@PHP71Migration:risky' => true, 16 | '@PHPUnit60Migration:risky' => true, 17 | 'array_indentation' => true, 18 | 'array_syntax' => ['syntax' => 'short'], 19 | 'blank_line_before_statement' => false, 20 | 'braces' => ['allow_single_line_closure' => false], 21 | 'comment_to_phpdoc' => true, 22 | 'concat_space' => false, 23 | 'declare_strict_types' => false, 24 | 'function_to_constant' => ['functions' => ['get_class', 'get_called_class', 'php_sapi_name', 'phpversion', 'pi']], 25 | 'heredoc_to_nowdoc' => true, 26 | 'list_syntax' => ['syntax' => 'short'], 27 | 'logical_operators' => true, 28 | 'native_constant_invocation' => false, 29 | 'no_blank_lines_after_phpdoc' => false, 30 | 'no_null_property_initialization' => true, 31 | 'no_php4_constructor' => true, 32 | 'no_superfluous_elseif' => true, 33 | 'no_unreachable_default_argument_value' => true, 34 | 'no_useless_else' => true, 35 | 'no_useless_return' => true, 36 | 'ordered_imports' => true, 37 | 'php_unit_internal_class' => true, 38 | 'php_unit_method_casing' => true, 39 | 'php_unit_set_up_tear_down_visibility' => true, 40 | 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], 41 | 'phpdoc_annotation_without_dot' => false, 42 | 'phpdoc_no_package' => false, 43 | 'phpdoc_order' => true, 44 | 'phpdoc_trim_consecutive_blank_line_separation' => true, 45 | 'phpdoc_types_order' => false, 46 | 'phpdoc_var_annotation_correct_order' => true, 47 | 'psr4' => false, 48 | 'semicolon_after_instruction' => false, 49 | 'space_after_semicolon' => true, 50 | 'static_lambda' => true, 51 | 'string_line_ending' => true, 52 | 'void_return' => false, 53 | 'yoda_style' => false, 54 | ]) 55 | ->setFinder($finder) 56 | ; 57 | -------------------------------------------------------------------------------- /lang/en_gb.lang: -------------------------------------------------------------------------------- 1 | user_minibar = Show minibar in the frontend 2 | user_minibar_note = Shows a toolbar with additional information 3 | minibar_database = Database 4 | minibar_documentation_link_label = Read documentation 5 | minibar_help = Help 6 | minibar_slack_link_label = Slack channel 7 | minibar_errorreport = Report issue on GitHub 8 | 9 | metainfo_minibar_article_title = Current article metadata 10 | metainfo_minibar_clang_title = Current language metadata 11 | 12 | structure_article_name = Article name 13 | structure_path = Path 14 | structure_status = Status 15 | structure_info = Information 16 | 17 | structure_content_minibar_article_edit = Edit in the backend 18 | structure_content_minibar_article_show = Show articel 19 | 20 | minibar_system_setting = Show minibar 21 | minibar_enabled_everywhere = Frontend and backend 22 | minibar_enabled_frontend = Frontend only 23 | minibar_enabled_backend = Backend only 24 | minibar_disabled = deactivated 25 | 26 | minibar_system_setting_inpopup = Minibar in backend popups 27 | minibar_inpopup_enabled = show 28 | minibar_inpopup_disabled = hide 29 | 30 | minibar_system_setting_hide_empty_metainfos = Show empty metadata in minibar 31 | minibar_metainfo_hide = hide 32 | minibar_metainfo_show = show 33 | minibar_system_info = System 34 | 35 | 36 | minibar_debug_header = Debug mode is activated 37 | minibar_debug_info = Info 38 | minibar_debug_info_text = Debug should not be enabled on live systems.
Contact an administrator if needed. 39 | minibar_debug_links = Links 40 | minibar_debug_system_settings = System settings 41 | minibar_debug_start_debug = Open debug addon 42 | 43 | minibar_scheme_title = Dark/Light-Mode 44 | minibar_scheme_default = Default 45 | minibar_scheme_dark = Dark 46 | minibar_scheme_light = Light 47 | minibar_scheme_auto = Auto 48 | minibar_scheme_reset = Reset 49 | 50 | # Minibar URL2/YForm Element 51 | minibar_url2_yform_title = Table 52 | minibar_url2_yform_header_found = YForm Record 53 | minibar_url2_yform_header_info = Page Information 54 | minibar_url2_yform_table = Table 55 | minibar_url2_yform_record_id = Record ID 56 | minibar_url2_yform_edit_record = Edit record 57 | minibar_url2_yform_open_table = Open table 58 | minibar_url2_yform_no_record = No record found 59 | minibar_url2_yform_no_data = This page has no editable database content 60 | minibar_url2_yform_not_found = Not found 61 | minibar_url2_yform_no_permission = No permission 62 | -------------------------------------------------------------------------------- /lang/de_de.lang: -------------------------------------------------------------------------------- 1 | user_minibar = Minibar im Frontend anzeigen? 2 | user_minibar_note = Stellt eine Toolbar mit weiteren Informationen zur Verfügung 3 | minibar_database = Datenbank 4 | minibar_documentation_link_label = Dokumentation lesen 5 | minibar_help = Hilfe 6 | minibar_slack_link_label = Slack Channel 7 | minibar_errorreport = Problem bei GitHub melden 8 | 9 | metainfo_minibar_article_title = Metadaten des aktuellen Artikels 10 | metainfo_minibar_clang_title = Metadaten der aktuellen Sprache 11 | 12 | structure_article_name = Artikelname 13 | structure_path = Pfad 14 | structure_status = Status 15 | structure_info = Informationen 16 | 17 | structure_content_minibar_article_edit = Im Backend bearbeiten 18 | structure_content_minibar_article_show = Artikel anzeigen 19 | 20 | minibar_system_setting = Minibar anzeigen 21 | minibar_enabled_everywhere = Frontend und Backend 22 | minibar_enabled_frontend = nur im Frontend 23 | minibar_enabled_backend = nur im Backend 24 | minibar_disabled = deaktiviert 25 | 26 | minibar_system_setting_inpopup = Minibar in Backend PopUps 27 | minibar_inpopup_enabled = anzeigen 28 | minibar_inpopup_disabled = verbergen 29 | 30 | minibar_system_setting_hide_empty_metainfos = Leere Metadaten in Minibar anzeigen 31 | minibar_metainfo_hide = verbergen 32 | minibar_metainfo_show = anzeigen 33 | minibar_system_info = System 34 | 35 | minibar_debug_header = Der Debug-Modus ist aktiviert 36 | minibar_debug_info = Info 37 | minibar_debug_info_text = Debug sollte nicht in Live-Systemen aktiviert werden.
Kontaktiere ggf. einen Administrator 38 | minibar_debug_links = Links 39 | minibar_debug_system_settings = Systemeinstellungen 40 | minibar_debug_start_debug = Debug-AddOn aufrufen 41 | 42 | minibar_scheme_title = Dark/Light-Mode 43 | minibar_scheme_default = Default 44 | minibar_scheme_dark = Dark 45 | minibar_scheme_light = Light 46 | minibar_scheme_auto = Auto 47 | minibar_scheme_reset = Reset 48 | 49 | # Minibar URL2/YForm Element 50 | minibar_url2_yform_title = Tabelle 51 | minibar_url2_yform_header_found = YForm Datensatz 52 | minibar_url2_yform_header_info = Seiten-Information 53 | minibar_url2_yform_table = Tabelle 54 | minibar_url2_yform_record_id = Datensatz ID 55 | minibar_url2_yform_edit_record = Datensatz bearbeiten 56 | minibar_url2_yform_open_table = Tabelle öffnen 57 | minibar_url2_yform_no_record = Kein Datensatz gefunden 58 | minibar_url2_yform_no_data = Diese Seite hat keine bearbeitbaren Datenbank-Inhalte 59 | minibar_url2_yform_not_found = Nicht gefunden 60 | minibar_url2_yform_no_permission = Keine Berechtigung 61 | -------------------------------------------------------------------------------- /lib/element/debug.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | class rex_minibar_element_debug extends rex_minibar_element 12 | { 13 | public function render() 14 | { 15 | $links = ''; 16 | if (rex::getUser()->isAdmin()) { 17 | $links = ' 18 |
19 | '.rex_i18n::msg('minibar_debug_links').' 20 | 21 | '.rex_i18n::msg('minibar_debug_system_settings').' 22 | 23 |
'; 24 | if (rex_addon::get('debug')->isAvailable()) 25 | { 26 | $links .= ' 27 | '.rex_i18n::msg('minibar_debug_start_debug').' 28 | 29 | ';} 30 | 31 | $links .= '
'; 32 | } 33 | return 34 | ' 35 | 75 |
76 | 77 | 78 | 79 | 80 | '.rex_i18n::msg('debug_mode').' 81 | 82 |
83 |
84 |
'.rex_i18n::msg('minibar_debug_header').'
85 |
86 |
87 | '.rex_i18n::msg('minibar_debug_info').' 88 | 89 | '.rex_i18n::msg('minibar_debug_info_text').' 90 | 91 |
92 | '.$links.' 93 |
94 |
95 | '; 96 | } 97 | 98 | public function getOrientation() 99 | { 100 | return rex_minibar_element::RIGHT; 101 | } 102 | } 103 | 104 | -------------------------------------------------------------------------------- /lib/element/syslog.php: -------------------------------------------------------------------------------- 1 | isAdmin()) { 17 | return ''; 18 | } 19 | 20 | $status = 'rex-syslog-ok'; 21 | 22 | $sysLogFile = rex_logger::getPath(); 23 | $login = rex::getProperty('login'); 24 | 25 | // in case someone else aready read the filemtime() and the file was changed afterwards within the same request 26 | clearstatcache( true, $sysLogFile ); 27 | $lastModified = filemtime($sysLogFile); 28 | 29 | // "last-seen" will be updated, when the user looks into the syslog 30 | if (rex::isBackend() && rex_be_controller::getCurrentPage() == 'system/log/redaxo') { 31 | // use the backend-session instead of rex_session() to make it work consistently across frontend/backend. 32 | // the frontend should reflect when we look into the log in the backend. 33 | $login->setSessionVar('rex_syslog_last_seen', $lastModified ); 34 | $lastSeen = $lastModified; 35 | } else { 36 | $lastSeen = $login->getSessionVar('rex_syslog_last_seen'); 37 | } 38 | 39 | // when the user never looked into the file (e.g. after login), we dont have a timely reference point. 40 | // therefore we check for changes in the file within the last 24hours 41 | if (!$lastSeen) { 42 | if ($lastModified > strtotime('-24 hours')) { 43 | $status = 'rex-syslog-changed'; 44 | } 45 | } elseif ($lastModified && $lastModified > $lastSeen) { 46 | $status = 'rex-syslog-changed'; 47 | } 48 | 49 | $item = 50 | '
51 | 52 | 53 | 54 | 55 | 56 | System Log 57 | 58 | 59 |
'; 60 | 61 | $logFile = rex_logger::getPath(); 62 | $editor = rex_editor::factory(); 63 | $url = $editor->getUrl($logFile, 1); 64 | 65 | $info = ''; 66 | if ($url) { 67 | $info = 68 | '
69 | 74 |
'; 75 | } 76 | 77 | return $item . $info; 78 | } 79 | 80 | public function getOrientation() 81 | { 82 | return rex_minibar_element::RIGHT; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/minibar.php: -------------------------------------------------------------------------------- 1 | elements[] = $instance; 21 | } 22 | 23 | /** 24 | * @param string $className 25 | * 26 | * @return rex_minibar_element|null 27 | */ 28 | public function elementByClass($className) 29 | { 30 | foreach ($this->elements as $element) { 31 | if (get_class($element) === $className) { 32 | return $element; 33 | } 34 | } 35 | } 36 | 37 | public function get() 38 | { 39 | if (!self::shouldRender()) { 40 | return null; 41 | } 42 | 43 | if (!count($this->elements)) { 44 | return null; 45 | } 46 | 47 | $fragment = new rex_fragment([ 48 | 'elements' => $this->elements, 49 | ]); 50 | 51 | if (rex::isBackend()) { 52 | return $fragment->parse('core/minibar/minibar_backend.php'); 53 | } 54 | 55 | return $fragment->parse('core/minibar/minibar_frontend.php'); 56 | } 57 | 58 | /** 59 | * Returns if the minibar should be rendered. 60 | * 61 | * @return bool 62 | */ 63 | public function shouldRender() 64 | { 65 | if (is_bool($this->isActive)) { 66 | return $this->isActive; 67 | } 68 | 69 | $user = rex_backend_login::createUser(); 70 | if (!$user) { 71 | return false; 72 | } 73 | 74 | $enabled = rex_config::get('minibar', 'enabled', rex_system_setting_minibar::ENABLED_EVERYWHERE); 75 | if ($enabled === rex_system_setting_minibar::ENABLED_EVERYWHERE) { 76 | return true; 77 | } 78 | if ($enabled === rex_system_setting_minibar::ENABLED_BACKEND) { 79 | return rex::isBackend(); 80 | } 81 | if ($enabled === rex_system_setting_minibar::ENABLED_FRONTEND) { 82 | return rex::isFrontend(); 83 | } 84 | return false; 85 | } 86 | 87 | /** 88 | * Returns if the minibar is visible. 89 | * 90 | * @return bool 91 | */ 92 | public function isVisible() 93 | { 94 | return !rex_cookie('rex_minibar_frontend_hidden', 'bool', false); 95 | } 96 | 97 | /** 98 | * Sets the visibility. 99 | * 100 | * @param bool $value 101 | */ 102 | public function setVisibility($value) 103 | { 104 | if ($value) { 105 | rex_response::sendCookie('rex_minibar_frontend_hidden', ''); 106 | } else { 107 | rex_response::sendCookie('rex_minibar_frontend_hidden', '1', ['expires' => time() + rex::getProperty('session_duration'), 'samesite' => 'strict']); 108 | } 109 | } 110 | 111 | /** 112 | * @param bool $isActive 113 | */ 114 | public function setActive($isActive) 115 | { 116 | $this->isActive = $isActive; 117 | } 118 | 119 | /** 120 | * @return bool|null 121 | */ 122 | public function isActive() 123 | { 124 | return $this->isActive; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/element/system.php: -------------------------------------------------------------------------------- 1 | isAdmin()) { 14 | $links .= ''.rex_i18n::msg('minibar_documentation_link_label').''; 15 | } 16 | 17 | $logo = str_replace(' 21 | '.$logo.' 22 | 23 | 24 | '.rex::getVersion().' 25 | 26 | 27 |
28 |
'. rex_i18n::msg('minibar_system_info') .'
29 |
30 |
31 | REDAXO 32 | '.rex::getVersion().' '.(rex::getUser() && rex::getUser()->isAdmin() ? '
'.rex_i18n::msg('logfiles').'
'.rex_i18n::msg('system_report').'' : '') .'
33 |
34 |
35 | PHP Version 36 | '.PHP_VERSION.' '.(rex::isBackend() && rex::getUser() && rex::getUser()->isAdmin() ? 'phpinfo()' : '') .' 37 |
38 |
39 | MySQL 40 | '.rex_sql::getServerVersion().' 41 |
42 |
43 | '.rex_i18n::msg('minibar_database').' 44 | '.rex_escape($database[1]['name']).' 45 |
46 |
47 | Host 48 | '.$database[1]['host'].' 49 |
50 |
51 |
52 |
53 | '.rex_i18n::msg('minibar_help').' 54 | 55 | '.$links.'
56 | '.rex_i18n::msg('minibar_slack_link_label').'
57 | '.rex_i18n::msg('minibar_errorreport').' 58 |
59 |
60 |
61 | 62 | 63 | redaxo.org 64 | / 65 | '.rex_i18n::msg('footer_credits').' 66 |
67 | yakamara.de 68 |
69 |
70 |
71 |
'; 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | public function getOrientation() 78 | { 79 | return rex_minibar_element::RIGHT; 80 | } 81 | 82 | /** 83 | * {@inheritdoc} 84 | */ 85 | public function isPrimary() 86 | { 87 | return true; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /boot.php: -------------------------------------------------------------------------------- 1 | getConfig('compile')) { 7 | $compiler = new rex_scss_compiler(); 8 | 9 | $compiler->setRootDir(rex_path::addon('minibar/scss')); 10 | $compiler->setScssFile([$addon->getPath('scss/styles.scss')]); 11 | 12 | // Compile in backend assets dir 13 | $compiler->setCssFile($addon->getPath('assets/styles.css')); 14 | $compiler->compile(); 15 | $addon->setConfig('compile', false); 16 | } 17 | 18 | $mypage = 'minibar'; 19 | $addon = rex_addon::get('minibar'); 20 | 21 | rex_minibar::getInstance()->addElement(new rex_minibar_element_system()); 22 | rex_minibar::getInstance()->addElement(new rex_minibar_element_time()); 23 | rex_minibar::getInstance()->addElement(new rex_minibar_element_syslog()); 24 | 25 | // URL2/YForm Element für alle Backend-Bereiche verfügbar machen 26 | if (rex::isFrontend()) { 27 | rex_minibar::getInstance()->addElement(new rex_minibar_element_url2_yform()); 28 | } 29 | if (rex::isFrontend() || (rex::isBackend() && (rex_be_controller::getCurrentPagePart(1) === 'content' || rex_be_controller::getCurrentPagePart(1) === 'structure'))) { 30 | rex_minibar::getInstance()->addElement(new rex_minibar_element_structure_article()); 31 | } 32 | if (rex::isFrontend() && rex::isDebugMode()) { 33 | rex_minibar::getInstance()->addElement(new rex_minibar_element_debug()); 34 | } 35 | 36 | if (rex::isBackend()) { 37 | rex_minibar::getInstance()->addElement(new rex_minibar_element_scheme()); 38 | 39 | if (rex_be_controller::getCurrentPagePart(1) == 'system') { 40 | rex_system_setting::register(new rex_system_setting_minibar()); 41 | rex_system_setting::register(new rex_system_setting_minibar_inpopup()); 42 | rex_system_setting::register(new rex_system_setting_minibar_hide_empty_metainfos()); 43 | } 44 | 45 | require_once __DIR__.'/extensions/extension_metainfo.php'; 46 | 47 | rex_extension::register('PAGE_BODY_ATTR', static function (rex_extension_point $ep) { 48 | if (rex_minibar::getInstance()->isActive() !== false) { 49 | $body_attr = $ep->getSubject(); 50 | $body_attr['class'][] = 'rex-minibar-is-active'; 51 | return $body_attr; 52 | } 53 | }); 54 | 55 | rex_extension::register('PAGE_CHECKED', static function (rex_extension_point $ep) { 56 | $page = rex_be_controller::getCurrentPageObject(); 57 | if ($page && $page->isPopup()) { 58 | $enabled = rex_config::get('minibar', 'inpopup_enabled', rex_system_setting_minibar_inpopup::DISABLED); 59 | rex_minibar::getInstance()->setActive($enabled == rex_system_setting_minibar_inpopup::ENABLED); 60 | } 61 | }); 62 | 63 | if (rex_minibar::getInstance()->shouldRender()) { 64 | rex_view::addJsFile($addon->getAssetsUrl('minibar.js')); 65 | rex_view::addCssFile($addon->getAssetsUrl('styles.css')); 66 | } 67 | 68 | // XXX vermutlich nicht mehr nötig? 69 | // update body class if minibar has been set inactive 70 | rex_extension::register('OUTPUT_FILTER', static function (rex_extension_point $ep) { 71 | if (rex_minibar::getInstance()->isActive() === false) { 72 | $ep->setSubject(preg_replace( 73 | '/(<(body|html)[^>]*)rex-minibar-is-active/iU', 74 | '$1', 75 | $ep->getSubject()) 76 | ); 77 | } 78 | }); 79 | 80 | // minibar aktualisieren bei PJAX requests. 81 | // in full-page requests wird die minibar via fragments/core/bottom.php gerendert. 82 | if (rex_request::isPJAXRequest()) { 83 | rex_extension::register('OUTPUT_FILTER', static function (rex_extension_point $ep) use ($addon) { 84 | // replace last occrance within a string 85 | // credits to https://stackoverflow.com/questions/3835636/php-replace-last-occurrence-of-a-string-in-a-string 86 | $str_lreplace = static function ($search, $replace, $subject) { 87 | $pos = strrpos($subject, $search); 88 | 89 | if ($pos !== false) { 90 | $subject = substr_replace($subject, $replace, $pos, strlen($search)); 91 | } 92 | 93 | return $subject; 94 | }; 95 | 96 | $minibar = rex_minibar::getInstance()->get(); 97 | if ($minibar) { 98 | $pjaxResp = $str_lreplace('', "\n". $minibar . '', $ep->getSubject()); 99 | $ep->setSubject($pjaxResp); 100 | } 101 | }); 102 | } 103 | } 104 | 105 | if (rex::isFrontend()) { 106 | rex_extension::register('OUTPUT_FILTER', static function (rex_extension_point $ep) use ($addon) { 107 | $minibar = rex_minibar::getInstance()->get(); 108 | 109 | if ($minibar) { 110 | $ep->setSubject(str_ireplace( 111 | ['', ''], 112 | ['', $minibar . ''], 113 | $ep->getSubject()) 114 | ); 115 | } 116 | }); 117 | } 118 | -------------------------------------------------------------------------------- /extensions/extension_metainfo.php: -------------------------------------------------------------------------------- 1 | setDebug(); 7 | $fields = $sqlFields->getArray(' 8 | SELECT `t`.`label`, 9 | `f`.`title`, 10 | `f`.`name` 11 | FROM '.rex::getTable('metainfo_field').' AS f 12 | LEFT JOIN '.rex::getTable('metainfo_type').' AS t 13 | ON `f`.`type_id` = `t`.`id` 14 | WHERE `f`.`name` LIKE :art OR `f`.`name` LIKE :cat 15 | ORDER BY LEFT(`f`.`name`, 4), priority', ['art' => 'art_%', 'cat' => 'cat_%']); 16 | 17 | if (!count($fields)) { 18 | return null; 19 | } 20 | 21 | $article = $ep->getParam('article'); 22 | 23 | $items = []; 24 | foreach ($fields as $field) { 25 | // Durch das unterschiedliche Erstellen der Optionen (Pipe, Sql) können die dazugehörigen Labels nicht ganz so einfach aufgelöst werden 26 | // Ein Admin sieht daher die gespeicherten Werte, ein Redakteur kann damit weniger anfangen 27 | if (!rex::getUser()->isAdmin() && in_array($field['label'], ['checkbox', 'radio', 'select'])) { 28 | continue; 29 | } 30 | if (in_array($field['label'], ['legend'])) { 31 | continue; 32 | } 33 | 34 | $value = $article->getValue($field['name']); 35 | if (trim($value) != '') { 36 | switch ($field['label']) { 37 | case 'REX_MEDIA_WIDGET': 38 | $value = sprintf('%s', rex_url::media($value), $value); 39 | break; 40 | case 'REX_MEDIALIST_WIDGET': 41 | $values = explode(',', $value); 42 | $value = []; 43 | foreach ($values as $fileName) { 44 | $value[] = sprintf('%s', rex_url::media($fileName), $fileName); 45 | } 46 | $value = implode(' | ', $value); 47 | break; 48 | case 'REX_LINK_WIDGET': 49 | $linkedArticle = rex_article::get($value); 50 | if (!$linkedArticle) { 51 | break; 52 | } 53 | $value = sprintf('%s', $linkedArticle->getUrl(), $linkedArticle->getName()); 54 | break; 55 | case 'REX_LINKLIST_WIDGET': 56 | $values = explode(',', $value); 57 | $value = []; 58 | foreach ($values as $articleId) { 59 | $linkedArticle = rex_article::get($articleId); 60 | if (!$linkedArticle) { 61 | continue; 62 | } 63 | $value[] = sprintf('%s', $linkedArticle->getUrl(), $linkedArticle->getName()); 64 | } 65 | $value = implode(' | ', $value); 66 | break; 67 | case 'date': 68 | $value = rex_formatter::strftime($value); 69 | break; 70 | case 'datetime': 71 | $value = rex_formatter::strftime($value, 'datetime'); 72 | break; 73 | case 'time': 74 | $value = rex_formatter::strftime($value, 'time'); 75 | break; 76 | } 77 | } 78 | 79 | if (!$value && $showMetaInfo === rex_system_setting_minibar_hide_empty_metainfos::HIDE) { 80 | continue; 81 | } 82 | 83 | $item = ' 84 |
85 | '.rex_i18n::translate($field['title']).' 86 | '.$value.' 87 |
'; 88 | 89 | $items[] = $item; 90 | } 91 | 92 | if (!$items && $showMetaInfo === rex_system_setting_minibar_hide_empty_metainfos::HIDE) { 93 | return null; 94 | } 95 | 96 | return 97 | '
98 |
'.rex_i18n::msg('metainfo_minibar_article_title').'
99 | '.implode('', $items).' 100 |
'; 101 | }); 102 | 103 | rex_extension::register('MINIBAR_CLANG', static function (rex_extension_point $ep) { 104 | if (!rex::getUser()->isAdmin()) { 105 | return null; 106 | } 107 | 108 | $sqlFields = rex_sql::factory(); 109 | // $sqlFields->setDebug(); 110 | $fields = $sqlFields->getArray('SELECT `title`, `name` FROM '.rex::getTable('metainfo_field').' WHERE `name` LIKE :prefix ORDER BY priority', ['prefix' => 'clang_%']); 111 | 112 | if (!count($fields)) { 113 | return null; 114 | } 115 | 116 | $clang = $ep->getParam('clang'); 117 | $items = []; 118 | foreach ($fields as $field) { 119 | $item = ' 120 |
121 | '.rex_i18n::translate($field['title']).' 122 | '.$clang->getValue($field['name']).' 123 |
'; 124 | 125 | $items[] = $item; 126 | } 127 | 128 | return 129 | '
130 |
'.rex_i18n::msg('metainfo_minibar_clang_title').'
131 | '.implode('', $items).' 132 |
'; 133 | }); 134 | -------------------------------------------------------------------------------- /lib/element/article.php: -------------------------------------------------------------------------------- 1 | getArticle(); 19 | 20 | if (!$article instanceof rex_article) { 21 | return ''; 22 | } 23 | 24 | // Return if user have no rights to the site start article 25 | if (rex::isBackend() && !rex::getUser()->getComplexPerm('structure')->hasCategoryPerm($article->getCategoryId())) { 26 | return ''; 27 | } 28 | 29 | return 30 | '
31 | 32 | 33 | 34 | 35 | Artikel "' . $article->getName() . '" 36 | 37 |
'; 38 | } 39 | 40 | protected function renderComplete() 41 | { 42 | $articleId = rex_get('article_id'); 43 | $languageId = rex_get('current_lang'); 44 | $article = rex_article::get($articleId, $languageId); 45 | 46 | if (!$article instanceof rex_article) { 47 | return ''; 48 | } 49 | 50 | // Return if user have no rights to the site start article 51 | if (rex::isBackend() && !rex::getUser()->getComplexPerm('structure')->hasCategoryPerm($article->getCategoryId())) { 52 | return ''; 53 | } 54 | 55 | $articleLink = '' . rex_i18n::msg('structure_content_minibar_article_edit') . ' '; 56 | if (!rex::getUser()->getComplexPerm('structure')->hasCategoryPerm($article->getCategoryId())) { 57 | $articleLink = rex_i18n::msg('no_rights_to_edit'); 58 | } elseif (rex::isBackend()) { 59 | $articleLink = '' . rex_i18n::msg('structure_content_minibar_article_show') . ''; 60 | } 61 | 62 | $articlePath = []; 63 | $tree = $article->getParentTree(); 64 | if (!$article->isStartarticle()) { 65 | $tree[] = $article; 66 | } 67 | foreach ($tree as $parent) { 68 | $id = $parent->getId(); 69 | $item = rex_escape($parent->getName()); 70 | if (rex::isBackend() && rex::getUser()->getComplexPerm('structure')->hasCategoryPerm($id) && $parent->isStartarticle()) { 71 | $item = '' . rex_escape($parent->getName()) . ''; 72 | } elseif (!rex::isBackend()) { 73 | $item = '' . rex_escape($parent->getName()) . ''; 74 | } 75 | $articlePath[] = $item; 76 | } 77 | 78 | $groups = rex_extension::registerPoint(new rex_extension_point('MINIBAR_ARTICLE', '', [ 79 | 'article' => $article, 80 | ])); 81 | 82 | return 83 | '
84 | 85 | 86 | 87 | 88 | Artikel "' . $article->getName() . '" 89 | 90 |
91 |
92 |
' . rex_i18n::msg('structure_info') . '
93 |
94 |
95 | ' . rex_i18n::msg('structure_article_name') . ' 96 | ' . rex_escape($article->getName()) . ' 97 |
98 |
99 | ' . rex_i18n::msg('structure_status') . ' 100 | ' . ($article->isOnline() ? '' : '') . ' ' . ($article->isOnline() ? rex_i18n::msg('status_online') : rex_i18n::msg('status_offline')) . ' 101 |
102 |
103 | ' . rex_i18n::msg('structure_path') . ' 104 | ' . implode(' / ', $articlePath) . ' 105 |
106 |
107 | 108 | ' . $articleLink . ' 109 |
110 |
111 | ' . $groups . ' 112 |
113 | '; 114 | } 115 | 116 | private function getArticle() 117 | { 118 | $clangId = rex_request('clang', 'int'); 119 | $clangId = rex_clang::exists($clangId) ? $clangId : rex_clang::getStartId(); 120 | 121 | if (rex::isBackend()) { 122 | $article = rex_article::get(rex_request('article_id', 'int'), $clangId); 123 | 124 | if (!$article) { 125 | $article = rex_article::get(rex_request('category_id', 'int'), $clangId); 126 | } 127 | } else { 128 | $article = rex_article::getCurrent(); 129 | } 130 | 131 | if (!$article) { 132 | $article = rex_article::getSiteStartArticle(); 133 | } 134 | 135 | return $article; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # REDAXO Minibar 2 | 3 | ![Screenshot](https://github.com/FriendsOfREDAXO/minibar/blob/assets/minibar.png?raw=true) 4 | 5 | 6 | Die Minibar erweitert das Backend und Frontend um eine schwebende Leiste, die mit verschiedenen Elementen bestückt ist. Dazu gehören u.a. Systeminformationen, Informationen zum aktuellen Artikel, Buttons und Links zur Bearbeitung im Backend. Sie kann um weitere Elemente erweitert werden. 7 | 8 | Einstellungen zur Minibar finden sich in den Systemeinstellungen. 9 | 10 | ## Features 11 | 12 | - nur für im Backend angemeldete Benutzer 13 | - Elemente via api registrierbar 14 | - Unterstützt Lazy-Elemente die erst bei Interaktion initalisiert werden 15 | - via system/setting einstellbar ob generell aktiv/inaktiv 16 | - via system/setting einstellbar ob in Frontend und/oder Backend aktiv 17 | - via system/setting einstellbar ob in popups aktiv/inaktiv 18 | 19 | ## Vorraussetzungen 20 | 21 | Damit die Minibar vom Addon im Frontend eingebunden werden kann muss das HTML der Seite sowohl ein `` als auch ein `` enthalten. Die Minibar wird nur angezeigt, wenn man im Backend angemeldet ist. 22 | 23 | Je nach Einstellung unter "System" wird die Minibar nur im Frontend und/oder Backend angezeigt. 24 | 25 | ## Element API - Eigene Widgets erstellen 26 | 27 | Die Minibar kann mit eigenen Widgets/Elementen erweitert werden. Es gibt zwei Arten von Elementen: einfache Elemente und Lazy-Elemente. 28 | 29 | **📖 Vollständige Anleitung:** Siehe [WIDGETS.md](WIDGETS.md) für eine ausführliche Dokumentation mit vielen Beispielen. 30 | 31 | ### Einfache Elemente 32 | 33 | Einfache Elemente erweitern die Klasse `rex_minibar_element` und implementieren die Methode `render()`. 34 | 35 | #### Minimales Beispiel 36 | 37 | ```php 38 | 44 | Mein Element 45 | '; 46 | } 47 | } 48 | 49 | // Element registrieren 50 | rex_minibar::getInstance()->addElement(new mein_minibar_element()); 51 | ``` 52 | 53 | #### Vollständiges Beispiel mit allen Optionen 54 | 55 | ```php 56 | 63 | 64 | 65 | 66 | 67 | Info-Text 68 | 69 | 70 |
71 |
Zusätzliche Informationen
72 |
73 |
74 | Details 75 | Hier steht der Inhalt 76 |
77 |
78 |
'; 79 | } 80 | 81 | // Optional: Position des Elements festlegen 82 | public function getOrientation() 83 | { 84 | return rex_minibar_element::RIGHT; // oder ::LEFT 85 | } 86 | 87 | // Optional: Element als "danger" kennzeichnen (rote Färbung) 88 | public function isDanger() 89 | { 90 | return false; 91 | } 92 | 93 | // Optional: Element als "primary" kennzeichnen (blaue Färbung) 94 | public function isPrimary() 95 | { 96 | return true; 97 | } 98 | 99 | // Optional: Element als "warning" kennzeichnen (gelbe Färbung) 100 | public function isWarning() 101 | { 102 | return false; 103 | } 104 | } 105 | 106 | // Element registrieren 107 | rex_minibar::getInstance()->addElement(new mein_erweitertes_element()); 108 | ``` 109 | 110 | ### Lazy-Elemente (für aufwändige Inhalte) 111 | 112 | Wenn das Rendern eines Elements zeit- und/oder ressourcenaufwändig ist, kann die Klasse `rex_minibar_lazy_element` erweitert werden. Lazy-Elemente laden ihren vollständigen Inhalt erst bei Benutzerinteraktion (Mouse-Hover). 113 | 114 | #### Beispiel für ein Lazy-Element 115 | 116 | ```php 117 | 127 | Lade Daten... 128 | '; 129 | } 130 | 131 | /** 132 | * Vollständige Darstellung mit allen Daten 133 | * Wird erst bei Mouse-Hover geladen 134 | */ 135 | protected function renderComplete() 136 | { 137 | // Hier können aufwändige Datenbankabfragen oder Berechnungen stattfinden 138 | $data = $this->getDatenAusDatenbank(); 139 | 140 | return ' 141 |
142 | 143 | 144 | 145 | 146 | ' . count($data) . ' Einträge 147 | 148 |
149 |
150 |
Datenbank-Informationen
151 |
152 |
153 | ' . htmlspecialchars(implode(', ', $data)) . ' 154 |
155 |
156 |
'; 157 | } 158 | 159 | private function getDatenAusDatenbank() 160 | { 161 | // Beispiel für eine aufwändige Datenbankabfrage 162 | $sql = rex_sql::factory(); 163 | $sql->setQuery('SELECT * FROM rex_article LIMIT 10'); 164 | 165 | $results = []; 166 | foreach ($sql as $row) { 167 | $results[] = $row->getValue('name'); 168 | } 169 | return $results; 170 | } 171 | 172 | public function getOrientation() 173 | { 174 | return rex_minibar_element::LEFT; 175 | } 176 | } 177 | 178 | // Element registrieren 179 | rex_minibar::getInstance()->addElement(new mein_lazy_element()); 180 | ``` 181 | 182 | ### Wo werden Elemente registriert? 183 | 184 | Minibar-Elemente sollten in der `boot.php` Datei des eigenen Addons oder im Project-Addon registriert werden: 185 | 186 | ```php 187 | addElement(new mein_frontend_element()); 193 | } 194 | 195 | // Element nur im Backend anzeigen 196 | if (rex::isBackend()) { 197 | rex_minibar::getInstance()->addElement(new mein_backend_element()); 198 | } 199 | 200 | // Element überall anzeigen (Frontend und Backend) 201 | rex_minibar::getInstance()->addElement(new mein_element()); 202 | 203 | // Element nur bei bestimmten Bedingungen anzeigen 204 | if (rex::isFrontend() && rex::isDebugMode()) { 205 | rex_minibar::getInstance()->addElement(new mein_debug_element()); 206 | } 207 | ``` 208 | 209 | ### HTML-Struktur und CSS-Klassen 210 | 211 | Die Minibar verwendet spezifische CSS-Klassen für konsistentes Styling: 212 | 213 | - `rex-minibar-item`: Haupt-Container für ein Element 214 | - `rex-minibar-icon`: Container für Icons 215 | - `rex-minibar-value`: Container für Text/Werte 216 | - `rex-minibar-info`: Container für zusätzliche Informationen (wird bei Hover angezeigt) 217 | - `rex-minibar-info-header`: Überschrift des Info-Bereichs 218 | - `rex-minibar-info-group`: Gruppierung von Info-Elementen 219 | - `rex-minibar-info-piece`: Einzelnes Info-Element mit Titel und Wert 220 | 221 | #### Icons 222 | 223 | Für Icons können Font Awesome Icons verwendet werden: 224 | ```html 225 | 226 | ``` 227 | 228 | Beispiele: 229 | - `rex-minibar-icon--fa-info-circle` 230 | - `rex-minibar-icon--fa-database` 231 | - `rex-minibar-icon--fa-flag` 232 | - `rex-minibar-icon--fa-heartbeat` 233 | 234 | ### Best Practices 235 | 236 | 1. **Performance**: Verwende Lazy-Elemente für aufwändige Operationen (Datenbankabfragen, API-Calls) 237 | 2. **Sicherheit**: Überprüfe Benutzerrechte, bevor sensible Informationen angezeigt werden 238 | 3. **HTML-Escaping**: Verwende `rex_escape()` oder `htmlspecialchars()` für Benutzereingaben 239 | 4. **Internationalisierung**: Nutze `rex_i18n::msg()` für mehrsprachige Texte 240 | 5. **Konditionale Anzeige**: Registriere Elemente nur, wenn sie benötigt werden (z.B. nur im Frontend oder nur für Admins) 241 | 242 | ### Beispiele aus dem Minibar-Addon 243 | 244 | Im `lib/element/` Verzeichnis finden sich weitere Beispiele: 245 | - `time.php`: Einfaches Element mit Zeitanzeige 246 | - `system.php`: Komplexes Element mit Systeminformationen 247 | - `syslog.php`: Element mit bedingter Anzeige (nur für Admins) 248 | - `debug.php`: Element mit Warnung und Custom-Styling 249 | - `article.php`: Element mit Artikelinformationen im Frontend 250 | 251 | ## Entwicklung am Addon 252 | 253 | Styles werden durch re-install des minibar Addons neu compiliert (Vorraussetzung: be_style Addon ist aktiv) 254 | 255 | ## 💌 Give back some love 256 | 257 | [Consider supporting the project](https://github.com/sponsors/staabm), so we can make this tool even better even faster for everyone. 258 | -------------------------------------------------------------------------------- /lib/element/url2_yform.php: -------------------------------------------------------------------------------- 1 | getUrl2Info(); 14 | 15 | // Check permissions first 16 | $user = rex_backend_login::createUser(); 17 | if (!$user) { 18 | return ''; 19 | } 20 | 21 | // Das Widget ist für alle eingeloggten Backend-Benutzer sichtbar 22 | // Die spezifischen Berechtigungen werden bei den Buttons geprüft 23 | 24 | // Für spezifische YForm-Tabellenberechtigung prüfen wir später pro Tabelle 25 | 26 | // Always show the basic item 27 | $status = 'rex-minibar-url2-none'; 28 | $value = rex_i18n::msg('minibar_url2_yform_title'); 29 | $itemStyle = ''; 30 | 31 | if ($url2Info && $url2Info['is_yform_table']) { 32 | $status = 'rex-minibar-url2-found'; 33 | $value = $url2Info['table_label']; 34 | // Noch dunklerer grüner Hintergrund wenn YForm-Daten gefunden 35 | $itemStyle = ' style="background-color: #14532d; color: white; border-radius: 4px; padding: 2px 6px;"'; 36 | } 37 | 38 | $item = 39 | '
40 | 41 | 42 | 43 | 44 | ' . rex_escape($value) . ' 45 | 46 |
'; 47 | 48 | $info = ''; 49 | if ($url2Info && $url2Info['is_yform_table']) { 50 | // Get CSRF token for YForm operations 51 | $csrf_token = null; 52 | if (rex::isFrontend() && rex_backend_login::hasSession()) { 53 | rex::setProperty('redaxo', true); 54 | try { 55 | $table = rex_yform_manager_table::get($url2Info['table']); 56 | if ($table) { 57 | $_csrf_key = $table->getCSRFKey(); 58 | $_csrf_params = rex_csrf_token::factory($_csrf_key)->getUrlParams(); 59 | $csrf_token = $_csrf_params['_csrf_token']; 60 | } 61 | } catch (\Exception $e) { 62 | // CSRF token generation failed, continue without token 63 | } 64 | rex::setProperty('redaxo', false); 65 | } 66 | 67 | // Build edit URL 68 | $editUrl = ''; 69 | $tableUrl = ''; 70 | if ($url2Info['record_id']) { 71 | $recordParams = [ 72 | 'table_name' => $url2Info['table'], 73 | 'data_id' => $url2Info['record_id'], 74 | 'func' => 'edit' 75 | ]; 76 | if ($csrf_token) { 77 | $recordParams['_csrf_token'] = $csrf_token; 78 | } 79 | 80 | $editUrl = rex_url::backendPage('yform/manager/data_edit', $recordParams); 81 | } 82 | 83 | // Build table overview URL 84 | $tableParams = [ 85 | 'table_name' => $url2Info['table'] 86 | ]; 87 | $tableUrl = rex_url::backendPage('yform/manager/data_edit', $tableParams); 88 | 89 | // Check YForm table permissions for this specific table 90 | $table = rex_yform_manager_table::get($url2Info['table']); 91 | $canEditTable = false; 92 | $canViewTable = false; 93 | 94 | if ($table && $user) { 95 | // Admin kann alles 96 | if ($user->isAdmin()) { 97 | $canEditTable = true; 98 | $canViewTable = true; 99 | } else { 100 | // Berechtigungsprüfung wie in QuickNavigation 101 | $yform = rex_addon::get('yform'); 102 | $yperm_suffix = '_edit'; 103 | if (version_compare($yform->getVersion(), '4.0.0-dev', '<')) { 104 | $yperm_suffix = ''; 105 | } 106 | 107 | $complexPerm = $user->getComplexPerm('yform_manager_table' . $yperm_suffix); 108 | if ($complexPerm && method_exists($complexPerm, 'hasPerm')) { 109 | $canEditTable = call_user_func([$complexPerm, 'hasPerm'], $url2Info['table']); 110 | $canViewTable = $canEditTable; 111 | } else { 112 | $canEditTable = false; 113 | $canViewTable = false; 114 | } 115 | } 116 | } 117 | 118 | $editButton = ($editUrl && $canEditTable) ? 119 | '' . rex_i18n::msg('minibar_url2_yform_edit_record') . '' : 120 | '' . ($editUrl ? rex_i18n::msg('minibar_url2_yform_no_permission') : rex_i18n::msg('minibar_url2_yform_no_record')) . ''; 121 | 122 | $tableButton = $canViewTable ? 123 | '' . rex_i18n::msg('minibar_url2_yform_open_table') . '' : 124 | '' . rex_i18n::msg('minibar_url2_yform_no_permission') . ''; 125 | 126 | $info = 127 | '
128 |
' . rex_i18n::msg('minibar_url2_yform_header_found') . '
129 |
130 |
131 | ' . rex_i18n::msg('minibar_url2_yform_table') . ' 132 | ' . rex_escape($url2Info['table_label']) . ' 133 |
134 |
135 | ' . rex_i18n::msg('minibar_url2_yform_record_id') . ' 136 | ' . rex_escape($url2Info['record_id'] ?: rex_i18n::msg('minibar_url2_yform_not_found')) . ' 137 |
138 |
139 | 140 | ' . $editButton . $tableButton . ' 141 |
142 |
143 |
'; 144 | } else { 145 | // Info für den Fall, wenn keine URL2/YForm-Daten gefunden wurden 146 | $info = 147 | '
148 |
' . rex_i18n::msg('minibar_url2_yform_header_info') . '
149 |
150 |
151 | Info 152 | ' . rex_i18n::msg('minibar_url2_yform_no_data') . ' 153 |
154 |
155 |
'; 156 | } 157 | 158 | return $item . $info; 159 | } 160 | 161 | public function getOrientation() 162 | { 163 | return rex_minibar_element::LEFT; 164 | } 165 | 166 | private function getUrl2Info(): ?array 167 | { 168 | try { 169 | // Check if URL addon is available 170 | if (!rex_addon::get('url')->isAvailable()) { 171 | return null; 172 | } 173 | 174 | // Try to resolve current URL with URL2 175 | $url = \Url\Url::resolveCurrent(); 176 | if (!$url) { 177 | return null; 178 | } 179 | 180 | // Get the resolved URL data (this is a YForm dataset object, not array) 181 | $dataset = $url->getDataset(); 182 | 183 | // Get table name from URL profile 184 | $profile = $url->getProfile(); 185 | if (!$profile) { 186 | return null; 187 | } 188 | 189 | $tableName = $profile->getTableName(); 190 | if (!$tableName) { 191 | return null; 192 | } 193 | 194 | // Check if it's a YForm table 195 | $table = rex_yform_manager_table::get($tableName); 196 | if (!$table) { 197 | return null; 198 | } 199 | 200 | // Get record ID from dataset (object method, not array access) 201 | $recordId = null; 202 | if ($dataset && method_exists($dataset, 'getId')) { 203 | $recordId = $dataset->getId(); 204 | } 205 | 206 | // Use table description (name field) instead of technical table name 207 | $tableLabel = $table->getName() ?: $tableName; 208 | 209 | return [ 210 | 'url' => $_SERVER['REQUEST_URI'] ?? '', 211 | 'is_yform_table' => true, 212 | 'table' => $tableName, 213 | 'table_label' => $tableLabel, // Use human-readable table description from name field 214 | 'record_id' => $recordId, 215 | 'dataset' => $dataset 216 | ]; 217 | 218 | } catch (\Exception $e) { 219 | // URL2 not available or error occurred 220 | return null; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /scss/minibar.scss: -------------------------------------------------------------------------------- 1 | $element-hover-bg: lighten($color-a-dark, 5%); 2 | 3 | .rex-nav-main, 4 | .rex-page-main { 5 | padding-bottom: ($minibar-height * 2); 6 | } 7 | .rex-icon-minibar-close { 8 | @extend .rex-minibar-icon--fa-times; 9 | } 10 | .rex-icon-minibar-open { 11 | @extend .rex-minibar-icon--fa-window-minimize; 12 | } 13 | 14 | .rex-minibar { 15 | position: fixed; 16 | right: 0; 17 | bottom: 0; 18 | z-index: 100000; 19 | padding-right: $minibar-height; 20 | color: $color-a; 21 | direction: ltr; 22 | font-size: 11px; 23 | font-family: -apple-system, BlinkMacSystemFont, "Open Sans", "Helvetica Neue", Arial, sans-serif; 24 | text-align: left; 25 | 26 | &[data-minibar="false"] { 27 | height: $minibar-height; 28 | } 29 | 30 | &[data-minibar="true"], 31 | &.rex-minibar-backend { 32 | left: 0; 33 | } 34 | &.rex-minibar-backend { 35 | padding-right: 0; 36 | } 37 | 38 | * { 39 | box-sizing: content-box; 40 | vertical-align: baseline; 41 | letter-spacing: normal; 42 | width: auto; 43 | } 44 | 45 | &-close, 46 | &-opener { 47 | position: absolute; 48 | right: 0; 49 | top: 0; 50 | z-index: 100001; 51 | width: $minibar-height; 52 | height: $minibar-height; 53 | font-size: 18px; 54 | cursor: pointer; 55 | transition: all 200ms ease-in-out; 56 | 57 | &:hover { 58 | background: $color-a-dark; 59 | color: $color-a-light; 60 | } 61 | } 62 | &-close { 63 | background: $color-a-dark; 64 | color: $link-color; 65 | } 66 | &-opener { 67 | background: $color-a-light; 68 | color: $color-a; 69 | } 70 | 71 | &-close-icon, 72 | &-opener-icon { 73 | position: absolute; 74 | top: 50%; 75 | left: 50%; 76 | transform: translate(-50%, -50%); 77 | /* Fix bugs ON Safari */ 78 | -webkit-transform: translate3d(-50%, -50%, 0); 79 | } 80 | 81 | &[data-minibar="false"] &-close, 82 | &[data-minibar="false"] &-elements { 83 | display: none; 84 | } 85 | 86 | &[data-minibar="true"] &-opener { 87 | display: none; 88 | } 89 | 90 | &-elements { 91 | width: 100%; 92 | background: darken($color-a-darker, 2%); 93 | &:after { 94 | content: ''; 95 | clear: both; 96 | display: table; 97 | } 98 | } 99 | &-element { 100 | cursor: default; 101 | display: block; 102 | float: left; 103 | height: $minibar-height; 104 | margin-right: 0; 105 | padding: 0 10px; 106 | white-space: nowrap; 107 | //transition: all 200ms ease-in-out; 108 | 109 | &:hover { 110 | position: relative; 111 | background-color: $element-hover-bg; 112 | } 113 | 114 | &-right { 115 | float: right; 116 | } 117 | 118 | > a { 119 | display: inline-block; 120 | text-decoration: none; 121 | 122 | &:hover { 123 | text-decoration: none; 124 | } 125 | } 126 | span { 127 | display: inline-block; 128 | } 129 | } 130 | 131 | &-item { 132 | display: inline-block; 133 | height: $minibar-height; 134 | } 135 | 136 | &-info { 137 | position: absolute; 138 | left: 0; 139 | bottom: $minibar-height; 140 | display: none; 141 | background-color: rgba($color-a-darker, 0.97); 142 | color: $color-a-light; 143 | //padding: 9px 0; 144 | padding-bottom: 15px; 145 | } 146 | 147 | &-info-header { 148 | color: $color-a-lighter; 149 | background-color: #1e2832; 150 | padding: 10px 15px; 151 | font-size: 12px; 152 | font-weight: bold; 153 | } 154 | 155 | &-info-group { 156 | padding: 10px 15px 0 15px; 157 | } 158 | 159 | &-info-group + &-info-group { 160 | margin-top: 15px; 161 | padding-top: 15px; 162 | position: relative; 163 | 164 | &:before { 165 | content: ''; 166 | width: calc(100% - 26px); 167 | left: 13px; 168 | top: 0; 169 | position: absolute; 170 | height: 1px; 171 | background-color: #515f6c; 172 | } 173 | } 174 | 175 | &-info-group-title { 176 | color: $color-a-lighter; 177 | font-size: 12px; 178 | font-weight: 700; 179 | vertical-align: top; 180 | padding: 5px 0; 181 | } 182 | 183 | &-info-piece { 184 | display: table-row; 185 | border-collapse: collapse; 186 | 187 | a { 188 | color: $link-color; 189 | transition: color 200ms ease-in-out; 190 | 191 | &:hover { 192 | color: #58b5ff; 193 | } 194 | } 195 | 196 | span { 197 | padding: 5px 0; 198 | color: $color-a-light; 199 | font-size: 13px; 200 | 201 | &.title { 202 | display: table-cell; 203 | min-width: 70px; 204 | padding: 5px 15px 5px 0; 205 | border-collapse: collapse; 206 | color: $color-a; 207 | font-size: 12px; 208 | vertical-align: top; 209 | } 210 | } 211 | } 212 | 213 | &-element:hover &-info { 214 | display: block; 215 | //padding: 10px; 216 | max-width: 480px; 217 | max-height: 480px; 218 | word-wrap: break-word; 219 | overflow: hidden; 220 | overflow-y: auto; 221 | } 222 | 223 | &-element-right &-info { 224 | left: auto; 225 | right: 0; 226 | } 227 | 228 | &-icon { 229 | display: inline-block; 230 | color: $color-a; 231 | font-size: 16px; 232 | } 233 | 234 | &-icon + &-value, 235 | &-value + &-value { 236 | margin-left: 5px; 237 | } 238 | 239 | &-value { 240 | color: $color-a-light; 241 | font-size: 13px; 242 | line-height: $minibar-height; 243 | } 244 | 245 | &-label { 246 | display: inline; 247 | color: $color-a; 248 | font-size: 12px; 249 | line-height: $minibar-height; 250 | } 251 | 252 | &-status-danger { 253 | background-color: $brand-danger; 254 | } 255 | 256 | &-status-primary { 257 | background-color: $brand-primary; 258 | color: #fff; 259 | } 260 | 261 | &-status-warning { 262 | background-color: $brand-warning; 263 | } 264 | 265 | &-info &-status-green, 266 | &-info &-status-red { 267 | min-width: 15px; 268 | min-height: 13px; 269 | margin: 0; 270 | padding: 5px 0; 271 | color: #fff; 272 | vertical-align: middle; 273 | text-align: center; 274 | } 275 | 276 | &-info &-status-green { 277 | color: $color-online; 278 | } 279 | 280 | &-info &-status-red { 281 | color: $brand-danger; 282 | } 283 | } 284 | 285 | 286 | .rex-minibar-element-system { 287 | .rex-redaxo-logo { 288 | height: 10px; 289 | width: 70px; 290 | vertical-align: middle; 291 | margin-right: -62px; 292 | 293 | 294 | @media (max-width: 767px) { 295 | path.rex-redaxo-logo-e, 296 | path.rex-redaxo-logo-d, 297 | path.rex-redaxo-logo-a, 298 | path.rex-redaxo-logo-x, 299 | path.rex-redaxo-logo-o { 300 | display: none; 301 | } 302 | } 303 | @media (min-width: 768px) { 304 | height: 10px; 305 | width: 60px; 306 | margin-right: -14px; 307 | } 308 | 309 | path.rex-redaxo-logo-r, 310 | path.rex-redaxo-logo-e, 311 | path.rex-redaxo-logo-d, 312 | path.rex-redaxo-logo-a, 313 | path.rex-redaxo-logo-x, 314 | path.rex-redaxo-logo-o { 315 | fill: #fff; 316 | } 317 | 318 | path.rex-redaxo-logo-cms, 319 | path.rex-redaxo-logo-reg { 320 | display: none; 321 | } 322 | } 323 | } 324 | 325 | 326 | .rex-minibar-close-icon { 327 | @extend .rex-minibar-icon--fa; 328 | @extend .rex-minibar-icon--fa-times; 329 | } 330 | .rex-minibar-opener-icon { 331 | @extend .rex-minibar-icon--fa; 332 | @extend .rex-minibar-icon--fa-window-minimize; 333 | } 334 | 335 | .rex-minibar-icon .rex-syslog-ok { 336 | color: #3bb594; 337 | } 338 | .rex-minibar-icon .rex-syslog-changed { 339 | color: yellow; 340 | } 341 | 342 | // ---------------------------------------------------------------------------- 343 | 344 | // additional dark theme variables 345 | // copied over from _variables-dark.scss so that minibar can be used with REDAXO <5.13 for now 346 | // TODO: use @import instead in future minibar versions! 347 | 348 | // Grey 349 | $color-grey-1: #151c22; // darken($color-a-darker, 10%); 350 | $color-grey-2: #1b232c; // darken($color-a-darker, 7%); 351 | $color-grey-3: #202b35; // darken($color-a-darker, 4%); 352 | $color-grey-4: #26323f; // darken($color-a-darker, 1%); 353 | $color-grey-5: #2e3b4a; // darken($color-a-dark, 2%); 354 | 355 | // Blue 356 | $color-blue-1: #0b304d; // saturate(darken($color-b, 40%), 10%); 357 | $color-blue-2: #114b7a; // saturate(darken($color-b, 30%), 10%); 358 | $color-blue-3: #1867a6; // saturate(darken($color-b, 20%), 10%); 359 | $color-blue-4: #409be4; // saturate($color-b, 10%); 360 | $color-blue-5: #7abaec; // saturate(lighten($color-b, 13%), 10%); 361 | 362 | // Green 363 | // mix in blue when green gets darker 364 | $color-green-1: #1a3332; // darken(mix($color-b, $color-d, 40%), 35%); 365 | $color-green-2: #1f3d3c; // darken(mix($color-b, $color-d, 40%), 32%); 366 | $color-green-3: #0d6a38; // saturate(darken($color-d, 30%), 40%); 367 | 368 | // text 369 | $color-text-dark: rgba(0, 0, 0, 0.75); 370 | $color-text-dark-inactive: rgba(0, 0, 0, 0.45); 371 | $color-text-light: rgba(255, 255, 255, 0.75); 372 | $color-text-light-inactive: rgba(255, 255, 255, 0.45); 373 | 374 | // dark brand colors 375 | $dark-brand-primary: $color-blue-3; 376 | $dark-brand-success: $color-green-3; 377 | $dark-brand-info: $color-blue-3; 378 | $dark-brand-warning: #78641e; 379 | $dark-brand-danger: #801919; 380 | 381 | 382 | @mixin _minibar-dark { 383 | 384 | .rex-minibar { 385 | color: $color-text-light-inactive; 386 | 387 | &-close, 388 | &-opener { 389 | &:hover { 390 | background-color: $color-grey-2; 391 | color: $color-text-light; 392 | } 393 | } 394 | &-close { 395 | background-color: $color-grey-3; 396 | color: $color-blue-4; 397 | } 398 | &-opener { 399 | background-color: $color-grey-2; 400 | color: $color-blue-4; 401 | } 402 | 403 | &-elements { 404 | background: darken($color-grey-1, 1.5%); // avoid using exactly the same color like page elements, so let’s go even darker here! 405 | } 406 | 407 | &-element { 408 | &:hover { 409 | background-color: rgba(darken($color-grey-2, 1.5%), 0.97); 410 | } 411 | } 412 | 413 | &-info { 414 | background-color: rgba(darken($color-grey-2, 1.5%), 0.97); 415 | color: $color-text-light; 416 | } 417 | 418 | &-info-header { 419 | color: $color-text-light; 420 | background-color: darken($color-grey-1, 1.5%); // avoid same colors 421 | } 422 | 423 | &-info-group + &-info-group { 424 | &:before { 425 | background-color: $color-grey-2; 426 | } 427 | } 428 | 429 | &-info-group-title { 430 | color: $color-text-light; 431 | } 432 | 433 | &-info-piece { 434 | a { 435 | color: $color-blue-4; 436 | 437 | &:hover { 438 | color: $color-blue-5; 439 | } 440 | } 441 | 442 | span { 443 | color: $color-text-light; 444 | 445 | &.title { 446 | color: $color-text-light-inactive; 447 | } 448 | } 449 | } 450 | 451 | &-icon { 452 | color: $color-text-light; 453 | } 454 | 455 | &-value { 456 | color: $color-text-light; 457 | } 458 | 459 | &-label { 460 | color: $color-text-light-inactive; 461 | } 462 | 463 | &-status-danger { 464 | background-color: $dark-brand-danger; 465 | } 466 | 467 | &-status-primary { 468 | background-color: $dark-brand-primary; 469 | color: $color-text-light; 470 | } 471 | 472 | &-status-warning { 473 | background-color: $dark-brand-warning; 474 | } 475 | 476 | &-info &-status-green, 477 | &-info &-status-red { 478 | color: $color-text-light; 479 | } 480 | 481 | &-info &-status-green { 482 | color: $dark-brand-success; 483 | } 484 | 485 | &-info &-status-red { 486 | color: $dark-brand-danger; 487 | } 488 | } 489 | 490 | .rex-minibar-element-system { 491 | .rex-redaxo-logo { 492 | path.rex-redaxo-logo-r, 493 | path.rex-redaxo-logo-e, 494 | path.rex-redaxo-logo-d, 495 | path.rex-redaxo-logo-a, 496 | path.rex-redaxo-logo-x, 497 | path.rex-redaxo-logo-o { 498 | fill: rgba(255, 255, 255, 0.8) !important; // must be important to win over #id 499 | } 500 | } 501 | } 502 | 503 | .rex-minibar-icon .rex-syslog-ok { 504 | color: $dark-brand-success; 505 | } 506 | .rex-minibar-icon .rex-syslog-changed { 507 | color: $dark-brand-warning; 508 | } 509 | } 510 | 511 | body.rex-theme-dark { 512 | @include _minibar-dark; 513 | } 514 | 515 | @media (prefers-color-scheme: dark) { 516 | body.rex-has-theme:not(.rex-theme-light) { 517 | @include _minibar-dark; 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /WIDGETS.md: -------------------------------------------------------------------------------- 1 | # Minibar Widgets erstellen - Ausführliche Anleitung 2 | 3 | Diese Anleitung erklärt detailliert, wie eigene Widgets für die REDAXO Minibar erstellt werden können. 4 | 5 | ## Inhaltsverzeichnis 6 | 7 | 1. [Grundlagen](#grundlagen) 8 | 2. [Einfache Widgets](#einfache-widgets) 9 | 3. [Lazy Widgets](#lazy-widgets) 10 | 4. [Praxis-Beispiele](#praxis-beispiele) 11 | 5. [API-Referenz](#api-referenz) 12 | 13 | ## Grundlagen 14 | 15 | Die Minibar ist eine schwebende Toolbar im REDAXO Backend und Frontend, die mit eigenen Widgets erweitert werden kann. Es gibt zwei Arten von Widgets: 16 | 17 | - **Einfache Widgets** (`rex_minibar_element`): Für schnelle, einfache Inhalte 18 | - **Lazy Widgets** (`rex_minibar_lazy_element`): Für aufwändige Inhalte, die erst bei Bedarf geladen werden 19 | 20 | ## Einfache Widgets 21 | 22 | ### Minimales Widget 23 | 24 | Das einfachste Widget besteht aus einer Klasse, die `rex_minibar_element` erweitert und die `render()`-Methode implementiert: 25 | 26 | ```php 27 | 33 | Hello World! 34 | '; 35 | } 36 | } 37 | ``` 38 | 39 | ### Widget mit Icon 40 | 41 | Icons machen Widgets visuell ansprechender. Die Minibar unterstützt Font Awesome Icons: 42 | 43 | ```php 44 | 50 | 51 | 52 | 53 | 54 | Favoriten 55 | 56 | '; 57 | } 58 | } 59 | ``` 60 | 61 | ### Widget mit zusätzlichen Informationen 62 | 63 | Widgets können bei Mouse-Hover zusätzliche Informationen anzeigen: 64 | 65 | ```php 66 | getChildren(true)->count() : 0; 73 | 74 | return ' 75 |
76 | 77 | 78 | 79 | 80 | ' . $articleCount . ' Artikel 81 | 82 |
83 |
84 |
Artikel-Statistik
85 |
86 |
87 | Gesamt 88 | ' . $articleCount . ' Artikel 89 |
90 |
91 | Status 92 | Aktiv 93 |
94 |
95 |
'; 96 | } 97 | } 98 | ``` 99 | 100 | ### Widget mit Links 101 | 102 | Widgets können klickbare Links enthalten: 103 | 104 | ```php 105 | 111 | 112 | 113 | 114 | 115 | 116 | Struktur 117 | 118 | 119 | '; 120 | } 121 | } 122 | ``` 123 | 124 | ### Widget mit Position 125 | 126 | Die Position eines Widgets (links oder rechts) kann festgelegt werden: 127 | 128 | ```php 129 | 135 | Rechts positioniert 136 | '; 137 | } 138 | 139 | public function getOrientation() 140 | { 141 | return rex_minibar_element::RIGHT; // oder ::LEFT 142 | } 143 | } 144 | ``` 145 | 146 | ### Widget mit Status-Färbung 147 | 148 | Widgets können farblich hervorgehoben werden: 149 | 150 | ```php 151 | 157 | Wichtiger Hinweis 158 | '; 159 | } 160 | 161 | // Für rote Färbung (Gefahr/Fehler) 162 | public function isDanger() 163 | { 164 | return true; 165 | } 166 | 167 | // Oder für blaue Färbung (Primär/Info) 168 | public function isPrimary() 169 | { 170 | return false; 171 | } 172 | 173 | // Oder für gelbe Färbung (Warnung) 174 | public function isWarning() 175 | { 176 | return false; 177 | } 178 | } 179 | ``` 180 | 181 | ## Lazy Widgets 182 | 183 | Lazy Widgets laden ihren vollständigen Inhalt erst, wenn der Benutzer mit der Maus über das Widget fährt. Dies ist ideal für aufwändige Operationen. 184 | 185 | ### Basis Lazy Widget 186 | 187 | ```php 188 | 195 | Klick für Details 196 | '; 197 | } 198 | 199 | protected function renderComplete() 200 | { 201 | // Aufwändige Daten erst hier laden 202 | $data = $this->loadExpensiveData(); 203 | 204 | return '
205 | ' . count($data) . ' Einträge 206 |
207 |
208 |
Details
209 |
210 |
211 | ' . implode('
', $data) . '
212 |
213 |
214 |
'; 215 | } 216 | 217 | private function loadExpensiveData() 218 | { 219 | // Beispiel: Datenbankabfrage 220 | $sql = rex_sql::factory(); 221 | $sql->setQuery('SELECT name FROM rex_article LIMIT 5'); 222 | 223 | $results = []; 224 | foreach ($sql as $row) { 225 | $results[] = rex_escape($row->getValue('name')); 226 | } 227 | return $results; 228 | } 229 | } 230 | ``` 231 | 232 | ## Praxis-Beispiele 233 | 234 | ### Beispiel 1: Benutzer-Info Widget 235 | 236 | ```php 237 | 249 | 250 | 251 | 252 | 253 | ' . rex_escape($user->getValue('login')) . ' 254 | 255 | 256 |
257 |
Benutzer-Informationen
258 |
259 |
260 | Name 261 | ' . rex_escape($user->getValue('name')) . ' 262 |
263 |
264 | E-Mail 265 | ' . rex_escape($user->getValue('email')) . ' 266 |
267 |
268 | Rolle 269 | ' . ($user->isAdmin() ? 'Administrator' : 'Benutzer') . ' 270 |
271 |
272 |
'; 273 | } 274 | 275 | public function getOrientation() 276 | { 277 | return rex_minibar_element::RIGHT; 278 | } 279 | } 280 | ``` 281 | 282 | ### Beispiel 2: Cache-Status Widget (Lazy) 283 | 284 | ```php 285 | 291 | 292 | 293 | 294 | Cache 295 | '; 296 | } 297 | 298 | protected function renderComplete() 299 | { 300 | $cacheDir = rex_path::addonCache('structure'); 301 | $cacheFiles = glob($cacheDir . '*.cache') ?: []; 302 | $totalSize = 0; 303 | 304 | if ($cacheFiles) { 305 | foreach ($cacheFiles as $file) { 306 | $size = filesize($file); 307 | if ($size !== false) { 308 | $totalSize += $size; 309 | } 310 | } 311 | } 312 | 313 | $sizeInMB = round($totalSize / 1024 / 1024, 2); 314 | 315 | return ' 316 |
317 | 318 | 319 | 320 | 321 | ' . count($cacheFiles) . ' Cache-Dateien 322 | 323 |
324 |
325 |
Cache-Informationen
326 |
327 |
328 | Dateien 329 | ' . count($cacheFiles) . ' 330 |
331 |
332 | Größe 333 | ' . $sizeInMB . ' MB 334 |
335 |
336 | Cache leeren 337 |
338 |
339 |
'; 340 | } 341 | 342 | public function getOrientation() 343 | { 344 | return rex_minibar_element::RIGHT; 345 | } 346 | } 347 | ``` 348 | 349 | ### Beispiel 3: Debug-Modus Warnung 350 | 351 | ```php 352 | 364 | 365 | 366 | 367 | 368 | Debug-Modus aktiv 369 | 370 | 371 |
372 |
Warnung
373 |
374 |
375 | Der Debug-Modus sollte auf Live-Systemen deaktiviert werden! 376 |
377 |
378 |
'; 379 | } 380 | 381 | public function isDanger() 382 | { 383 | return true; // Rote Färbung 384 | } 385 | } 386 | ``` 387 | 388 | ### Beispiel 4: Mehrsprachiges Widget 389 | 390 | ```php 391 | '; 402 | $langList .= '' . rex_escape($lang->getName()) . ''; 403 | $langList .= ''; 404 | } 405 | 406 | return ' 407 |
408 | 409 | 410 | 411 | 412 | ' . rex_escape($currentLang->getName()) . ' 413 | 414 |
415 |
416 |
' . rex_i18n::msg('clang_select') . '
417 |
418 | ' . $langList . ' 419 |
420 |
'; 421 | } 422 | } 423 | ``` 424 | 425 | ## Widget registrieren 426 | 427 | Widgets werden in der `boot.php` des eigenen Addons oder im Project-Addon registriert: 428 | 429 | ```php 430 | addElement(new my_simple_widget()); 435 | 436 | // Widget nur im Frontend anzeigen 437 | if (rex::isFrontend()) { 438 | rex_minibar::getInstance()->addElement(new my_frontend_widget()); 439 | } 440 | 441 | // Widget nur im Backend anzeigen 442 | if (rex::isBackend()) { 443 | rex_minibar::getInstance()->addElement(new my_backend_widget()); 444 | } 445 | 446 | // Widget nur für Administratoren 447 | if (rex::getUser() && rex::getUser()->isAdmin()) { 448 | rex_minibar::getInstance()->addElement(new my_admin_widget()); 449 | } 450 | 451 | // Widget nur im Debug-Modus 452 | if (rex::isDebugMode()) { 453 | rex_minibar::getInstance()->addElement(new my_debug_widget()); 454 | } 455 | 456 | // Mehrere Bedingungen kombinieren 457 | if (rex::isFrontend() && rex::getUser() && rex::getUser()->isAdmin()) { 458 | rex_minibar::getInstance()->addElement(new my_conditional_widget()); 459 | } 460 | ``` 461 | 462 | ## API-Referenz 463 | 464 | ### rex_minibar_element 465 | 466 | Basis-Klasse für einfache Widgets. 467 | 468 | #### Methoden 469 | 470 | **`render(): string`** (abstract, erforderlich) 471 | - Gibt den HTML-Code des Widgets zurück 472 | - Muss implementiert werden 473 | 474 | **`getOrientation(): string`** 475 | - Gibt die Position zurück: `rex_minibar_element::LEFT` oder `rex_minibar_element::RIGHT` 476 | - Standard: `LEFT` 477 | 478 | **`isDanger(): bool`** 479 | - Gibt `true` zurück für rote Färbung (Gefahr/Fehler) 480 | - Standard: `false` 481 | 482 | **`isPrimary(): bool`** 483 | - Gibt `true` zurück für blaue Färbung (Primär/Info) 484 | - Standard: `false` 485 | 486 | **`isWarning(): bool`** 487 | - Gibt `true` zurück für gelbe Färbung (Warnung) 488 | - Standard: `false` 489 | 490 | ### rex_minibar_lazy_element 491 | 492 | Erweiterte Klasse für Lazy-Loading Widgets. 493 | 494 | #### Methoden 495 | 496 | **`renderFirstView(): string`** (abstract, erforderlich) 497 | - Gibt den initialen HTML-Code zurück (schnell) 498 | - Wird beim Seitenladen ausgeführt 499 | 500 | **`renderComplete(): string`** (abstract, erforderlich) 501 | - Gibt den vollständigen HTML-Code zurück 502 | - Wird erst bei Mouse-Hover ausgeführt 503 | 504 | **`isFirstView(): bool`** (static) 505 | - Prüft, ob gerade die erste Ansicht gerendert wird 506 | - Intern verwendet 507 | 508 | ### rex_minibar 509 | 510 | Manager-Klasse für die Minibar. 511 | 512 | #### Methoden 513 | 514 | **`getInstance(): rex_minibar`** (static) 515 | - Gibt die Singleton-Instanz zurück 516 | 517 | **`addElement(rex_minibar_element $element): void`** 518 | - Registriert ein neues Widget 519 | 520 | **`isActive(): bool`** 521 | - Prüft, ob die Minibar aktiv ist 522 | 523 | **`shouldRender(): bool`** 524 | - Prüft, ob die Minibar gerendert werden soll 525 | 526 | ## HTML-Struktur und CSS-Klassen 527 | 528 | ### Basis-Struktur 529 | 530 | ```html 531 |
532 | 533 | 534 | 535 | 536 | 537 | 538 |
539 | ``` 540 | 541 | ### Mit zusätzlichen Informationen 542 | 543 | ```html 544 |
545 | Haupttext 546 |
547 |
548 |
Überschrift
549 |
550 |
551 | Titel 552 | Inhalt 553 |
554 |
555 |
556 | ``` 557 | 558 | ### CSS-Klassen 559 | 560 | | Klasse | Beschreibung | 561 | |--------|--------------| 562 | | `rex-minibar-item` | Haupt-Container für Widget | 563 | | `rex-minibar-icon` | Container für Icons | 564 | | `rex-minibar-value` | Container für Text/Werte | 565 | | `rex-minibar-info` | Container für Zusatzinfos (bei Hover) | 566 | | `rex-minibar-info-header` | Überschrift der Zusatzinfos | 567 | | `rex-minibar-info-group` | Gruppe von Info-Elementen | 568 | | `rex-minibar-info-piece` | Einzelnes Info-Element | 569 | | `rex-minibar-icon--fa` | Font Awesome Icon Basis | 570 | | `rex-minibar-icon--fa-{name}` | Spezifisches Font Awesome Icon | 571 | 572 | ## Best Practices 573 | 574 | ### Performance 575 | 576 | 1. **Lazy Loading nutzen**: Verwende `rex_minibar_lazy_element` für: 577 | - Datenbankabfragen 578 | - API-Calls 579 | - Datei-Operationen 580 | - Komplexe Berechnungen 581 | 582 | 2. **Caching**: Speichere aufwändige Daten zwischen: 583 | ```php 584 | private function getCachedData() 585 | { 586 | $key = 'my_widget_data'; 587 | $cache = new rex_cache_file([ 588 | 'cache_dir' => rex_path::cache('my_widget/'), 589 | 'ttl' => 300, // 5 Minuten Cache 590 | ]); 591 | 592 | $data = $cache->get($key, function () { 593 | return $this->loadExpensiveData(); 594 | }); 595 | 596 | return $data; 597 | } 598 | ``` 599 | 600 | ### Sicherheit 601 | 602 | 1. **Benutzerrechte prüfen**: 603 | ```php 604 | public function render() 605 | { 606 | if (!rex::getUser() || !rex::getUser()->isAdmin()) { 607 | return ''; // Nichts anzeigen 608 | } 609 | // ... 610 | } 611 | ``` 612 | 613 | 2. **HTML escapen**: 614 | ```php 615 | $output = rex_escape($userInput); 616 | // oder 617 | $output = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8'); 618 | ``` 619 | 620 | 3. **SQL Injection vermeiden**: 621 | ```php 622 | $sql = rex_sql::factory(); 623 | $sql->setQuery('SELECT * FROM table WHERE id = ?', [$id]); // Mit Platzhalter 624 | ``` 625 | 626 | ### Internationalisierung 627 | 628 | Verwende `rex_i18n::msg()` für alle Texte: 629 | 630 | ```php 631 | // In lang/de_de.lang: 632 | my_widget_title = Mein Widget 633 | my_widget_info = Informationen 634 | 635 | // Im PHP-Code: 636 | $title = rex_i18n::msg('my_widget_title'); 637 | $info = rex_i18n::msg('my_widget_info'); 638 | ``` 639 | 640 | ### Fehlerbehandlung 641 | 642 | ```php 643 | public function render() 644 | { 645 | try { 646 | $data = $this->loadData(); 647 | return $this->renderWithData($data); 648 | } catch (Exception $e) { 649 | // Fehler loggen 650 | rex_logger::logException($e); 651 | 652 | // Fallback anzeigen 653 | return '
654 | Fehler beim Laden 655 |
'; 656 | } 657 | } 658 | ``` 659 | 660 | ### Code-Organisation 661 | 662 | Organisiere deine Widget-Klassen übersichtlich: 663 | 664 | ``` 665 | /redaxo/src/addons/mein_addon/ 666 | ├── boot.php 667 | ├── lib/ 668 | │ └── minibar/ 669 | │ ├── base.php 670 | │ ├── user_widget.php 671 | │ ├── cache_widget.php 672 | │ └── debug_widget.php 673 | └── lang/ 674 | ├── de_de.lang 675 | └── en_gb.lang 676 | ``` 677 | 678 | In `boot.php`: 679 | ```php 680 | addElement(new my_user_widget()); 687 | rex_minibar::getInstance()->addElement(new my_cache_widget()); 688 | ``` 689 | 690 | ## Weitere Ressourcen 691 | 692 | - Beispiel-Widgets im Minibar-Addon: `/redaxo/src/addons/minibar/lib/element/` 693 | - REDAXO Dokumentation: https://redaxo.org/doku/ 694 | - Font Awesome Icons: https://fontawesome.com/icons 695 | 696 | ## Support 697 | 698 | Bei Fragen oder Problemen: 699 | - GitHub Issues: https://github.com/FriendsOfREDAXO/minibar/issues 700 | - REDAXO Slack: https://redaxo.org/slack/ 701 | --------------------------------------------------------------------------------