├── .gitignore ├── elgg-services.php ├── .github └── workflows │ ├── lint.yml │ ├── create-zip-release.yml │ ├── phpunit.yml │ └── tweet-release.yml ├── views └── default │ ├── forms │ ├── widgets │ │ ├── advanced.mjs │ │ ├── save.php │ │ └── advanced.php │ └── widget_manager │ │ ├── manage_widgets │ │ ├── widget.css │ │ └── widget.php │ │ └── widget_page.php │ ├── search │ └── object │ │ └── widget_page.php │ ├── widget_manager │ ├── widgets │ │ ├── custom_more.php │ │ └── settings.php │ ├── group_tool_widgets.php │ ├── lazy_loading.mjs │ ├── forms │ │ └── groups_widget_access.php │ ├── site.css │ └── fluid.mjs │ ├── admin │ └── widgets │ │ ├── pages.php │ │ ├── manage.mjs │ │ ├── manage.php │ │ └── cleanup.php │ ├── page │ └── layouts │ │ ├── widgets │ │ └── add_button.php │ │ └── widgets.php │ ├── resources │ ├── widget_manager │ │ ├── custom_index.php │ │ └── widget_page.php │ └── widgets │ │ ├── add_panel.mjs │ │ └── add_panel.php │ ├── object │ ├── widget_page.php │ └── widget.php │ ├── elgg │ └── widgets.mjs │ └── plugins │ └── widget_manager │ └── settings.php ├── actions └── widget_manager │ ├── fluid_order.php │ ├── force_tool_widgets.php │ ├── cleanup.php │ ├── lazy_load_widgets.php │ ├── groups │ └── update_widget_access.php │ ├── widget_page.php │ └── manage_widgets.php ├── composer.json ├── classes ├── ColdTrick │ └── WidgetManager │ │ ├── Settings.php │ │ ├── Menus │ │ ├── Entity.php │ │ ├── Title.php │ │ └── AdminHeader.php │ │ ├── Bootstrap.php │ │ ├── Seeder.php │ │ ├── WidgetsSettingsConfig.php │ │ ├── Access.php │ │ ├── Widgets.php │ │ └── Groups.php ├── WidgetManagerWidget.php └── WidgetPage.php ├── README.md ├── languages ├── fr.php ├── es.php ├── en.php └── nl.php ├── elgg-plugin.php ├── composer.lock └── CHANGES.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /elgg-services.php: -------------------------------------------------------------------------------- 1 | DI\get(WidgetsSettingsConfig::class), 7 | ]; 8 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint checks 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | name: Lint checks 8 | uses: ColdTrick/.github/.github/workflows/lint.yml@master 9 | -------------------------------------------------------------------------------- /views/default/forms/widgets/advanced.mjs: -------------------------------------------------------------------------------- 1 | import 'jquery'; 2 | 3 | $(document).on('open', '.elgg-tabs-component.widget-settings .elgg-tabs > li', function() { 4 | $(window).trigger('resize.lightbox'); 5 | }); 6 | -------------------------------------------------------------------------------- /.github/workflows/create-zip-release.yml: -------------------------------------------------------------------------------- 1 | name: Create ZIP Release 2 | 3 | on: 4 | release: 5 | types: [created, edited] 6 | 7 | jobs: 8 | build: 9 | name: Build release 10 | uses: ColdTrick/.github/.github/workflows/create-zip-release.yml@master 11 | -------------------------------------------------------------------------------- /.github/workflows/phpunit.yml: -------------------------------------------------------------------------------- 1 | name: PHPUnit Plugin Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | phpunit: 7 | name: Run PHPUnit test suites 8 | uses: ColdTrick/.github/.github/workflows/phpunit.yml@master 9 | with: 10 | elgg_major_version: 6 11 | -------------------------------------------------------------------------------- /.github/workflows/tweet-release.yml: -------------------------------------------------------------------------------- 1 | name: Tweet about a new release 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | tweet: 9 | name: Send a Tweet 10 | uses: ColdTrick/.github/.github/workflows/tweet-release.yml@master 11 | secrets: inherit 12 | -------------------------------------------------------------------------------- /views/default/forms/widget_manager/manage_widgets/widget.css: -------------------------------------------------------------------------------- 1 | .widget-manager-manage-widgets-non-default, 2 | .widget-manager-manage-widgets-non-default label { 3 | color: orange; 4 | } 5 | 6 | .widget-manager-manage-widgets-toggle-multiple { 7 | white-space: nowrap; 8 | margin-bottom: 0; 9 | } 10 | -------------------------------------------------------------------------------- /views/default/search/object/widget_page.php: -------------------------------------------------------------------------------- 1 | $guid) { 9 | $widget = elgg_call(ELGG_IGNORE_ACCESS, function() use ($guid) { 10 | return get_entity($guid); 11 | }); 12 | 13 | if (!$widget instanceof \ElggWidget || !$widget->canEdit()) { 14 | continue; 15 | } 16 | 17 | $widget->column = 1; 18 | $widget->order = $index + 1; 19 | } 20 | 21 | return elgg_ok_response(); 22 | -------------------------------------------------------------------------------- /views/default/widget_manager/widgets/custom_more.php: -------------------------------------------------------------------------------- 1 | widget_manager_custom_more_title; 9 | $custom_more_url = $widget->widget_manager_custom_more_url; 10 | 11 | if (empty($custom_more_title) || empty($custom_more_url)) { 12 | return; 13 | } 14 | 15 | echo elgg_view('page/components/list/widget_more', ['widget_more' => elgg_view_url($custom_more_url, $custom_more_title)]); 16 | -------------------------------------------------------------------------------- /views/default/admin/widgets/pages.php: -------------------------------------------------------------------------------- 1 | 'add_widget_page', 5 | 'text' => elgg_echo('add'), 6 | 'href' => 'ajax/form/widget_manager/widget_page', 7 | 'class' => 'elgg-lightbox elgg-button elgg-button-action', 8 | 'icon' => 'plus', 9 | ]); 10 | 11 | echo elgg_list_entities([ 12 | 'type' => 'object', 13 | 'subtype' => 'widget_page', 14 | 'limit' => false, 15 | 'no_results' => true, 16 | 'sort_by' => [ 17 | 'property' => 'url', 18 | 'direction' => 'asc', 19 | ], 20 | ]); 21 | -------------------------------------------------------------------------------- /actions/widget_manager/force_tool_widgets.php: -------------------------------------------------------------------------------- 1 | 'group', 12 | 'limit' => false, 13 | 'batch' => true, 14 | ]); 15 | 16 | foreach ($groups as $group) { 17 | $group->save(); 18 | $counter++; 19 | } 20 | 21 | return elgg_ok_response('', elgg_echo('widget_manager:action:force_tool_widgets:success', [$counter])); 22 | -------------------------------------------------------------------------------- /actions/widget_manager/cleanup.php: -------------------------------------------------------------------------------- 1 | 'object', 12 | 'subtype' => 'widget', 13 | 'metadata_name_value_pairs' => [ 14 | 'handler' => $handler, 15 | 'context' => $context, 16 | ], 17 | 'limit' => false, 18 | 'batch' => true, 19 | 'batch_inc_offset' => false, 20 | ]); 21 | 22 | foreach ($entities as $entity) { 23 | $entity->delete(); 24 | } 25 | 26 | return elgg_ok_response('', elgg_echo('entity:delete:success', [elgg_echo('collection:object:widget')])); 27 | -------------------------------------------------------------------------------- /views/default/admin/widgets/manage.mjs: -------------------------------------------------------------------------------- 1 | import 'jquery'; 2 | import i18n from 'elgg/i18n'; 3 | import Ajax from 'elgg/Ajax'; 4 | 5 | $(document).on('click', '.widget-manager-unsupported-context .elgg-input-checkbox', function (e, elem) { 6 | if (!$(this).is(':checked')) { 7 | return; 8 | } 9 | 10 | if (!confirm(i18n.echo('widget_manager:forms:manage_widgets:unsupported_context:confirm'))) { 11 | return false; 12 | } 13 | }); 14 | 15 | $(document).on('change', '.elgg-input-checkbox[name^="widgets_config"]', function (e, elem) { 16 | var ajax = new Ajax(); 17 | 18 | ajax.action('widget_manager/manage_widgets', { 19 | data: { 20 | [$(this).attr('name')]: $(this).is(':checked') ? 1 : 0 21 | }, 22 | showSuccessMessages: false 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coldtrick/widget_manager", 3 | "description": "Manage your widgets", 4 | "homepage": "https://www.coldtrick.com", 5 | "type": "elgg-plugin", 6 | "keywords": ["elgg", "plugin", "widget"], 7 | "license": "GPL-2.0-only", 8 | "support": { 9 | "source": "https://github.com/ColdTrick/widget_manager", 10 | "issues": "https://github.com/ColdTrick/widget_manager/issues" 11 | }, 12 | "require": { 13 | "mobiledetect/mobiledetectlib": "~4.8.0", 14 | "npm-asset/muuri": "~0.9.5" 15 | }, 16 | "repositories": [{ 17 | "type": "composer", 18 | "url": "https://asset-packagist.org" 19 | } 20 | ], 21 | "config": { 22 | "fxp-asset": { 23 | "enabled": false 24 | } 25 | }, 26 | "conflict": { 27 | "elgg/elgg": "<6.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/Settings.php: -------------------------------------------------------------------------------- 1 | getParam('plugin_id') !== 'widget_manager') { 19 | return null; 20 | } 21 | 22 | if ($event->getParam('name') !== 'index_managers') { 23 | return null; 24 | } 25 | 26 | $current_value = $event->getValue(); 27 | if (!is_array($current_value)) { 28 | return null; 29 | } 30 | 31 | return implode(',', $current_value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Widget Manager 2 | ============== 3 | 4 | ![Elgg 6.0](https://img.shields.io/badge/Elgg-6.0-green.svg) 5 | ![Lint Checks](https://github.com/ColdTrick/widget_manager/actions/workflows/lint.yml/badge.svg?event=push) 6 | [![Latest Stable Version](https://poser.pugx.org/coldtrick/widget_manager/v/stable.svg)](https://packagist.org/packages/coldtrick/widget_manager) 7 | [![License](https://poser.pugx.org/coldtrick/widget_manager/license.svg)](https://packagist.org/packages/coldtrick/widget_manager) 8 | 9 | A plugin for Elgg that adds more widget related features. 10 | 11 | Features 12 | -------- 13 | 14 | - Index widgets (with a few different layouts) 15 | - Group widgets (instead of group profile modules) 16 | - Option to globally hide a specific widgettype (will even hide widget already placed on profile/dashboard) 17 | - Create group default widgets 18 | - Create extra pages with a widget layout 19 | -------------------------------------------------------------------------------- /classes/WidgetManagerWidget.php: -------------------------------------------------------------------------------- 1 | widget_manager_custom_title ?: parent::getDisplayName(); 15 | } 16 | 17 | /** 18 | * {@inheritdoc} 19 | */ 20 | public function invalidateCache(): void { 21 | if (!$this->guid) { 22 | return; 23 | } 24 | 25 | parent::invalidateCache(); 26 | 27 | if (!\ColdTrick\WidgetManager\Widgets::isCacheableWidget($this)) { 28 | return; 29 | } 30 | 31 | $languages = elgg()->translator->getAllowedLanguages(); 32 | foreach ($languages as $language) { 33 | elgg_delete_system_cache("widget_cache_{$this->guid}_{$language}"); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /actions/widget_manager/lazy_load_widgets.php: -------------------------------------------------------------------------------- 1 | $guids, 23 | 'type' => 'object', 24 | 'subtype' => 'widget', 25 | 'limit' => false, 26 | ]); 27 | 28 | $result = []; 29 | foreach ($entities as $entity) { 30 | $result[$entity->guid] = elgg_view('object/widget/body', ['entity' => $entity]); 31 | } 32 | 33 | elgg_pop_context(); 34 | elgg_set_context_stack($old_context_stack); 35 | 36 | return elgg_ok_response($result); 37 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/Menus/Entity.php: -------------------------------------------------------------------------------- 1 | getEntityParam(); 21 | if (!$entity instanceof \WidgetPage || !$entity->canEdit()) { 22 | return null; 23 | } 24 | 25 | $result = $event->getValue(); 26 | 27 | $result[] = \ElggMenuItem::factory([ 28 | 'name' => 'edit', 29 | 'text' => elgg_echo('edit'), 30 | 'icon' => 'edit', 31 | 'href' => "ajax/form/widget_manager/widget_page?guid={$entity->guid}", 32 | 'link_class' => 'elgg-lightbox', 33 | 'data-colorbox-opts' => json_encode([ 34 | 'trapFocus' => false, 35 | ]), 36 | ]); 37 | 38 | return $result; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /views/default/widget_manager/group_tool_widgets.php: -------------------------------------------------------------------------------- 1 | [], 'disable' => []]; 25 | $params = ['entity' => $page_owner]; 26 | 27 | $result = (array) elgg_trigger_event_results('group_tool_widgets', 'widget_manager', $params, $result); 28 | 29 | if (empty($result)) { 30 | return; 31 | } 32 | 33 | $disable_widget_handlers = (array) elgg_extract('disable', $result, []); 34 | foreach ($disable_widget_handlers as $handler) { 35 | elgg_unregister_widget_type($handler); 36 | } 37 | -------------------------------------------------------------------------------- /views/default/page/layouts/widgets/add_button.php: -------------------------------------------------------------------------------- 1 | elgg_get_context(), 8 | 'context_stack' => elgg_get_context_stack(), 9 | 'show_access' => elgg_extract('show_access', $vars, true), 10 | 'owner_guid' => elgg_extract('owner_guid', $vars, elgg_get_page_owner_guid()), 11 | 'new_widget_column' => elgg_extract('new_widget_column', $vars), 12 | 'new_widget_position' => elgg_extract('new_widget_position', $vars), 13 | ]); 14 | 15 | echo elgg_view_menu('title:widgets', [ 16 | 'items' => [ 17 | [ 18 | 'name' => 'widgets_add', 19 | 'href' => false, 20 | 'text' => elgg_echo('widgets:add'), 21 | 'icon' => 'cog', 22 | 'link_class' => 'elgg-lightbox elgg-more', 23 | 'data-colorbox-opts' => json_encode([ 24 | 'href' => $href, 25 | 'maxWidth' => '900px', 26 | 'maxHeight' => '90%', 27 | ]), 28 | ], 29 | ], 30 | 'class' => 'elgg-menu-hz', 31 | 'show_collapse_content' => elgg_extract('show_collapse_content', $vars, true), 32 | ]); 33 | -------------------------------------------------------------------------------- /views/default/widget_manager/widgets/settings.php: -------------------------------------------------------------------------------- 1 | canEdit()) { 14 | return; 15 | } 16 | 17 | elgg_push_context($widget->context); 18 | elgg_push_context('widgets'); 19 | elgg_set_page_owner_guid($widget->getOwnerGUID()); 20 | 21 | $additional_class = preg_replace('/[^a-z0-9-]/i', '-', "elgg-form-widgets-save-{$widget->handler}"); 22 | 23 | $body_vars = ['widget' => $widget, 'show_access' => $show_access]; 24 | $form_vars = ['class' => $additional_class]; 25 | 26 | $form = elgg_view_form('widgets/save', $form_vars, $body_vars); 27 | 28 | echo elgg_format_element('div', [ 29 | 'class' => 'widget-manager-lightbox-edit', 30 | 'id' => "widget-edit-{$widget->guid}", 31 | ], $form); 32 | 33 | elgg_pop_context(); // undo widgets 34 | elgg_pop_context(); // undo $widget->context 35 | -------------------------------------------------------------------------------- /actions/widget_manager/groups/update_widget_access.php: -------------------------------------------------------------------------------- 1 | canEdit()) { 19 | return elgg_error_response(elgg_echo('groups:cantedit')); 20 | } 21 | 22 | $widgets = elgg_get_entities([ 23 | 'type' => 'object', 24 | 'subtype' => 'widget', 25 | 'owner_guid' => $group->guid, 26 | 'metadata_name' => 'context', 27 | 'metadata_value' => 'groups', 28 | 'limit' => false, 29 | ]); 30 | 31 | foreach ($widgets as $widget) { 32 | $widget->access_id = (int) $new_access; 33 | $widget->save(); 34 | } 35 | 36 | return elgg_ok_response('', elgg_echo('widget_manager:action:groups:update_widget_access:success'), $group->getURL()); 37 | -------------------------------------------------------------------------------- /views/default/widget_manager/lazy_loading.mjs: -------------------------------------------------------------------------------- 1 | import 'jquery'; 2 | import 'elgg'; 3 | import 'elgg/widgets'; 4 | import Ajax from 'elgg/Ajax'; 5 | 6 | if ($('.elgg-module-widget.lazy-loading').length) { 7 | $('.elgg-layout-widgets').each(function(i, layout) { 8 | var $lazy_widgets = $(layout).find('.elgg-module-widget.lazy-loading'); 9 | if (!$lazy_widgets.length) { 10 | return; 11 | } 12 | 13 | var guids = []; 14 | 15 | $lazy_widgets.each(function(index, item) { 16 | var guidString = $(item).attr('id'); 17 | guids.push(guidString.substr(guidString.indexOf('elgg-widget-') + 'elgg-widget-'.length)); 18 | }); 19 | 20 | var ajax = new Ajax(false); 21 | ajax.action('widget_manager/lazy_load_widgets', { 22 | data: { 23 | guids: guids, 24 | page_owner_guid: $(layout).data().pageOwnerGuid, 25 | context_stack: $(layout).data().contextStack, 26 | }, 27 | success: function (result) { 28 | $.each(result, function(guid, body) { 29 | $('#elgg-widget-' + guid + ' > .elgg-body').html(body); 30 | }); 31 | 32 | $(layout).trigger({ 33 | type: 'lazyLoaded', 34 | layout: $(layout) 35 | }); 36 | } 37 | }); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /actions/widget_manager/widget_page.php: -------------------------------------------------------------------------------- 1 | canEdit()) { 11 | return elgg_error_response(elgg_echo('actionunauthorized')); 12 | } 13 | } 14 | 15 | $url = get_input('url'); 16 | if (empty($url) && !($entity instanceof \WidgetPage && !elgg_is_admin_logged_in())) { 17 | return elgg_error_response(elgg_echo('error:missing_data')); 18 | } 19 | 20 | if (!$entity instanceof \WidgetPage) { 21 | if (!elgg_is_admin_logged_in()) { 22 | return elgg_error_response(elgg_echo('actionunauthorized')); 23 | } 24 | 25 | $entity = new \WidgetPage(); 26 | } 27 | 28 | if (elgg_is_admin_logged_in()) { 29 | $entity->url = $url; 30 | } 31 | 32 | $entity->title = get_input('title'); 33 | $entity->description = get_input('description'); 34 | $entity->show_description = (bool) get_input('show_description'); 35 | $entity->layout = get_input('layout'); 36 | 37 | $entity->save(); 38 | 39 | $entity->setManagers((array) get_input('manager', [])); 40 | 41 | elgg_delete_system_cache('widget_pages'); 42 | 43 | return elgg_ok_response('', elgg_echo('save:success')); 44 | -------------------------------------------------------------------------------- /views/default/resources/widget_manager/custom_index.php: -------------------------------------------------------------------------------- 1 | guid); // site owns the index widgets 6 | 7 | $num_columns = 3; 8 | 9 | $layout = elgg_get_plugin_setting('widget_layout', 'widget_manager'); 10 | if (!empty($layout)) { 11 | $num_columns = count(explode('|', $layout)); 12 | } 13 | 14 | $classes = []; 15 | $column_classes = []; 16 | 17 | switch ($layout) { 18 | case 'fluid': 19 | $classes[] = 'widgets-1-columns'; 20 | $classes[] = 'widgets-fluid-columns'; 21 | $num_columns = 1; 22 | 23 | elgg_import_esm('widget_manager/fluid'); 24 | break; 25 | case '33|33|33': 26 | $classes[] = 'widgets-3-columns'; 27 | break; 28 | case '50|50': 29 | $classes[] = 'widgets-2-columns'; 30 | break; 31 | default: 32 | $classes[] = "widgets-{$num_columns}-columns"; 33 | 34 | $columns = array_reverse(explode('|', $layout)); 35 | foreach ($columns as $column_index => $column_width) { 36 | $column_classes[$column_index + 1] = "col-width-{$column_width}"; 37 | } 38 | break; 39 | } 40 | 41 | // draw the page 42 | $content = elgg_view_layout('widgets', [ 43 | 'class' => $classes, 44 | 'num_columns' => $num_columns, 45 | 'column_classes' => $column_classes, 46 | 'exact_match' => true, 47 | ]); 48 | 49 | echo elgg_view_page('', ['content' => $content]); 50 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/Menus/Title.php: -------------------------------------------------------------------------------- 1 | getParam('show_collapse_content', false)) { 26 | return null; 27 | } 28 | 29 | $result = $event->getValue(); 30 | 31 | $result[] = \ElggMenuItem::factory([ 32 | 'name' => 'hide-widget-contents', 33 | 'class' => 'elgg-more', 34 | 'text' => elgg_echo('widget_manager:layout:content:hide'), 35 | 'icon' => 'eye-slash', 36 | 'href' => false, 37 | 'priority' => 80, 38 | ]); 39 | 40 | $result[] = \ElggMenuItem::factory([ 41 | 'name' => 'show-widget-contents', 42 | 'class' => 'elgg-more', 43 | 'item_class' => 'hidden', 44 | 'text' => elgg_echo('widget_manager:layout:content:show'), 45 | 'icon' => 'eye', 46 | 'href' => false, 47 | 'priority' => 81, 48 | ]); 49 | 50 | return $result; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /views/default/admin/widgets/manage.php: -------------------------------------------------------------------------------- 1 | name . '_' . $widget_definition->id; 34 | $widget_output[$key] = elgg_view('forms/widget_manager/manage_widgets/widget', [ 35 | 'widget' => $widget_definition, 36 | 'contexts' => $contexts, 37 | ]); 38 | } 39 | 40 | ksort($widget_output); 41 | 42 | echo implode('', $widget_output); 43 | -------------------------------------------------------------------------------- /views/default/object/widget_page.php: -------------------------------------------------------------------------------- 1 | getDisplayName() . " [{$entity->url}]"; 12 | 13 | $vars['title'] = elgg_view_url($entity->getURL(), elgg_get_excerpt($title, 100)); 14 | 15 | echo elgg_view('object/elements/summary', $vars); 16 | return; 17 | } 18 | 19 | $num_columns = $entity->getNumColumns(); 20 | $layout = $entity->layout; 21 | 22 | $classes = []; 23 | $column_classes = []; 24 | 25 | switch ($layout) { 26 | case '33|33|33': 27 | $classes[] = 'widgets-3-columns'; 28 | break; 29 | case '50|50': 30 | $classes[] = 'widgets-2-columns'; 31 | break; 32 | default: 33 | $classes[] = "widgets-{$num_columns}-columns"; 34 | 35 | $columns = array_reverse(explode('|', $layout)); 36 | foreach ($columns as $column_index => $column_width) { 37 | $column_classes[$column_index + 1] = "col-width-{$column_width}"; 38 | } 39 | break; 40 | } 41 | 42 | if ($entity->show_description !== false && !empty($entity->description)) { 43 | echo elgg_view('output/longtext', ['value' => $entity->description, 'class' => 'widget-page-description']); 44 | } 45 | 46 | echo elgg_view_layout('widgets', [ 47 | 'class' => $classes, 48 | 'num_columns' => $num_columns, 49 | 'column_classes' => $column_classes, 50 | 'exact_match' => true, 51 | 'show_add_widgets' => false, 52 | ]); 53 | -------------------------------------------------------------------------------- /views/default/admin/widgets/cleanup.php: -------------------------------------------------------------------------------- 1 | elgg_echo('admin:widgets:cleanup:info')]); 6 | 7 | $select = Select::fromTable('entities', 'e'); 8 | $select->join('e', 'metadata', 'md_handler', 'md_handler.entity_guid = e.guid'); 9 | $select->join('e', 'metadata', 'md_context', 'md_context.entity_guid = e.guid'); 10 | $select->select('md_handler.value AS handler'); 11 | $select->addSelect('md_context.value AS context'); 12 | $select->addSelect('count(*) AS total'); 13 | $select->where('e.type = "object"'); 14 | $select->andWhere('e.subtype = "widget"'); 15 | $select->andWhere('md_handler.name = "handler"'); 16 | $select->andWhere('md_context.name = "context"'); 17 | $select->groupBy('md_handler.value'); 18 | $select->addGroupBy('md_context.value'); 19 | $select->orderBy('md_handler.value, md_context.value'); 20 | 21 | $res = elgg()->db->getData($select); 22 | 23 | $handler = ''; 24 | foreach ($res as $row) { 25 | if ($row->handler !== $handler) { 26 | $handler = $row->handler; 27 | $title = elgg_format_element('b', [], $handler); 28 | echo elgg_format_element('div', ['class' => 'mtm'], $title); 29 | } 30 | 31 | $line = "{$row->context} ({$row->total})"; 32 | $line .= elgg_view('output/url', [ 33 | 'text' => elgg_echo('delete'), 34 | 'href' => elgg_generate_action_url('widget_manager/cleanup', [ 35 | 'handler' => $handler, 36 | 'context' => $row->context, 37 | ]), 38 | 'confirm' => true, 39 | 'class' => 'mlm', 40 | ]); 41 | 42 | echo elgg_format_element('div', ['class' => 'pll'], $line); 43 | } 44 | -------------------------------------------------------------------------------- /views/default/widget_manager/forms/groups_widget_access.php: -------------------------------------------------------------------------------- 1 | canEdit()) { 8 | return; 9 | } 10 | 11 | $group_enable = elgg_get_plugin_setting('group_enable', 'widget_manager'); 12 | if (!in_array($group_enable, ['yes', 'forced'])) { 13 | return; 14 | } 15 | 16 | $widgets_count = elgg_count_entities([ 17 | 'type' => 'object', 18 | 'subtype' => 'widget', 19 | 'owner_guid' => $group->guid, 20 | 'metadata_name' => 'context', 21 | 'metadata_value' => 'groups', 22 | ]); 23 | 24 | if (!$widgets_count) { 25 | // no widgets = no need for these actions 26 | return; 27 | } 28 | 29 | $access_options = elgg_get_write_access_array(0, false, [ 30 | 'container_guid' => $group->guid, 31 | 'value' => $group->access_id, 32 | 'entity_type' => 'object', 33 | 'entity_subtype' => 'widget', 34 | 'purpose' => 'groups_widget_access', 35 | ]); 36 | 37 | if (empty($access_options)) { 38 | return; 39 | } 40 | 41 | $content = elgg_view('output/longtext', [ 42 | 'value' => elgg_echo('widget_manager:forms:groups_widget_access:description'), 43 | ]); 44 | 45 | foreach ($access_options as $access_id => $option) { 46 | $content .= elgg_view('output/url', [ 47 | 'href' => elgg_generate_action_url('widget_manager/groups/update_widget_access', [ 48 | 'widget_access_level' => $access_id, 49 | 'group_guid' => $group->guid, 50 | ]), 51 | 'text' => $option, 52 | 'class' => [ 53 | 'elgg-button', 54 | 'elgg-button-action', 55 | ], 56 | ]); 57 | } 58 | 59 | echo elgg_view_module('info', elgg_echo('widget_manager:forms:groups_widget_access:title'), $content); 60 | -------------------------------------------------------------------------------- /views/default/widget_manager/site.css: -------------------------------------------------------------------------------- 1 | .widget_manager_hide_header { 2 | > .elgg-head { 3 | display: none; 4 | } 5 | 6 | &.widget_manager_hide_header_admin:hover { 7 | > .elgg-head { 8 | display: flex; 9 | } 10 | } 11 | } 12 | 13 | .elgg-module-widget { 14 | &.widget_manager_disable_widget_content_style { 15 | background: none; 16 | border: 0px; 17 | 18 | .elgg-widget-content { 19 | padding: 0px; 20 | } 21 | 22 | &:hover { 23 | box-shadow: none; 24 | } 25 | } 26 | } 27 | 28 | .elgg-widgets-grid { 29 | flex-wrap: wrap; 30 | } 31 | 32 | .elgg-widgets-hide-content .elgg-module-widget { 33 | >.elgg-head .elgg-widget-title { 34 | display: block; 35 | } 36 | 37 | > .elgg-body { 38 | visibility: hidden; 39 | height: 0px; 40 | } 41 | } 42 | 43 | .widgets-fluid-columns { 44 | .elgg-widgets-grid { 45 | display: block; 46 | visibility: hidden; 47 | } 48 | 49 | .elgg-widgets { 50 | padding: 0; 51 | position: relative; 52 | } 53 | 54 | .elgg-module-widget { 55 | position: absolute; 56 | margin: 14px; 57 | 58 | &.muuri-item-dragging { 59 | z-index: 3; 60 | } 61 | &.muuri-item-releasing { 62 | z-index: 2; 63 | } 64 | &.muuri-item-hidden { 65 | z-index: 0; 66 | } 67 | } 68 | 69 | .fluid-placeholder { 70 | border: 3px dashed $(border-color-mild); 71 | } 72 | } 73 | 74 | @media $(media-desktop-up) { 75 | .elgg-widgets { 76 | flex-basis: 0; 77 | 78 | &.col-width-25 { 79 | flex-grow: 25; 80 | } 81 | &.col-width-33 { 82 | flex-grow: 33; 83 | } 84 | &.col-width-40 { 85 | flex-grow: 40; 86 | } 87 | &.col-width-50 { 88 | flex-grow: 50; 89 | } 90 | &.col-width-60 { 91 | flex-grow: 60; 92 | } 93 | &.col-width-75 { 94 | flex-grow: 75; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /views/default/resources/widget_manager/widget_page.php: -------------------------------------------------------------------------------- 1 | 'object', 9 | 'subtype' => \WidgetPage::SUBTYPE, 10 | 'metadata_name_value_pairs' => ['url' => $handler], 11 | 'limit' => 1, 12 | ]); 13 | 14 | if (empty($pages)) { 15 | throw new EntityNotFoundException(); 16 | } 17 | 18 | $widget_page = $pages[0]; 19 | 20 | elgg_push_context('index'); 21 | elgg_set_page_owner_guid($widget_page->guid); 22 | 23 | if ($widget_page->canEdit()) { 24 | $href = elgg_generate_url('widgets:add_panel', [ 25 | 'context' => elgg_get_context(), 26 | 'context_stack' => elgg_get_context_stack(), 27 | 'show_access' => true, 28 | 'owner_guid' => elgg_get_page_owner_guid(), 29 | ]); 30 | 31 | elgg_register_menu_item('title', [ 32 | 'name' => 'widgets_add', 33 | 'href' => false, 34 | 'icon' => 'plus', 35 | 'text' => elgg_echo('widgets:add'), 36 | 'link_class' => ['elgg-lightbox', 'elgg-button', 'elgg-button-action'], 37 | 'data-colorbox-opts' => json_encode([ 38 | 'href' => $href, 39 | 'maxWidth' => '900px', 40 | 'maxHeight' => '90%', 41 | ]), 42 | ]); 43 | 44 | elgg_register_menu_item('title', [ 45 | 'name' => 'hide-widget-contents', 46 | 'link_class' => ['elgg-button', 'elgg-button-action'], 47 | 'text' => elgg_echo('widget_manager:layout:content:hide'), 48 | 'icon' => 'eye-slash', 49 | 'href' => false, 50 | 'priority' => 80, 51 | ]); 52 | 53 | elgg_register_menu_item('title', [ 54 | 'name' => 'show-widget-contents', 55 | 'link_class' => ['elgg-button', 'elgg-button-action'], 56 | 'item_class' => 'hidden', 57 | 'text' => elgg_echo('widget_manager:layout:content:show'), 58 | 'icon' => 'eye', 59 | 'href' => false, 60 | 'priority' => 81, 61 | ]); 62 | } 63 | 64 | echo elgg_view_page($widget_page->getDisplayName(), [ 65 | 'title' => $widget_page->title ?: false, 66 | 'content' => elgg_view_entity($widget_page), 67 | 'entity' => $widget_page, 68 | ]); 69 | -------------------------------------------------------------------------------- /views/default/forms/widgets/save.php: -------------------------------------------------------------------------------- 1 | handler}/edit")) { 17 | $custom_form_section = elgg_view("widgets/{$widget->handler}/edit", ['entity' => $widget]); 18 | } 19 | 20 | $access = ''; 21 | if (elgg_extract('show_access', $vars, true)) { 22 | $access = elgg_view_field([ 23 | '#type' => 'access', 24 | '#label' => elgg_echo('access'), 25 | 'name' => 'params[access_id]', 26 | 'value' => $widget->access_id, 27 | 'entity' => $widget, 28 | ]); 29 | } 30 | 31 | $basic_content = $custom_form_section . $access; 32 | 33 | $advanced_content = elgg_view('forms/widgets/advanced', [ 34 | 'entity' => $widget, 35 | 'widget_context' => $widget->context, 36 | ]); 37 | 38 | if (empty($advanced_content)) { 39 | echo $basic_content; 40 | } elseif (empty($basic_content)) { 41 | echo $advanced_content; 42 | } else { 43 | echo elgg_view('page/components/tabs', [ 44 | 'class' => 'widget-settings', 45 | 'tabs' => [ 46 | [ 47 | 'text' => elgg_echo('settings'), 48 | 'content' => $basic_content, 49 | 'selected' => true, 50 | ], 51 | [ 52 | 'text' => elgg_echo('widget_manager:widgets:edit:advanced'), 53 | 'content' => $advanced_content, 54 | ], 55 | ], 56 | ]); 57 | 58 | elgg_import_esm('forms/widgets/advanced'); 59 | } 60 | 61 | echo elgg_view_field([ 62 | '#type' => 'hidden', 63 | 'name' => 'guid', 64 | 'value' => $widget->guid, 65 | ]); 66 | 67 | if (elgg_in_context('default_widgets')) { 68 | echo elgg_view_field([ 69 | '#type' => 'hidden', 70 | 'name' => 'default_widgets', 71 | 'value' => 1, 72 | ]); 73 | } 74 | 75 | $footer = elgg_view_field([ 76 | '#type' => 'submit', 77 | 'text' => elgg_echo('save'), 78 | ]); 79 | 80 | elgg_set_form_footer($footer); 81 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/Menus/AdminHeader.php: -------------------------------------------------------------------------------- 1 | getValue(); 25 | foreach ($return_value as $menu_item) { 26 | if ($menu_item->getName() == 'default_widgets') { 27 | // move defaultwidgets menu item 28 | $menu_item->setParentName('widgets'); 29 | } 30 | } 31 | 32 | $return_value[] = \ElggMenuItem::factory([ 33 | 'name' => 'widgets', 34 | 'text' => elgg_echo('admin:widgets'), 35 | 'href' => false, 36 | 'parent_name' => 'configure', 37 | ]); 38 | 39 | $return_value[] = \ElggMenuItem::factory([ 40 | 'name' => 'widgets:manage', 41 | 'href' => 'admin/widgets/manage', 42 | 'text' => elgg_echo('admin:widgets:manage'), 43 | 'parent_name' => 'widgets', 44 | ]); 45 | $return_value[] = \ElggMenuItem::factory([ 46 | 'name' => 'widgets:cleanup', 47 | 'href' => 'admin/widgets/cleanup', 48 | 'text' => elgg_echo('admin:widgets:cleanup'), 49 | 'parent_name' => 'widgets', 50 | ]); 51 | $return_value[] = \ElggMenuItem::factory([ 52 | 'name' => 'widgets:pages', 53 | 'href' => 'admin/widgets/pages', 54 | 'text' => elgg_echo('admin:widgets:pages'), 55 | 'parent_name' => 'widgets', 56 | ]); 57 | 58 | if (elgg_get_plugin_setting('custom_index', 'widget_manager') == '1|0') { 59 | // a special link to manage homepages that are only available if logged out 60 | $return_value[] = \ElggMenuItem::factory([ 61 | 'name' => 'admin:widgets:manage:index', 62 | 'href' => elgg_get_site_url() . '?override=true', 63 | 'text' => elgg_echo('admin:widgets:manage:index'), 64 | 'parent_name' => 'widgets', 65 | ]); 66 | } 67 | 68 | return $return_value; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/Bootstrap.php: -------------------------------------------------------------------------------- 1 | registerIndexRoute(); 17 | $this->registerWidgetPagesRoutes(); 18 | } 19 | 20 | /** 21 | * Conditionally registers a route for the index page 22 | * 23 | * @return void 24 | */ 25 | protected function registerIndexRoute(): void { 26 | // @todo check -> need later priority to win over walledgarden 27 | $setting = elgg_get_plugin_setting('custom_index', 'widget_manager'); 28 | if (empty($setting)) { 29 | return; 30 | } 31 | 32 | list($non_loggedin, $loggedin) = explode('|', $setting); 33 | 34 | if ((!elgg_is_logged_in() && !empty($non_loggedin)) || (elgg_is_logged_in() && !empty($loggedin)) || (elgg_is_admin_logged_in() && (get_input('override') == true))) { 35 | elgg_register_route('index', [ 36 | 'path' => '/', 37 | 'resource' => 'widget_manager/custom_index', 38 | ]); 39 | } 40 | } 41 | 42 | /** 43 | * Registers routes for the extra contexts pages 44 | * 45 | * @return void 46 | */ 47 | protected function registerWidgetPagesRoutes(): void { 48 | $urls = $this->getWidgetPagesUrls(); 49 | 50 | foreach ($urls as $url) { 51 | elgg_register_route($url, [ 52 | 'path' => $url, 53 | 'resource' => 'widget_manager/widget_page', 54 | ]); 55 | } 56 | } 57 | 58 | /** 59 | * Returns array of widget page urls 60 | * 61 | * @return array 62 | */ 63 | protected function getWidgetPagesUrls(): array { 64 | $urls = elgg_load_system_cache('widget_pages'); 65 | if ($urls) { 66 | return $urls; 67 | } 68 | 69 | $urls = []; 70 | 71 | $metadata = elgg_get_metadata([ 72 | 'type' => 'object', 73 | 'subtype' => \WidgetPage::SUBTYPE, 74 | 'limit' => false, 75 | 'batch' => true, 76 | 'metadata_name' => 'url', 77 | ]); 78 | 79 | foreach ($metadata as $md) { 80 | $urls[] = $md->value; 81 | } 82 | 83 | elgg_save_system_cache('widget_pages', $urls); 84 | 85 | return $urls; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /actions/widget_manager/manage_widgets.php: -------------------------------------------------------------------------------- 1 | $widget_config) { 32 | $configured_widget = elgg_extract($widget_id, $configured_widgets); 33 | if (empty($configured_widget)) { 34 | unset($setting[$widget_id]); 35 | continue; 36 | } 37 | 38 | // only store if different 39 | if (isset($widget_config['multiple'])) { 40 | if ((bool) $widget_config['multiple'] == (bool) $configured_widget->originals['multiple']) { 41 | unset($setting[$widget_id]['multiple']); 42 | } else { 43 | $setting[$widget_id]['multiple'] = 1; 44 | } 45 | } 46 | 47 | if (isset($widget_config['contexts'])) { 48 | foreach ($widget_config['contexts'] as $context => $context_config) { 49 | foreach ($context_config as $option => $value) { 50 | $setting[$widget_id]['contexts'][$context][$option] = $value; 51 | } 52 | } 53 | } 54 | } 55 | 56 | $plugin = elgg_get_plugin_from_id('widget_manager'); 57 | $plugin->setSetting('widgets_config', json_encode($setting)); 58 | 59 | return elgg_ok_response('', elgg_echo('widget_manager:action:manage:success')); 60 | -------------------------------------------------------------------------------- /views/default/resources/widgets/add_panel.mjs: -------------------------------------------------------------------------------- 1 | import 'jquery'; 2 | import elgg from 'elgg'; 3 | import Ajax from 'elgg/Ajax'; 4 | 5 | /** 6 | * Adds a new widget 7 | * 8 | * Makes Ajax call to add new widget and inserts the widget html 9 | * 10 | * @param {Object} event 11 | * @return void 12 | */ 13 | function addWidget(event) { 14 | event.preventDefault(); 15 | 16 | var $item = $(this).closest('li'); 17 | 18 | // if multiple instances not allow, disable this widget type add button 19 | if (!$item.is('.elgg-widget-multiple')) { 20 | $item.toggleClass('elgg-state-unavailable elgg-state-available'); 21 | } 22 | 23 | var href = $(this).attr('href'); 24 | var ajax = new Ajax(); 25 | 26 | ajax.path(href).done(function(output) { 27 | var query_parts = elgg.parse_url(href, 'query', true); 28 | var selector = ''; 29 | var context = query_parts['context']; 30 | var page_owner_guid = query_parts['page_owner_guid']; 31 | var new_widget_column = query_parts['new_widget_column'] || 1; 32 | var new_widget_position = query_parts['new_widget_position'] || 'top'; 33 | 34 | if (context && page_owner_guid) { 35 | // target the correct widget layout 36 | selector = '.elgg-layout-widgets-' + context + '[data-page-owner-guid="' + page_owner_guid + '"] .elgg-widget-col-' + new_widget_column; 37 | } else { 38 | selector = '.elgg-widget-col-' + new_widget_column; 39 | } 40 | 41 | if (new_widget_position === 'top') { 42 | $(selector).prepend(output); 43 | } else { 44 | $(selector).append(output); 45 | } 46 | 47 | var $layout = $(selector).closest('.elgg-layout-widgets'); 48 | $layout.trigger({ 49 | type: 'widgetAdd', 50 | layout: $layout 51 | }); 52 | }); 53 | } 54 | 55 | $(document).on('click', '.elgg-widgets-add-panel .elgg-widgets-add-actions .elgg-button-submit', addWidget); 56 | 57 | $(document).on('keyup', '.elgg-widgets-add-panel input[name="widget_search"]', function() { 58 | var $container = $('.elgg-widgets-add-panel'); 59 | var $items = $container.find('> .elgg-body > ul > li'); 60 | var q = $(this).val(); 61 | 62 | if (q === '') { 63 | $items.show(); 64 | } else { 65 | $items.hide(); 66 | $items.filter(function () { 67 | return $(this).text().toUpperCase().indexOf(q.toUpperCase()) >= 0; 68 | }).show(); 69 | } 70 | }); 71 | -------------------------------------------------------------------------------- /views/default/object/widget.php: -------------------------------------------------------------------------------- 1 | getSetting($widget->handler, 'hide', (string) $widget->context)) { 24 | return true; 25 | } 26 | 27 | $widget_instance = preg_replace('/[^a-z0-9-]/i', '-', "elgg-widget-instance-{$widget->handler}"); 28 | $widget_class = elgg_extract_class($vars, $widget_instance); 29 | 30 | $can_edit = $widget->canEdit(); 31 | if ($can_edit) { 32 | $widget_class[] = 'elgg-state-draggable'; 33 | 34 | if (!elgg_view_exists("widgets/{$widget->handler}/edit") && elgg_extract('show_access', $vars) === false) { 35 | // store for determining the edit menu item 36 | $vars['show_edit'] = false; 37 | } 38 | } 39 | 40 | if ($widget->widget_manager_custom_class) { 41 | $widget_class[] = $widget->widget_manager_custom_class; // optional custom class for this widget 42 | } 43 | 44 | if ($widget->widget_manager_hide_header == 'yes') { 45 | $widget_class[] = 'widget_manager_hide_header'; 46 | if ($can_edit) { 47 | $widget_class[] = 'widget_manager_hide_header_admin'; 48 | } 49 | } 50 | 51 | if ($widget->widget_manager_disable_widget_content_style == 'yes') { 52 | $widget_class[] = 'widget_manager_disable_widget_content_style'; 53 | } 54 | 55 | if ($widget instanceof WidgetManagerWidget && WidgetsSettingsConfig::instance()->showLazyLoaded($widget, (array) elgg_extract('layout_info', $vars, []))) { 56 | elgg_import_esm('widget_manager/lazy_loading'); 57 | $body = elgg_view('graphics/ajax_loader', ['hidden' => false]); 58 | $widget_class[] = 'lazy-loading'; 59 | } else { 60 | $body = elgg_view('object/widget/body', $vars); 61 | } 62 | 63 | echo elgg_view_module('widget', '', $body, [ 64 | 'class' => $widget_class, 65 | 'id' => "elgg-widget-{$widget->guid}", 66 | 'header' => elgg_view('object/widget/header', $vars), 67 | ]); 68 | -------------------------------------------------------------------------------- /views/default/forms/widgets/advanced.php: -------------------------------------------------------------------------------- 1 | 'text', 11 | '#label' => elgg_echo('widget_manager:widgets:edit:custom_title'), 12 | 'name' => 'params[widget_manager_custom_title]', 13 | 'value' => $widget->widget_manager_custom_title, 14 | ], 15 | [ 16 | '#type' => 'text', 17 | '#label' => elgg_echo('widget_manager:widgets:edit:custom_url'), 18 | 'name' => 'params[widget_manager_custom_url]', 19 | 'value' => $widget->widget_manager_custom_url, 20 | ], 21 | [ 22 | '#type' => 'text', 23 | '#label' => elgg_echo('widget_manager:widgets:edit:custom_more_title'), 24 | 'name' => 'params[widget_manager_custom_more_title]', 25 | 'value' => $widget->widget_manager_custom_more_title, 26 | ], 27 | [ 28 | '#type' => 'text', 29 | '#label' => elgg_echo('widget_manager:widgets:edit:custom_more_url'), 30 | 'name' => 'params[widget_manager_custom_more_url]', 31 | 'value' => $widget->widget_manager_custom_more_url, 32 | ], 33 | ]; 34 | 35 | $advanced_context = elgg_trigger_event_results('advanced_context', 'widget_manager', ['entity' => $widget], ['index']); 36 | 37 | if (is_array($advanced_context) && in_array($widget_context, $advanced_context)) { 38 | $fields[] = [ 39 | '#type' => 'checkbox', 40 | '#label' => elgg_echo('widget_manager:widgets:edit:hide_header'), 41 | 'name' => 'params[widget_manager_hide_header]', 42 | 'checked' => $widget->widget_manager_hide_header === 'yes', 43 | 'switch' => true, 44 | 'default' => 'no', 45 | 'value' => 'yes', 46 | ]; 47 | $fields[] = [ 48 | '#type' => 'checkbox', 49 | '#label' => elgg_echo('widget_manager:widgets:edit:disable_widget_content_style'), 50 | 'name' => 'params[widget_manager_disable_widget_content_style]', 51 | 'checked' => $widget->widget_manager_disable_widget_content_style === 'yes', 52 | 'switch' => true, 53 | 'default' => 'no', 54 | 'value' => 'yes', 55 | ]; 56 | $fields[] = [ 57 | '#type' => 'text', 58 | '#label' => elgg_echo('widget_manager:widgets:edit:custom_class'), 59 | 'name' => 'params[widget_manager_custom_class]', 60 | 'value' => $widget->widget_manager_custom_class, 61 | ]; 62 | 63 | if ((bool) elgg_get_plugin_setting('lazy_loading_enabled', 'widget_manager') && !WidgetsSettingsConfig::instance()->getSetting($widget->handler, 'always_lazy_load', (string) $widget->context)) { 64 | $fields[] = [ 65 | '#type' => 'switch', 66 | '#label' => elgg_echo('widget_manager:widgets:edit:lazy_load_content'), 67 | 'name' => 'params[widget_manager_lazy_load_content]', 68 | 'value' => $widget->widget_manager_lazy_load_content, 69 | ]; 70 | } 71 | } 72 | 73 | echo elgg_view_field([ 74 | '#type' => 'fieldset', 75 | 'fields' => $fields, 76 | ]); 77 | -------------------------------------------------------------------------------- /views/default/forms/widget_manager/widget_page.php: -------------------------------------------------------------------------------- 1 | canEdit()) { 5 | throw new \Elgg\Exceptions\Http\EntityPermissionsException(); 6 | } 7 | 8 | echo elgg_format_element('div', ['class' => 'elgg-subtext mbm'], elgg_echo('widget_manager:settings:extra_contexts:description')); 9 | 10 | echo elgg_view_field([ 11 | '#type' => 'hidden', 12 | 'name' => 'guid', 13 | 'value' => $entity?->guid, 14 | ]); 15 | 16 | if (elgg_is_admin_logged_in()) { 17 | echo elgg_view_field([ 18 | '#type' => 'text', 19 | '#label' => elgg_echo('widget_manager:settings:extra_contexts:page'), 20 | 'name' => 'url', 21 | 'value' => $entity?->url, 22 | 'required' => true, 23 | ]); 24 | } 25 | 26 | echo elgg_view_field([ 27 | '#type' => 'text', 28 | '#label' => elgg_echo('title'), 29 | '#help' => elgg_echo('widget_manager:widget_page:title:help'), 30 | 'name' => 'title', 31 | 'value' => $entity?->title, 32 | ]); 33 | 34 | echo elgg_view_field([ 35 | '#type' => 'longtext', 36 | '#label' => elgg_echo('description'), 37 | 'name' => 'description', 38 | 'value' => $entity?->description, 39 | ]); 40 | 41 | echo elgg_view_field([ 42 | '#type' => 'checkbox', 43 | '#label' => elgg_echo('widget_manager:widget_page:show_description'), 44 | '#help' => elgg_echo('widget_manager:widget_page:show_description:help'), 45 | 'name' => 'show_description', 46 | 'checked' => $entity?->show_description !== false, 47 | 'switch' => true, 48 | 'default' => false, 49 | 'value' => true, 50 | ]); 51 | 52 | echo elgg_view_field([ 53 | '#type' => 'select', 54 | '#label' => elgg_echo('widget_manager:settings:extra_contexts:layout'), 55 | '#help' => elgg_echo('widget_manager:settings:extra_contexts:layout:help'), 56 | 'name' => 'layout', 57 | 'options_values' => [ 58 | '33|33|33' => elgg_echo('widget_manager:settings:widget_layout:33|33|33'), 59 | '50|25|25' => elgg_echo('widget_manager:settings:widget_layout:50|25|25'), 60 | '25|50|25' => elgg_echo('widget_manager:settings:widget_layout:25|50|25'), 61 | '25|25|50' => elgg_echo('widget_manager:settings:widget_layout:25|25|50'), 62 | '75|25' => elgg_echo('widget_manager:settings:widget_layout:75|25'), 63 | '60|40' => elgg_echo('widget_manager:settings:widget_layout:60|40'), 64 | '50|50' => elgg_echo('widget_manager:settings:widget_layout:50|50'), 65 | '40|60' => elgg_echo('widget_manager:settings:widget_layout:40|60'), 66 | '25|75' => elgg_echo('widget_manager:settings:widget_layout:25|75'), 67 | '100' => elgg_echo('widget_manager:settings:widget_layout:100'), 68 | ], 69 | 'value' => $entity?->layout, 70 | ]); 71 | 72 | echo elgg_view_field([ 73 | '#type' => 'userpicker', 74 | '#label' => elgg_echo('widget_manager:settings:extra_contexts:manager'), 75 | '#help' => elgg_echo('widget_manager:settings:extra_contexts:manager:help'), 76 | 'name' => 'manager', 77 | 'value' => $entity?->getManagers(), 78 | 'show_friends' => false, 79 | ]); 80 | 81 | $footer = elgg_view_field([ 82 | '#type' => 'submit', 83 | 'text' => $entity ? elgg_echo('update') : elgg_echo('save'), 84 | ]); 85 | 86 | elgg_set_form_footer($footer); 87 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/Seeder.php: -------------------------------------------------------------------------------- 1 | advance($this->getCount()); 31 | 32 | $site = elgg_get_site_entity(); 33 | 34 | while ($this->getCount() < $this->limit) { 35 | try { 36 | /* @var $entity \WidgetPage */ 37 | $entity = $this->createObject([ 38 | 'subtype' => \WidgetPage::SUBTYPE, 39 | 'owner_guid' => $site->guid, 40 | 'container_guid' => $site->guid, 41 | 'layout' => $this->getRandomLayout(), 42 | ]); 43 | } catch (MaxAttemptsException $e) { 44 | // unable to create with the given options 45 | continue; 46 | } 47 | 48 | unset($entity->description); 49 | 50 | $entity->url = 'widgets-' . elgg_get_friendly_title($entity->title); 51 | 52 | $user_guids = []; 53 | for ($i = 0; $i < $this->faker()->numberBetween(0, 5); $i++) { 54 | $user = $this->getRandomUser($user_guids); 55 | $user_guids[] = $user->guid; 56 | 57 | $entity->addManager($user); 58 | } 59 | 60 | $this->advance(); 61 | } 62 | 63 | // need to invalidate the cache of widget page urls 64 | elgg_delete_system_cache('widget_pages'); 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function unseed() { 71 | /* @var $entities \ElggBatch */ 72 | $entities = elgg_get_entities([ 73 | 'type' => 'object', 74 | 'subtype' => \WidgetPage::SUBTYPE, 75 | 'metadata_name' => '__faker', 76 | 'limit' => false, 77 | 'batch' => true, 78 | 'batch_inc_offset' => false, 79 | ]); 80 | 81 | /* @var $entity \WidgetPage */ 82 | foreach ($entities as $entity) { 83 | if ($entity->delete()) { 84 | $this->log("Deleted widget page {$entity->guid}"); 85 | } else { 86 | $this->log("Failed to delete widget page {$entity->guid}"); 87 | $entities->reportFailure(); 88 | continue; 89 | } 90 | 91 | $this->advance(); 92 | } 93 | 94 | // need to invalidate the cache of widget page urls 95 | elgg_delete_system_cache('widget_pages'); 96 | } 97 | 98 | /** 99 | * {@inheritdoc} 100 | */ 101 | public static function getType(): string { 102 | return \WidgetPage::SUBTYPE; 103 | } 104 | 105 | /** 106 | * {@inheritdoc} 107 | */ 108 | public static function getDefaultLimit(): int { 109 | return 5; 110 | } 111 | 112 | /** 113 | * {@inheritdoc} 114 | */ 115 | protected function getCountOptions(): array { 116 | return [ 117 | 'type' => 'object', 118 | 'subtype' => \WidgetPage::SUBTYPE, 119 | ]; 120 | } 121 | 122 | /** 123 | * Get a random layout to add to the widget page 124 | * 125 | * @return string 126 | */ 127 | protected function getRandomLayout(): string { 128 | $key = array_rand($this->layouts); 129 | 130 | return $this->layouts[$key]; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /views/default/widget_manager/fluid.mjs: -------------------------------------------------------------------------------- 1 | import 'jquery'; 2 | import 'elgg/widgets'; 3 | import 'muuri'; 4 | import Ajax from 'elgg/Ajax'; 5 | 6 | var grid; 7 | var grid_selector = '.widgets-fluid-columns .elgg-widget-col-1'; 8 | var grid_options = { 9 | items: '.elgg-module-widget', 10 | dragEnabled: false, 11 | dragPlaceholder: { 12 | enabled: true, 13 | }, 14 | itemPlaceholderClass: 'fluid-placeholder', 15 | layoutDuration: 0, 16 | showDuration: 0, 17 | layoutOnInit: false, 18 | dragHandle: '.elgg-widget-handle', 19 | dragStartPredicate: { 20 | distance: 10, 21 | delay: 10 22 | }, 23 | }; 24 | 25 | if ($('.widgets-fluid-columns.elgg-layout-can-edit').length) { 26 | grid_options.dragEnabled = true; 27 | } 28 | 29 | var ajax = new Ajax(); 30 | 31 | // Returns a function, that, as long as it continues to be invoked, will not 32 | // be triggered. The function will be called after it stops being called for 33 | // N milliseconds. If `immediate` is passed, trigger the function on the 34 | // leading edge, instead of the trailing. 35 | var debounce = function(func, wait, immediate) { 36 | var timeout; 37 | return function() { 38 | var context = this, args = arguments; 39 | var later = function() { 40 | timeout = null; 41 | if (!immediate) func.apply(context, args); 42 | }; 43 | var callNow = immediate && !timeout; 44 | clearTimeout(timeout); 45 | timeout = setTimeout(later, wait); 46 | if (callNow) func.apply(context, args); 47 | }; 48 | } 49 | 50 | function setItemSizes($elem) { 51 | var container_width = $elem.width(); 52 | 53 | var $widgets = $elem.find('.elgg-module-widget'); 54 | if (container_width > 1200) { 55 | $widgets.css('width', 'calc(33% - 28px)'); 56 | } else if (container_width > 800) { 57 | $widgets.css('width', 'calc(50% - 28px)'); 58 | } else { 59 | $widgets.css('width', 'calc(100% - 28px)'); 60 | } 61 | } 62 | 63 | function gridcheck() { 64 | if (grid) { 65 | setTimeout(function() { 66 | $(grid_selector).each(function() { 67 | setItemSizes($(this)); 68 | grid.refreshItems().layout(); 69 | }); 70 | }, 200); 71 | } 72 | } 73 | 74 | function initGrid() { 75 | setTimeout(function() { 76 | // added bit of delay to allow images to load (also required for correct working of delete event) 77 | if (grid) { 78 | grid.destroy(); 79 | } 80 | 81 | setItemSizes($(grid_selector)); 82 | 83 | grid = new Muuri(grid_selector, grid_options); 84 | 85 | grid.on('dragEnd', function (item, event) { 86 | if (event.distance === 0) { 87 | return; 88 | } 89 | 90 | // update dom with new positions 91 | grid.synchronize(); 92 | 93 | var guids = []; 94 | $.each(grid.getItems(), function(index, item) { 95 | var guidString = $(item._element).attr('id'); 96 | guidString = guidString.substr(guidString.indexOf('elgg-widget-') + 'elgg-widget-'.length); 97 | 98 | guids.push(guidString); 99 | }); 100 | 101 | ajax.action('widget_manager/fluid_order', { 102 | data: { 103 | guids: guids 104 | } 105 | }); 106 | }).on('layoutEnd', function() { 107 | $(grid_selector).css('visibility', 'visible'); 108 | }).layout(); 109 | }, 200); 110 | } 111 | 112 | initGrid(); 113 | 114 | $(window).resize(debounce(gridcheck, 50)); 115 | 116 | $(document).on('saveSettings', '.elgg-layout-widgets .elgg-module-widget', gridcheck); 117 | $(document).on('lazyLoaded', '.elgg-layout-widgets', gridcheck); 118 | 119 | $(document).on('widgetAdd widgetRemove', '.elgg-layout-widgets', initGrid); 120 | 121 | // mmenu support 122 | $(document).on('mmenu.toggle', gridcheck); 123 | -------------------------------------------------------------------------------- /classes/WidgetPage.php: -------------------------------------------------------------------------------- 1 | attributes['subtype'] = self::SUBTYPE; 24 | $this->attributes['access_id'] = ACCESS_PUBLIC; 25 | $this->attributes['owner_guid'] = elgg_get_site_entity()->guid; 26 | $this->attributes['container_guid'] = elgg_get_site_entity()->guid; 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function canEdit(int $user_guid = 0): bool { 33 | $user_guid = $user_guid ?: elgg_get_logged_in_user_guid(); 34 | if (in_array($user_guid, $this->getManagers())) { 35 | return true; 36 | } 37 | 38 | return parent::canEdit($user_guid); 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | public function getURL(): string { 45 | return elgg_normalize_url((string) $this->url); 46 | } 47 | 48 | /** 49 | * Adds a manager relationship for this page 50 | * 51 | * @param \ElggUser $user Manager 52 | * 53 | * @return bool 54 | */ 55 | public function addManager(\ElggUser $user): bool { 56 | return $user->addRelationship($this->guid, self::MANAGER_RELATIONSHIP); 57 | } 58 | 59 | /** 60 | * Removes a manager relationship for this page 61 | * 62 | * @param \ElggUser $user Manager 63 | * 64 | * @return bool 65 | */ 66 | public function removeManager(\ElggUser $user): bool { 67 | return $user->removeRelationship($this->guid, self::MANAGER_RELATIONSHIP); 68 | } 69 | 70 | /** 71 | * Replaces existing managers with a set of new managers 72 | * 73 | * @param array $guids new managers 74 | * 75 | * @return void 76 | */ 77 | public function setManagers(array $guids = []): void { 78 | $current = $this->getManagers(); 79 | foreach ($current as $guid) { 80 | if (!in_array($guid, $guids)) { 81 | $user = get_user((int) $guid); 82 | if ($user) { 83 | $this->removeManager($user); 84 | } 85 | } 86 | } 87 | 88 | $new_managers = array_diff($guids, $current); 89 | foreach ($new_managers as $guid) { 90 | $user = get_user((int) $guid); 91 | if ($user) { 92 | $this->addManager($user); 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * Returns an array of guids of the current managers 99 | * 100 | * @return array 101 | */ 102 | public function getManagers(): array { 103 | return (array) elgg_get_entities([ 104 | 'type' => 'user', 105 | 'relationship' => self::MANAGER_RELATIONSHIP, 106 | 'relationship_guid' => $this->guid, 107 | 'inverse_relationship' => true, 108 | 'callback' => function($row) { 109 | return $row->guid; 110 | }, 111 | ]); 112 | } 113 | 114 | /** 115 | * Returns number of columns based on layout config 116 | * 117 | * @return int 118 | */ 119 | public function getNumColumns(): int { 120 | return count(explode('|', $this->layout)); 121 | } 122 | 123 | /** 124 | * {@inheritdoc} 125 | */ 126 | public function getDisplayName(): string { 127 | if (empty($this->title)) { 128 | return ucwords(str_replace('_', ' ', $this->url)); 129 | } 130 | 131 | return parent::getDisplayName(); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /views/default/forms/widget_manager/manage_widgets/widget.php: -------------------------------------------------------------------------------- 1 | originals; 12 | 13 | $body = elgg_view('output/longtext', ['value' => $widget_definition->description]); 14 | 15 | $body .= ''; 16 | $body .= ''; 17 | $body .= ''; 18 | $body .= ''; 19 | $body .= ''; 20 | $body .= ''; 21 | $body .= ''; 22 | foreach ($contexts as $context) { 23 | $body .= ''; 70 | } 71 | 72 | $body .= '
' . elgg_echo('widget_manager:forms:manage_widgets:context') . '' . elgg_echo('widget_manager:forms:manage_widgets:can_add') . '' . elgg_echo('hide') . '' . elgg_echo('widget_manager:forms:manage_widgets:always_lazy_load') . '
'; 24 | 25 | $options = [ 26 | 'name' => "widgets_config[{$widget_definition->id}][contexts][{$context}][enabled]", 27 | 'label' => $context, 28 | 'value' => 1, 29 | 'switch' => true, 30 | 'checked' => in_array($context, $widget_definition->context), 31 | 'label_class' => [], 32 | ]; 33 | 34 | if (!in_array($context, $originals['context'])) { 35 | $options['label_class'][] = 'widget-manager-unsupported-context'; 36 | } 37 | 38 | if (in_array($context, $widget_definition->context)) { 39 | if (!in_array($context, $originals['context'])) { 40 | $options['label_class'][] = 'widget-manager-manage-widgets-non-default'; 41 | $options['title'] = elgg_echo('widget_manager:forms:manage_widgets:non_default'); 42 | } 43 | } elseif (in_array($context, $originals['context'])) { 44 | $options['label_class'][] = 'widget-manager-manage-widgets-non-default'; 45 | $options['title'] = elgg_echo('widget_manager:forms:manage_widgets:non_default'); 46 | } 47 | 48 | $body .= elgg_view('input/checkbox', $options); 49 | $body .= ''; 50 | $body .= elgg_view('input/checkbox', [ 51 | 'name' => "widgets_config[{$widget_definition->id}][contexts][{$context}][can_add]", 52 | 'value' => 1, 53 | 'checked' => WidgetsSettingsConfig::instance()->getSetting($widget_definition->id, 'can_add', $context), 54 | ]); 55 | $body .= ''; 56 | $body .= elgg_view('input/checkbox', [ 57 | 'name' => "widgets_config[{$widget_definition->id}][contexts][{$context}][hide]", 58 | 'value' => 1, 59 | 'checked' => WidgetsSettingsConfig::instance()->getSetting($widget_definition->id, 'hide', $context), 60 | ]); 61 | 62 | $body .= ''; 63 | $body .= elgg_view('input/checkbox', [ 64 | 'name' => "widgets_config[{$widget_definition->id}][contexts][{$context}][always_lazy_load]", 65 | 'value' => 1, 66 | 'checked' => WidgetsSettingsConfig::instance()->getSetting($widget_definition->id, 'always_lazy_load', $context), 67 | ]); 68 | 69 | $body .= '
'; 73 | 74 | // multiple 75 | $multiple_options = [ 76 | '#type' => 'checkbox', 77 | '#class' => 'widget-manager-manage-widgets-toggle-multiple', 78 | '#label' => elgg_echo('widget_manager:forms:manage_widgets:multiple'), 79 | 'name' => "widgets_config[{$widget_definition->id}][multiple]", 80 | 'value' => 1, 81 | 'switch' => true, 82 | 'checked' => $widget_definition->multiple, 83 | ]; 84 | 85 | if ($widget_definition->multiple !== $originals['multiple']) { 86 | $multiple_options['label_class'] = 'widget-manager-manage-widgets-non-default'; 87 | $multiple_options['title'] = elgg_echo('widget_manager:forms:manage_widgets:non_default'); 88 | } 89 | 90 | echo elgg_view_module('info', $widget_definition->name . ' [' . $widget_definition->id . ']', $body, [ 91 | 'menu' => elgg_view_field($multiple_options), 92 | ]); 93 | -------------------------------------------------------------------------------- /views/default/elgg/widgets.mjs: -------------------------------------------------------------------------------- 1 | import 'jquery'; 2 | import 'jquery-ui'; 3 | import Ajax from 'elgg/Ajax'; 4 | import lightbox from 'elgg/lightbox'; 5 | 6 | /** 7 | * Persist the widget's new position 8 | * 9 | * @param {Object} event 10 | * @param {Object} ui 11 | * 12 | * @return void 13 | */ 14 | function moveWidget(event, ui) { 15 | // elgg-widget- 16 | var guidString = ui.item.attr('id'); 17 | guidString = guidString.substring(guidString.indexOf('elgg-widget-') + "elgg-widget-".length); 18 | 19 | var ajax = new Ajax(false); 20 | ajax.action('widgets/move', { 21 | data: { 22 | widget_guid: guidString, 23 | column: ui.item.parent().data('widgetColumn'), 24 | position: ui.item.index() 25 | } 26 | }); 27 | }; 28 | 29 | /** 30 | * Removes a widget from the layout 31 | * 32 | * Event callback the uses Ajax to delete the widget and removes its HTML 33 | * 34 | * @param {Object} event 35 | * @return void 36 | */ 37 | function removeWidget(event) { 38 | event.preventDefault(); 39 | 40 | var $layout = $(this).closest('.elgg-layout-widgets'); 41 | var $widget = $(this).closest('.elgg-module-widget'); 42 | $widget.remove(); 43 | 44 | // delete the widget through ajax 45 | var ajax = new Ajax(false); 46 | ajax.action($(this).attr('href')); 47 | 48 | $layout.trigger({ 49 | type: 'widgetRemove', 50 | layout: $layout, 51 | widget: $widget 52 | }); 53 | }; 54 | 55 | /** 56 | * Save a widget's settings 57 | * 58 | * Uses Ajax to save the settings and updates the HTML. 59 | * 60 | * @param {Object} event 61 | * @return void 62 | */ 63 | function saveWidgetSettings(event) { 64 | event.preventDefault(); 65 | 66 | var guid = $(this).find('[name="guid"]').val(); 67 | var $widget = $('#elgg-widget-' + guid); 68 | var $widgetContent = $widget.find('.elgg-widget-content'); 69 | 70 | var ajax = new Ajax(); 71 | ajax.action('widgets/save', { 72 | data: ajax.objectify(this), 73 | success: function (result) { 74 | lightbox.close(); 75 | 76 | $widgetContent.html(result.content); 77 | if (result.title !== '') { 78 | var $widgetTitle = $widget.find('.elgg-widget-title'); 79 | 80 | var newWidgetTitle = result.title; 81 | if (result.href !== '') { 82 | newWidgetTitle = "" + newWidgetTitle + ""; 83 | } 84 | 85 | $widgetTitle.html(newWidgetTitle); 86 | } 87 | 88 | $widget.trigger({ 89 | type: 'saveSettings', 90 | widget: $widget 91 | }); 92 | } 93 | }); 94 | }; 95 | 96 | $('.elgg-layout-widgets:not(.widgets-fluid-columns)').find('.elgg-widgets').each(function() { 97 | 98 | var opts = $(this).data().sortableOptions; 99 | var defaults = { 100 | items: '.elgg-module-widget.elgg-state-draggable', 101 | connectWith: '.elgg-widgets', 102 | handle: '.elgg-widget-handle', 103 | forcePlaceholderSize: true, 104 | placeholder: 'elgg-widget-placeholder', 105 | opacity: 0.8, 106 | revert: 500, 107 | stop: moveWidget 108 | }; 109 | var settings = $.extend({}, defaults, opts); 110 | 111 | $(this).sortable(settings); 112 | }); 113 | 114 | // regular layouts 115 | $(document).on('click', '.elgg-menu-title-widgets .elgg-menu-item-hide-widget-contents a, .elgg-menu-title-widgets .elgg-menu-item-show-widget-contents a', function() { 116 | var $layout = $(this).closest('.elgg-layout-widgets'); 117 | $layout.find('.elgg-menu-item-hide-widget-contents, .elgg-menu-item-show-widget-contents').toggleClass('hidden'); 118 | 119 | $layout.toggleClass('elgg-widgets-hide-content'); 120 | 121 | return false; 122 | }); 123 | 124 | // widget page layouts with a title menu 125 | $(document).on('click', '.elgg-menu-title .elgg-menu-item-hide-widget-contents a, .elgg-menu-title .elgg-menu-item-show-widget-contents a', function() { 126 | $('.elgg-menu-title').find('.elgg-menu-item-hide-widget-contents, .elgg-menu-item-show-widget-contents').toggleClass('hidden'); 127 | 128 | $('.elgg-layout-widgets').toggleClass('elgg-widgets-hide-content'); 129 | 130 | return false; 131 | }); 132 | 133 | $(document).on('click', 'a.elgg-widget-delete-button', removeWidget); 134 | $(document).on('submit', '.elgg-form-widgets-save', saveWidgetSettings); 135 | -------------------------------------------------------------------------------- /views/default/resources/widgets/add_panel.php: -------------------------------------------------------------------------------- 1 | guid); 33 | 34 | $widgets = elgg_get_widgets($owner->guid, $context); 35 | $widget_types = elgg_get_widget_types([ 36 | 'context' => $context, 37 | 'container' => $owner, 38 | ]); 39 | uasort($widget_types, function ($a, $b) { 40 | return strcmp($a->name, $b->name); 41 | }); 42 | 43 | $current_handlers = []; 44 | foreach ($widgets as $column_widgets) { 45 | foreach ($column_widgets as $widget) { 46 | $current_handlers[] = $widget->handler; 47 | } 48 | } 49 | 50 | $result = elgg_format_element('p', [ 51 | 'class' => 'elgg-text-help', 52 | ], elgg_echo('widgets:add:description')); 53 | 54 | $list_items = ''; 55 | $settings_service = WidgetsSettingsConfig::instance(); 56 | 57 | foreach ($widget_types as $handler => $widget_type) { 58 | $can_add = $settings_service->getSetting($handler, 'can_add', $context); 59 | $hide = $settings_service->getSetting($handler, 'hide', $context); 60 | 61 | if (!$can_add || $hide) { 62 | // can not add or should be hidden 63 | continue; 64 | } 65 | 66 | $class = []; 67 | // check if widget added and only one instance allowed 68 | if (!$widget_type->multiple && in_array($handler, $current_handlers)) { 69 | $class[] = 'elgg-state-unavailable'; 70 | } else { 71 | $class[] = 'elgg-state-available'; 72 | } 73 | 74 | $class[] = $widget_type->multiple ? 'elgg-widget-multiple' : 'elgg-widget-single'; 75 | 76 | $action = '
'; 77 | if (!$widget_type->multiple) { 78 | $action .= elgg_format_element('span', ['class' => 'elgg-quiet'], elgg_echo('widget:unavailable')); 79 | } 80 | 81 | $action .= elgg_view('output/url', [ 82 | 'class' => ['elgg-button', 'elgg-button-submit', 'elgg-size-small'], 83 | 'text' => elgg_echo('add'), 84 | 'href' => elgg_generate_action_url('widgets/add', [ 85 | 'handler' => $handler, 86 | 'page_owner_guid' => $owner_guid, 87 | 'context' => $context, 88 | 'show_access' => elgg_extract('show_access', $vars, get_input('show_access')), 89 | 'default_widgets' => elgg_in_context('default_widgets'), 90 | 'new_widget_column' => $new_widget_column, 91 | 'new_widget_position' => $new_widget_position, 92 | ]), 93 | ]); 94 | $action .= '
'; 95 | 96 | $description = elgg_format_element('div', ['class' => 'elgg-loud'], $widget_type->name); 97 | 98 | if ($widget_type->description) { 99 | $description .= elgg_format_element('div', ['class' => 'elgg-quiet'], $widget_type->description); 100 | } 101 | 102 | $description = elgg_format_element('div', [ 103 | 'class' => 'elgg-widgets-add-description', 104 | ], $description); 105 | 106 | $item_content = $description . $action; 107 | 108 | $list_items .= elgg_format_element('li', [ 109 | 'class' => $class, 110 | 'data-elgg-widget-type' => $handler, 111 | ], $item_content); 112 | } 113 | 114 | $result .= elgg_format_element('ul', [], $list_items); 115 | 116 | $search_box = elgg_view('input/text', [ 117 | 'name' => 'widget_search', 118 | 'title' => elgg_echo('search'), 119 | 'placeholder' => elgg_echo('search'), 120 | ]); 121 | 122 | echo elgg_view_module('info', elgg_echo('widgets:add'), $result, [ 123 | 'class' => 'elgg-widgets-add-panel', 124 | 'menu' => $search_box, 125 | ]); 126 | -------------------------------------------------------------------------------- /languages/fr.php: -------------------------------------------------------------------------------- 1 | 'Blocs d\'info', 9 | 'admin:widgets:manage' => 'Gérer', 10 | 'admin:widgets:manage:index' => 'Gérer l\'index', 11 | 'admin:statistics:widgets' => 'Utilisation de blocs d\'infos', 12 | 'widget_manager:widgets:edit:custom_title' => 'Votre titre', 13 | 'widget_manager:widgets:edit:custom_url' => 'Lien de votre titre', 14 | 'widget_manager:widgets:edit:custom_more_title' => 'Plus de texte', 15 | 'widget_manager:widgets:edit:custom_more_url' => 'Plus de liens', 16 | 'widget_manager:widgets:edit:hide_header' => 'Cacher l\'entête', 17 | 'widget_manager:widgets:edit:custom_class' => 'Classe CSS sur mesure', 18 | 'widget_manager:widgets:edit:disable_widget_content_style' => 'Pas de style', 19 | 'widget_manager:groups:enable_widget_manager' => 'Activer la gestion des blocs d\'info', 20 | 'widget_manager:settings:index' => 'Page d\'index', 21 | 'widget_manager:settings:group' => 'Groupe', 22 | 'widget_manager:settings:custom_index' => 'Utiliser Widget Manager custom index?', 23 | 'widget_manager:settings:custom_index:non_loggedin' => 'Pour les utilisateurs non logués seulement', 24 | 'widget_manager:settings:custom_index:loggedin' => 'Pour les utilisateurs logués seulement', 25 | 'widget_manager:settings:custom_index:all' => 'Pour tous les utilisateurs', 26 | 'widget_manager:settings:widget_layout' => 'Choisissez un format de page de widgets', 27 | 'widget_manager:settings:widget_layout:33|33|33' => 'Par défaut (33% par colonne)', 28 | 'widget_manager:settings:widget_layout:50|25|25' => 'Colonne gauche large (50%, 25%, 25%)', 29 | 'widget_manager:settings:widget_layout:25|50|25' => 'Colonne du milieu large (25%, 50%, 25%)', 30 | 'widget_manager:settings:widget_layout:25|25|50' => 'Colonne de droite large (25%, 25%, 50%)', 31 | 'widget_manager:settings:widget_layout:75|25' => '2 colonnes (75%, 25%)', 32 | 'widget_manager:settings:widget_layout:60|40' => '2 colonnes (60%, 40%)', 33 | 'widget_manager:settings:widget_layout:50|50' => '2 colonnes (50%, 50%)', 34 | 'widget_manager:settings:widget_layout:40|60' => '2 colonnes (40%, 60%)', 35 | 'widget_manager:settings:widget_layout:25|75' => '2 colonnes (25%, 75%)', 36 | 'widget_manager:settings:widget_layout:100' => '1 seule colonne (100%)', 37 | 'widget_manager:settings:group:enable' => 'Activer la gestion des blocs d\'infos pour les groupes', 38 | 'widget_manager:settings:group:enable:yes' => 'Oui, gérable par l\'option outils de groupes', 39 | 'widget_manager:settings:group:enable:forced' => 'Oui, toujours actif', 40 | 'widget_manager:settings:group:option_default_enabled' => 'Gestion des blocs d\'infos pour les groupe activé par défaut', 41 | 'widget_manager:settings:group:option_admin_only' => 'Seul l\'administrateur peut activer la gestion des blocs d\'infos dans les groupes', 42 | 'widget_manager:settings:group:force_tool_widgets' => 'Appliquer la gestion des blocs d\'infos pour tous les groupes', 43 | 'widget_manager:settings:group:force_tool_widgets:confirm' => 'Êtes vous sûr/e? Cette action s\'appliquera pour tous les groupes.', 44 | 'widget_manager:settings:extra_contexts:page' => 'Page', 45 | 'widget_manager:settings:extra_contexts:layout' => 'Design', 46 | 'widget_manager:settings:extra_contexts:manager' => 'Gestionnaire', 47 | 'widget_manager:forms:manage_widgets:context' => 'Disponible dans ce contexte', 48 | 'widget_manager:forms:manage_widgets:multiple' => 'Widget multiple autorisé', 49 | 'widget_manager:forms:manage_widgets:non_default' => 'Ce paramétrage est différent de la valeur par défaut', 50 | 'widget_manager:forms:manage_widgets:unsupported_context:confirm' => 'Êtes vous sûr d\'activer ce widget pour ce contexte? Si ce widget ne supporte pas ce contexte, il pourrait survenir des problèmes.', 51 | 'widget_manager:forms:groups_widget_access:title' => 'Accès aux blocs d\'infos', 52 | 'widget_manager:forms:groups_widget_access:description' => 'Cette action vous permet de mettre à jour le niveau d\'accès de tous les blocs d\'infos de ce groupe.', 53 | 'widget_manager:action:manage:success' => 'La configuration des blocs d\'infos a été sauvegardée', 54 | 'widget_manager:action:force_tool_widgets:error:not_enabled' => 'La gestionnaire de blocs d\'info pour les groupes n\'est pas activé', 55 | 'widget_manager:action:force_tool_widgets:success' => 'Activer l\'outil widgets pour les groupes %s', 56 | 'widget_manager:action:groups:update_widget_access:success' => 'L\'accès à tous les blocs d\'infos pour ce groupe a été mis à jour', 57 | 'widget_manager:widgets:edit:advanced' => 'Avancé', 58 | ); 59 | -------------------------------------------------------------------------------- /languages/es.php: -------------------------------------------------------------------------------- 1 | 'Widgets', 9 | 'widget_manager:widgets:edit:advanced' => 'Avanzado', 10 | 'widget_manager:action:groups:update_widget_access:success' => 'Se actualiza el acceso a todos los widgets de este grupo', 11 | 'widget_manager:action:force_tool_widgets:success' => 'Widgets específicos de herramientas forzadas para grupos %s', 12 | 'widget_manager:action:force_tool_widgets:error:not_enabled' => 'La administración de widgets para grupos no está habilitada', 13 | 'widget_manager:action:manage:success' => 'Configuración de widgets guardada correctamente', 14 | 'widget_manager:forms:groups_widget_access:description' => 'Esta acción le permite actualizar el nivel de acceso de todos los widgets de este grupo al nivel de acceso especificado.', 15 | 'widget_manager:forms:groups_widget_access:title' => 'Acceso de Widget', 16 | 'widget_manager:forms:manage_widgets:unsupported_context:confirm' => '¿Estás seguro de que quieres habilitar este widget para este contexto? Si el widget no es compatible con el contexto, esto podría causar problemas.', 17 | 'widget_manager:forms:manage_widgets:non_default' => 'Esta configuración es diferente de la configuración predeterminada', 18 | 'widget_manager:forms:manage_widgets:multiple' => 'Múltiples widgets permitidos', 19 | 'widget_manager:forms:manage_widgets:can_add' => 'Puede ser añadido', 20 | 'widget_manager:forms:manage_widgets:context' => 'Disponible en el contexto', 21 | 'widget_manager:forms:manage_widgets:no_widgets' => 'No hay widgets para administrar', 22 | 'widget_manager:settings:extra_contexts:manager' => 'Administrador', 23 | 'widget_manager:settings:extra_contexts:layout' => 'Diseño', 24 | 'widget_manager:settings:extra_contexts:page' => 'Pagina', 25 | 'widget_manager:settings:extra_contexts:description' => 'Introduzca el nombre del controlador de página de la nueva página que obtendrá un diseño similar al de la página de índice. Puedes agregar tantas páginas como necesites. Asegúrese de no agregar un manejador de página que ya esté en uso. También puede configurar el diseño de columna para esa página y, opcionalmente, asignar usuarios no administradores como administrador de la página introduciendo su nombre de usuario. Puede tener varios administradores separando su nombre de usuario por una coma.', 26 | 'widget_manager:settings:group:force_tool_widgets:confirm' => '¿Estás seguro? Esto agregará / eliminará todos los widgets específicos de una opción de herramienta para todos los grupos (donde la administración de widgets está habilitada).', 27 | 'widget_manager:settings:group:force_tool_widgets' => 'Aplicar widgets de herramientas de grupo', 28 | 'widget_manager:settings:group:option_admin_only' => 'Sólo el administrador puede habilitar widgets de grupo', 29 | 'widget_manager:settings:group:option_default_enabled' => 'Gestión de widgets para grupos activada por defecto', 30 | 'widget_manager:settings:group:enable:forced' => 'Sí, siempre en', 31 | 'widget_manager:settings:group:enable:yes' => 'Sí, manejable por opción de herramienta de grupo', 32 | 'widget_manager:settings:group:enable' => 'Habilitar el Administrador de widgets para grupos', 33 | 'widget_manager:settings:widget_layout:100' => 'Columna simple (100%)', 34 | 'widget_manager:settings:widget_layout:25|75' => 'Dos columnas (25%, 75%)', 35 | 'widget_manager:settings:widget_layout:40|60' => 'Dos columnas (40%, 60%)', 36 | 'widget_manager:settings:widget_layout:50|50' => 'Dos columnas (50%, 50%)', 37 | 'widget_manager:settings:widget_layout:60|40' => 'Dos columnas (60%, 40%)', 38 | 'widget_manager:settings:widget_layout:75|25' => 'Dos columna (75%, 25%)', 39 | 'widget_manager:settings:widget_layout:25|25|50' => 'Columna ancha derecha (25%, 25%, 50%)', 40 | 'widget_manager:settings:widget_layout:25|50|25' => 'Columna media ancha (25%, 50%, 25%)', 41 | 'widget_manager:settings:widget_layout:50|25|25' => 'Columna ancha izquierda (50%, 25%, 25%)', 42 | 'widget_manager:settings:widget_layout:33|33|33' => 'Diseño por defecto (33% por columna)', 43 | 'widget_manager:settings:widget_layout' => 'Elija un diseño de widget', 44 | 'widget_manager:settings:custom_index:all' => 'Para todos los usuarios', 45 | 'widget_manager:settings:custom_index:loggedin' => 'Sólo para usuarios registrados', 46 | 'widget_manager:settings:custom_index:non_loggedin' => 'Sólo para usuarios no registrados', 47 | 'widget_manager:settings:custom_index' => '¿Utilizar el índice personalizado de administrador de Widget?', 48 | 'widget_manager:settings:group' => 'Grupo', 49 | 'widget_manager:settings:index' => 'Índice', 50 | 'widget_manager:groups:enable_widget_manager' => 'Habilitar la administración de widgets', 51 | 'widget_manager:widgets:edit:disable_widget_content_style' => 'No hay estilo de widget', 52 | 'widget_manager:widgets:edit:custom_class' => 'Clase CSS personalizada', 53 | 'widget_manager:widgets:edit:hide_header' => 'Ocultar encabezado', 54 | 'widget_manager:widgets:edit:custom_more_url' => 'Enlace personalizado más', 55 | 'widget_manager:widgets:edit:custom_more_title' => 'Personalizar más texto', 56 | 'widget_manager:widgets:edit:custom_url' => 'Enlace de título personalizado', 57 | 'widget_manager:widgets:edit:custom_title' => 'Título Personalizado', 58 | 'admin:statistics:widgets' => 'Uso de Widget', 59 | 'admin:widgets:manage:index' => 'Administrar el índice', 60 | 'admin:widgets:manage' => 'Gestionar', 61 | ); 62 | -------------------------------------------------------------------------------- /views/default/plugins/widget_manager/settings.php: -------------------------------------------------------------------------------- 1 | 'select', 7 | '#label' => elgg_echo('widget_manager:settings:custom_index'), 8 | 'name' => 'params[custom_index]', 9 | 'value' => $plugin->custom_index, 10 | 'options_values' => [ 11 | '0|0' => elgg_echo('option:no'), 12 | '1|0' => elgg_echo('widget_manager:settings:custom_index:non_loggedin'), 13 | '0|1' => elgg_echo('widget_manager:settings:custom_index:loggedin'), 14 | '1|1' => elgg_echo('widget_manager:settings:custom_index:all'), 15 | ], 16 | ]); 17 | 18 | $index_settings .= elgg_view_field([ 19 | '#type' => 'select', 20 | '#label' => elgg_echo('widget_manager:settings:widget_layout'), 21 | 'name' => 'params[widget_layout]', 22 | 'value' => $plugin->widget_layout, 23 | 'options_values' => [ 24 | 'fluid' => elgg_echo('widget_manager:settings:widget_layout:fluid'), 25 | '33|33|33' => elgg_echo('widget_manager:settings:widget_layout:33|33|33'), 26 | '50|25|25' => elgg_echo('widget_manager:settings:widget_layout:50|25|25'), 27 | '25|50|25' => elgg_echo('widget_manager:settings:widget_layout:25|50|25'), 28 | '25|25|50' => elgg_echo('widget_manager:settings:widget_layout:25|25|50'), 29 | '75|25' => elgg_echo('widget_manager:settings:widget_layout:75|25'), 30 | '60|40' => elgg_echo('widget_manager:settings:widget_layout:60|40'), 31 | '50|50' => elgg_echo('widget_manager:settings:widget_layout:50|50'), 32 | '40|60' => elgg_echo('widget_manager:settings:widget_layout:40|60'), 33 | '25|75' => elgg_echo('widget_manager:settings:widget_layout:25|75'), 34 | '100' => elgg_echo('widget_manager:settings:widget_layout:100'), 35 | ], 36 | ]); 37 | 38 | $index_settings .= elgg_view_field([ 39 | '#type' => 'userpicker', 40 | '#label' => elgg_echo('widget_manager:settings:index_managers'), 41 | 'name' => 'params[index_managers]', 42 | 'value' => $plugin->index_managers ? explode(',', $plugin->index_managers) : null, 43 | ]); 44 | 45 | echo elgg_view_module('info', elgg_echo('widget_manager:settings:index'), $index_settings); 46 | 47 | if (elgg_is_active_plugin('groups')) { 48 | $group_settings = elgg_view_field([ 49 | '#type' => 'select', 50 | '#label' => elgg_echo('widget_manager:settings:group:enable'), 51 | '#help' => elgg_view('output/url', [ 52 | 'text' => elgg_echo('widget_manager:settings:group:force_tool_widgets'), 53 | 'href' => 'action/widget_manager/force_tool_widgets', 54 | 'confirm' => elgg_echo('widget_manager:settings:group:force_tool_widgets:confirm'), 55 | ]), 56 | 'name' => 'params[group_enable]', 57 | 'value' => $plugin->group_enable, 58 | 'options_values' => [ 59 | 'no' => elgg_echo('option:no'), 60 | 'yes' => elgg_echo('widget_manager:settings:group:enable:yes'), 61 | 'forced' => elgg_echo('widget_manager:settings:group:enable:forced'), 62 | ], 63 | ]); 64 | 65 | $group_settings .= elgg_view_field([ 66 | '#type' => 'checkbox', 67 | '#label' => elgg_echo('widget_manager:settings:group:option_default_enabled'), 68 | 'name' => 'params[group_option_default_enabled]', 69 | 'checked' => $plugin->group_option_default_enabled === 'yes', 70 | 'switch' => true, 71 | 'default' => 'no', 72 | 'value' => 'yes', 73 | ]); 74 | 75 | $group_settings .= elgg_view_field([ 76 | '#type' => 'checkbox', 77 | '#label' => elgg_echo('widget_manager:settings:group:option_admin_only'), 78 | 'name' => 'params[group_option_admin_only]', 79 | 'checked' => $plugin->group_option_admin_only === 'yes', 80 | 'switch' => true, 81 | 'default' => 'no', 82 | 'value' => 'yes', 83 | ]); 84 | 85 | $group_settings .= elgg_view_field([ 86 | '#type' => 'number', 87 | '#label' => elgg_echo('widget_manager:settings:group:group_column_count'), 88 | 'name' => 'params[group_column_count]', 89 | 'value' => $plugin->group_column_count, 90 | 'min' => 1, 91 | ]); 92 | 93 | echo elgg_view_module('info', elgg_echo('widget_manager:settings:group'), $group_settings); 94 | } 95 | 96 | $lazy_settings = elgg_view_field([ 97 | '#type' => 'switch', 98 | '#label' => elgg_echo('widget_manager:settings:lazy_loading:enabled'), 99 | '#help' => elgg_echo('widget_manager:settings:lazy_loading:enabled:help'), 100 | 'name' => 'params[lazy_loading_enabled]', 101 | 'value' => $plugin->lazy_loading_enabled, 102 | ]); 103 | 104 | $lazy_settings .= elgg_view_field([ 105 | '#type' => 'switch', 106 | '#label' => elgg_echo('widget_manager:settings:lazy_loading:lazy_loading_mobile_columns'), 107 | '#help' => elgg_echo('widget_manager:settings:lazy_loading:lazy_loading_mobile_columns:help'), 108 | 'name' => 'params[lazy_loading_mobile_columns]', 109 | 'value' => $plugin->lazy_loading_mobile_columns, 110 | ]); 111 | 112 | $lazy_settings .= elgg_view_field([ 113 | '#type' => 'number', 114 | '#label' => elgg_echo('widget_manager:settings:lazy_loading:lazy_loading_under_fold'), 115 | '#help' => elgg_echo('widget_manager:settings:lazy_loading:lazy_loading_under_fold:help'), 116 | 'name' => 'params[lazy_loading_under_fold]', 117 | 'value' => $plugin->lazy_loading_under_fold, 118 | 'min' => 0, 119 | ]); 120 | 121 | echo elgg_view_module('info', elgg_echo('widget_manager:settings:lazy_loading'), $lazy_settings); 122 | 123 | $other_settings = elgg_view_field([ 124 | '#type' => 'switch', 125 | '#label' => elgg_echo('widget_manager:settings:show_collapse_content'), 126 | '#help' => elgg_echo('widget_manager:settings:show_collapse_content:help'), 127 | 'name' => 'params[show_collapse_content]', 128 | 'value' => $plugin->show_collapse_content, 129 | ]); 130 | 131 | echo elgg_view_module('info', elgg_echo('widget_manager:settings:other'), $other_settings); 132 | -------------------------------------------------------------------------------- /views/default/page/layouts/widgets.php: -------------------------------------------------------------------------------- 1 | guid !== $page_owner->guid) { 38 | elgg_set_page_owner_guid($owner->guid); 39 | } 40 | 41 | $can_edit_layout = elgg_can_edit_widget_layout($context); 42 | 43 | $classes = elgg_extract_class($vars, [ 44 | 'elgg-layout-widgets', 45 | "elgg-layout-widgets-{$context}", 46 | ]); 47 | 48 | if ($can_edit_layout) { 49 | $classes[] = 'elgg-layout-can-edit'; 50 | } 51 | 52 | $widgets = elgg_extract('widgets', $vars); 53 | if ($widgets === null) { 54 | $ignore_access = $can_edit_layout ? ELGG_IGNORE_ACCESS : 0; 55 | $widgets = elgg_call($ignore_access, function() use ($owner, $context) { 56 | return elgg_get_widgets($owner->guid, $context); 57 | }); 58 | } 59 | 60 | $result = ''; 61 | $no_widgets = elgg_extract('no_widgets', $vars); 62 | if (empty($widgets) && !empty($no_widgets)) { 63 | if ($no_widgets instanceof \Closure) { 64 | echo $no_widgets(); 65 | } else { 66 | $result .= $no_widgets; 67 | } 68 | } 69 | 70 | // adjusts context to get correct widgets for special widget pages 71 | $available_widgets_context = elgg_trigger_event_results('available_widgets_context', 'widget_manager', [], $context); 72 | if ($widgets) { 73 | $widget_types = elgg_get_widget_types([ 74 | 'context' => $available_widgets_context, 75 | 'container' => $owner, 76 | ]); 77 | } 78 | 79 | if ($show_add_widgets && $can_edit_layout) { 80 | $vars['show_collapse_content'] = !in_array('widgets-fluid-columns', $classes); 81 | 82 | $result .= elgg_view('page/layouts/widgets/add_button', $vars); 83 | } 84 | 85 | $show_empty_grid = (bool) elgg_extract('show_empty_grid', $vars, true); 86 | if (!$show_empty_grid && empty($result) && empty($widgets)) { 87 | // Restore original page owner 88 | if (empty($page_owner)) { 89 | elgg_set_page_owner_guid(false); 90 | } elseif ($owner->guid !== $page_owner->guid) { 91 | elgg_set_page_owner_guid($page_owner->guid); 92 | } 93 | 94 | return; 95 | } 96 | 97 | // push context after the add_button as add button uses current context 98 | elgg_push_context('widgets'); 99 | 100 | // move hidden columns widgets to last visible column 101 | if (!isset($widgets[$num_columns])) { 102 | $widgets[$num_columns] = []; 103 | } 104 | 105 | foreach ($widgets as $index => $column_widgets) { 106 | if ($index <= $num_columns) { 107 | continue; 108 | } 109 | 110 | // append widgets to last column and retain order 111 | foreach ($column_widgets as $column_widget) { 112 | $widgets[$num_columns][] = $column_widget; 113 | } 114 | 115 | unset($widgets[$index]); 116 | } 117 | 118 | // remove unsupported widgets 119 | foreach ($widgets as $column_index => $column_widgets) { 120 | foreach ($column_widgets as $widget_index => $column_widget) { 121 | if (!array_key_exists($column_widget->handler, $widget_types)) { 122 | unset($widgets[$column_index][$widget_index]); 123 | } 124 | } 125 | } 126 | 127 | $grid_id = uniqid('elgg-widgets-grid-'); 128 | $grid = ''; 129 | for ($column_index = 1; $column_index <= $num_columns; $column_index++) { 130 | $column_widgets = (array) elgg_extract($column_index, $widgets, []); 131 | 132 | $widgets_content = ''; 133 | foreach ($column_widgets as $widget) { 134 | $widgets_content .= elgg_view_entity($widget, [ 135 | 'show_access' => $show_access, 136 | 'register_rss_link' => false, 137 | 'layout_info' => [ 138 | 'widgets' => $widgets, 139 | 'classes' => $classes, 140 | ], 141 | ]); 142 | } 143 | 144 | $grid .= elgg_format_element('div', [ 145 | 'class' => elgg_extract_class(elgg_extract('column_classes', $vars, []), ['elgg-widgets', "elgg-widget-col-{$column_index}"], $column_index), 146 | 'data-sortable-options' => json_encode([ 147 | 'connectWith' => '#' . $grid_id . ' .elgg-widgets', 148 | ]), 149 | 'data-widget-column' => $column_index, 150 | ], $widgets_content); 151 | } 152 | 153 | $result .= elgg_format_element('div', [ 154 | 'class' => 'elgg-widgets-grid', 155 | 'id' => $grid_id, 156 | ], $grid); 157 | 158 | elgg_pop_context(); 159 | 160 | echo elgg_format_element('div', [ 161 | 'class' => $classes, 162 | 'data-page-owner-guid' => $owner->guid, 163 | 'data-context-stack' => json_encode(elgg_get_context_stack()), 164 | ], $result); 165 | 166 | // Restore original page owner 167 | if (empty($page_owner)) { 168 | elgg_set_page_owner_guid(0); 169 | } elseif ($owner->guid !== $page_owner->guid) { 170 | elgg_set_page_owner_guid($page_owner->guid); 171 | } 172 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/WidgetsSettingsConfig.php: -------------------------------------------------------------------------------- 1 | config = []; 26 | } else { 27 | $this->config = json_decode($widgets_config, true); 28 | } 29 | } 30 | 31 | /** 32 | * Returns all save settings for a widget 33 | * 34 | * @param string $handler Widget to retrieve settings for 35 | * @param string|null $context optional context to return settings for 36 | * 37 | * @return array 38 | */ 39 | public function getAll(string $handler, ?string $context = null): array { 40 | if (!isset($this->config[$handler]['contexts'])) { 41 | if (!isset($context)) { 42 | return []; 43 | } 44 | 45 | return ['contexts' => []]; 46 | } 47 | 48 | if (empty($context)) { 49 | return $this->config[$handler]; 50 | } 51 | 52 | return elgg_extract($context, $this->config[$handler]['contexts'], []); 53 | } 54 | 55 | /** 56 | * Returns a setting value for a specific widget handler 57 | * 58 | * @param string $handler Handler of the widget 59 | * @param string $setting Setting to retrieve 60 | * @param string $context Context for this setting (will default to current context) 61 | * 62 | * @return boolean|null 63 | */ 64 | public function getSetting(string $handler, string $setting, string $context = ''): ?bool { 65 | $context = $this->getContext($context); 66 | 67 | $setting_value = elgg_extract($setting, $this->getAll($handler, $context)); 68 | if (isset($setting_value)) { 69 | return (bool) $setting_value; 70 | } 71 | 72 | if (!in_array($setting, ['can_add', 'hide'])) { 73 | // unsupported setting 74 | return null; 75 | } 76 | 77 | if ($setting === 'can_add') { 78 | // default widgets can be added 79 | return true; 80 | } 81 | 82 | // default for unexisting setting value 83 | return false; 84 | } 85 | 86 | /** 87 | * Returns given context or detects current context 88 | * 89 | * @param string $context context 90 | * 91 | * @return string 92 | */ 93 | protected function getContext(string $context = ''): string { 94 | return $context ?: (string) elgg_get_context(); 95 | } 96 | 97 | /** 98 | * Returns registered service name 99 | * @return string 100 | */ 101 | public static function name() { 102 | return 'widgets.settings'; 103 | } 104 | 105 | /** 106 | * Returns whether or not the widget body should be lazy loaded 107 | * 108 | * @param \WidgetManagerWidget $widget Widget to check the lazy loading logic for 109 | * @param array $layout_info Additional info about the layout the widget is shown in 110 | * 111 | * @return bool 112 | */ 113 | public function showLazyLoaded(\WidgetManagerWidget $widget, array $layout_info = []): bool { 114 | if (elgg_is_xhr()) { 115 | // no lazy loading body for ajax loaded widgets 116 | return false; 117 | } 118 | 119 | if (!(bool) elgg_get_plugin_setting('lazy_loading_enabled', 'widget_manager')) { 120 | return false; 121 | } 122 | 123 | $detect_lazy_loading = function() use ($widget, $layout_info) { 124 | // configured in advanced widget config 125 | if ($widget->widget_manager_lazy_load_content) { 126 | return true; 127 | } 128 | 129 | // configured as always lazy loaded handler for context 130 | if ($this->getSetting($widget->handler, 'always_lazy_load', (string) $widget->context)) { 131 | return true; 132 | } 133 | 134 | $widgets = elgg_extract('widgets', $layout_info, []); 135 | 136 | // under the fold 137 | $under_fold_limit = elgg_get_plugin_setting('lazy_loading_under_fold', 'widget_manager'); 138 | if (!elgg_is_empty($under_fold_limit) && !empty($widgets)) { 139 | if (in_array('widgets-fluid-columns', elgg_extract('classes', $layout_info, []))) { 140 | $under_fold_limit = $under_fold_limit * 3; 141 | } 142 | 143 | // if invalid widgets are removed from the layout, the index could be messed up 144 | $column_widgets = array_values((array) elgg_extract($widget->column, $widgets, [])); 145 | 146 | foreach ($column_widgets as $column_index => $column_widget) { 147 | // if position in column is over the fold limit than it should be lazy loaded 148 | if ($column_index >= (int) $under_fold_limit) { 149 | return true; 150 | } 151 | 152 | if ($column_widget->guid === $widget->guid) { 153 | // do not look further 154 | break; 155 | } 156 | } 157 | } 158 | 159 | // mobile columns 160 | if ((bool) elgg_get_plugin_setting('lazy_loading_mobile_columns', 'widget_manager') && !empty($widgets)) { 161 | $first_column_with_data = 3; 162 | while (empty($widgets[$first_column_with_data])) { 163 | $first_column_with_data--; 164 | 165 | if ($first_column_with_data === 1) { 166 | break; 167 | } 168 | } 169 | 170 | if ($widget->column < $first_column_with_data) { 171 | $mobile_detect = new \Detection\MobileDetect(); 172 | if ($mobile_detect->hasUserAgent() && $mobile_detect->isMobile()) { 173 | return true; 174 | } 175 | } 176 | } 177 | 178 | return false; 179 | }; 180 | 181 | return (bool) elgg_trigger_event_results('lazy_load', 'widget_manager', ['entity' => $widget, 'layout_info' => $layout_info], $detect_lazy_loading()); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /elgg-plugin.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'version' => '17.1.2', 18 | 'dependencies' => [ 19 | 'profile' => [ 20 | 'must_be_active' => false, 21 | 'position' => 'after', 22 | ], 23 | 'groups' => [ 24 | 'must_be_active' => false, 25 | 'position' => 'after', 26 | ], 27 | ], 28 | ], 29 | 'bootstrap' => \ColdTrick\WidgetManager\Bootstrap::class, 30 | 'settings' => [ 31 | 'group_column_count' => 2, 32 | 'widget_layout' => '33|33|33', 33 | 'lazy_loading_enabled' => 0, 34 | 'lazy_loading_mobile_columns' => 1, 35 | 'lazy_loading_under_fold' => 5, 36 | 'show_collapse_content' => false, 37 | ], 38 | 'entities' => [ 39 | [ 40 | 'type' => 'object', 41 | 'subtype' => 'widget', 42 | 'class' => 'WidgetManagerWidget', 43 | 'capabilities' => [ 44 | 'commentable' => false, 45 | ], 46 | ], 47 | [ 48 | 'type' => 'object', 49 | 'subtype' => 'widget_page', 50 | 'class' => 'WidgetPage', 51 | 'capabilities' => [ 52 | 'commentable' => false, 53 | 'searchable' => true, 54 | ], 55 | ], 56 | ], 57 | 'actions' => [ 58 | 'widget_manager/lazy_load_widgets' => [ 59 | 'access' => 'public', 60 | ], 61 | 'widget_manager/groups/update_widget_access' => [], 62 | 'widget_manager/force_tool_widgets' => [ 63 | 'access' => 'admin', 64 | ], 65 | 'widget_manager/manage_widgets' => [ 66 | 'access' => 'admin', 67 | ], 68 | 'widget_manager/widget_page' => [], 69 | 'widget_manager/cleanup' => [ 70 | 'access' => 'admin', 71 | ], 72 | 'widget_manager/fluid_order' => [], 73 | ], 74 | 'views' => [ 75 | 'default' => [ 76 | 'muuri.mjs' => $composer_path . 'vendor/npm-asset/muuri/dist/muuri.js', 77 | ], 78 | ], 79 | 'view_extensions' => [ 80 | 'admin.css' => [ 81 | 'forms/widget_manager/manage_widgets/widget.css' => [], 82 | ], 83 | 'elements/widgets.css' => [ 84 | 'widget_manager/site.css' => [], 85 | ], 86 | 'groups/edit/settings' => [ 87 | 'widget_manager/forms/groups_widget_access' => [], 88 | ], 89 | 'object/widget/elements/content' => [ 90 | 'widget_manager/widgets/custom_more' => [], 91 | ], 92 | 'page/layouts/widgets/add_panel' => [ 93 | 'widget_manager/group_tool_widgets' => ['priority' => 400], 94 | ], 95 | ], 96 | 'view_options' => [ 97 | 'forms/widget_manager/widget_page' => ['ajax' => true], 98 | 'widget_manager/widgets/settings' => ['ajax' => true], 99 | 'widgets/user_search/content' => ['ajax' => true], 100 | ], 101 | 'events' => [ 102 | 'access:collections:write' => [ 103 | 'all' => [ 104 | '\ColdTrick\WidgetManager\Access::setWriteAccess' => ['priority' => 999], 105 | ], 106 | ], 107 | 'access:collections:read' => [ 108 | 'user' => [ 109 | '\ColdTrick\WidgetManager\Access::addLoggedOutReadAccess' => [], 110 | ], 111 | ], 112 | 'action:validate' => [ 113 | 'widgets/add' => [ 114 | '\ColdTrick\WidgetManager\Access::moreRightsForWidgetManager' => [], 115 | ], 116 | 'widgets/delete' => [ 117 | '\ColdTrick\WidgetManager\Access::moreRightsForWidgetManager' => [], 118 | ], 119 | 'widgets/move' => [ 120 | '\ColdTrick\WidgetManager\Access::moreRightsForWidgetManager' => [], 121 | ], 122 | 'widgets/save' => [ 123 | '\ColdTrick\WidgetManager\Access::moreRightsForWidgetManager' => [], 124 | ], 125 | ], 126 | 'create' => [ 127 | 'object' => [ 128 | '\ColdTrick\WidgetManager\Groups::addGroupWidget' => [], 129 | '\ColdTrick\WidgetManager\Widgets::fixPrivateAccess' => [], 130 | ], 131 | ], 132 | 'delete' => [ 133 | 'object' => [ 134 | '\ColdTrick\WidgetManager\Groups::deleteGroupWidget' => [], 135 | ], 136 | ], 137 | 'entity:url' => [ 138 | 'object' => [ 139 | '\ColdTrick\WidgetManager\Widgets::getWidgetURL' => ['priority' => 9999], 140 | ], 141 | ], 142 | 'get_list' => [ 143 | 'default_widgets' => [ 144 | '\ColdTrick\WidgetManager\Groups::addGroupsContextToDefaultWidgets' => [], 145 | ], 146 | ], 147 | 'group_tool_widgets' => [ 148 | 'widget_manager' => [ 149 | 'ColdTrick\WidgetManager\Widgets::groupToolWidgets' => [], 150 | ], 151 | ], 152 | 'handlers' => [ 153 | 'widgets' => [ 154 | '\ColdTrick\WidgetManager\Widgets::addDiscussionsWidgetToGroup' => [], 155 | '\ColdTrick\WidgetManager\Widgets::applyWidgetsConfig' => ['priority' => 9999], 156 | ], 157 | ], 158 | 'permissions_check' => [ 159 | 'object' => [ 160 | '\ColdTrick\WidgetManager\Access::canEditWidgetOnManagedLayout' => [], 161 | ], 162 | 'widget_layout' => [ 163 | '\ColdTrick\WidgetManager\Widgets::layoutPermissionsCheck' => [], 164 | ], 165 | ], 166 | 'permissions_check:delete' => [ 167 | 'object' => [ 168 | '\ColdTrick\WidgetManager\Access::onlyAdminsCanDeleteWidgetPages' => [], 169 | ], 170 | ], 171 | 'register' => [ 172 | 'menu:admin_header' => [ 173 | '\ColdTrick\WidgetManager\Menus\AdminHeader::registerAdminHeaderMenu' => [], 174 | ], 175 | 'menu:entity' => [ 176 | '\ColdTrick\WidgetManager\Menus\Entity::addWidgetPageEntityMenuItems' => [], 177 | ], 178 | 'menu:title:widgets' => [ 179 | '\ColdTrick\WidgetManager\Menus\Title::addWidgetsContentToggle' => [], 180 | ], 181 | ], 182 | 'seeds' => [ 183 | 'database' => [ 184 | '\ColdTrick\WidgetManager\Seeder::register' => [], 185 | ], 186 | ], 187 | 'setting' => [ 188 | 'plugin' => [ 189 | '\ColdTrick\WidgetManager\Settings::implodeSettings' => [], 190 | ], 191 | ], 192 | 'tool_options' => [ 193 | 'group' => [ 194 | '\ColdTrick\WidgetManager\Groups::registerGroupWidgetsTool' => [], 195 | ], 196 | ], 197 | 'update' => [ 198 | 'group' => [ 199 | '\ColdTrick\WidgetManager\Groups::updateGroupWidgets' => [], 200 | ], 201 | ], 202 | 'view' => [ 203 | 'object/widget/body' => [ 204 | '\ColdTrick\WidgetManager\Widgets::saveContentInCache' => ['priority' => 9999], 205 | ], 206 | ], 207 | 'view_vars' => [ 208 | 'groups/profile/widgets' => [ 209 | '\ColdTrick\WidgetManager\Groups::getGroupWidgetsLayout' => [], 210 | ], 211 | 'object/widget/body' => [ 212 | '\ColdTrick\WidgetManager\Widgets::getContentFromCache' => [], 213 | ], 214 | 'object/widget/edit' => [ 215 | '\ColdTrick\WidgetManager\Access::allowPrivateWidgetEdit' => [], 216 | ], 217 | 'object/widget/elements/controls' => [ 218 | '\ColdTrick\WidgetManager\Widgets::preventControls' => [], 219 | ], 220 | ], 221 | ], 222 | ]; 223 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "868cf70c025943127026cbd2d7228857", 8 | "packages": [ 9 | { 10 | "name": "mobiledetect/mobiledetectlib", 11 | "version": "4.8.09", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/serbanghita/Mobile-Detect.git", 15 | "reference": "a06fe2e546a06bb8c2639d6823d5250b2efb3209" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/a06fe2e546a06bb8c2639d6823d5250b2efb3209", 20 | "reference": "a06fe2e546a06bb8c2639d6823d5250b2efb3209", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=8.0", 25 | "psr/cache": "^3.0", 26 | "psr/simple-cache": "^3" 27 | }, 28 | "require-dev": { 29 | "friendsofphp/php-cs-fixer": "^v3.65.0", 30 | "phpbench/phpbench": "^1.2", 31 | "phpstan/phpstan": "^1.12.x-dev", 32 | "phpunit/phpunit": "^9.6.18", 33 | "squizlabs/php_codesniffer": "^3.11.1" 34 | }, 35 | "type": "library", 36 | "autoload": { 37 | "psr-4": { 38 | "Detection\\": "src/" 39 | } 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Serban Ghita", 48 | "email": "serbanghita@gmail.com", 49 | "homepage": "http://mobiledetect.net", 50 | "role": "Developer" 51 | } 52 | ], 53 | "description": "Mobile_Detect is a lightweight PHP class for detecting mobile devices. It uses the User-Agent string combined with specific HTTP headers to detect the mobile environment.", 54 | "homepage": "https://github.com/serbanghita/Mobile-Detect", 55 | "keywords": [ 56 | "detect mobile devices", 57 | "mobile", 58 | "mobile detect", 59 | "mobile detector", 60 | "php mobile detect" 61 | ], 62 | "support": { 63 | "issues": "https://github.com/serbanghita/Mobile-Detect/issues", 64 | "source": "https://github.com/serbanghita/Mobile-Detect/tree/4.8.09" 65 | }, 66 | "funding": [ 67 | { 68 | "url": "https://github.com/serbanghita", 69 | "type": "github" 70 | } 71 | ], 72 | "time": "2024-12-10T15:32:06+00:00" 73 | }, 74 | { 75 | "name": "npm-asset/muuri", 76 | "version": "0.9.5", 77 | "dist": { 78 | "type": "tar", 79 | "url": "https://registry.npmjs.org/muuri/-/muuri-0.9.5.tgz" 80 | }, 81 | "type": "npm-asset", 82 | "license": [ 83 | "MIT" 84 | ] 85 | }, 86 | { 87 | "name": "psr/cache", 88 | "version": "3.0.0", 89 | "source": { 90 | "type": "git", 91 | "url": "https://github.com/php-fig/cache.git", 92 | "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" 93 | }, 94 | "dist": { 95 | "type": "zip", 96 | "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", 97 | "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", 98 | "shasum": "" 99 | }, 100 | "require": { 101 | "php": ">=8.0.0" 102 | }, 103 | "type": "library", 104 | "extra": { 105 | "branch-alias": { 106 | "dev-master": "1.0.x-dev" 107 | } 108 | }, 109 | "autoload": { 110 | "psr-4": { 111 | "Psr\\Cache\\": "src/" 112 | } 113 | }, 114 | "notification-url": "https://packagist.org/downloads/", 115 | "license": [ 116 | "MIT" 117 | ], 118 | "authors": [ 119 | { 120 | "name": "PHP-FIG", 121 | "homepage": "https://www.php-fig.org/" 122 | } 123 | ], 124 | "description": "Common interface for caching libraries", 125 | "keywords": [ 126 | "cache", 127 | "psr", 128 | "psr-6" 129 | ], 130 | "support": { 131 | "source": "https://github.com/php-fig/cache/tree/3.0.0" 132 | }, 133 | "time": "2021-02-03T23:26:27+00:00" 134 | }, 135 | { 136 | "name": "psr/simple-cache", 137 | "version": "3.0.0", 138 | "source": { 139 | "type": "git", 140 | "url": "https://github.com/php-fig/simple-cache.git", 141 | "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" 142 | }, 143 | "dist": { 144 | "type": "zip", 145 | "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", 146 | "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", 147 | "shasum": "" 148 | }, 149 | "require": { 150 | "php": ">=8.0.0" 151 | }, 152 | "type": "library", 153 | "extra": { 154 | "branch-alias": { 155 | "dev-master": "3.0.x-dev" 156 | } 157 | }, 158 | "autoload": { 159 | "psr-4": { 160 | "Psr\\SimpleCache\\": "src/" 161 | } 162 | }, 163 | "notification-url": "https://packagist.org/downloads/", 164 | "license": [ 165 | "MIT" 166 | ], 167 | "authors": [ 168 | { 169 | "name": "PHP-FIG", 170 | "homepage": "https://www.php-fig.org/" 171 | } 172 | ], 173 | "description": "Common interfaces for simple caching", 174 | "keywords": [ 175 | "cache", 176 | "caching", 177 | "psr", 178 | "psr-16", 179 | "simple-cache" 180 | ], 181 | "support": { 182 | "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" 183 | }, 184 | "time": "2021-10-29T13:26:27+00:00" 185 | } 186 | ], 187 | "packages-dev": [], 188 | "aliases": [], 189 | "minimum-stability": "stable", 190 | "stability-flags": {}, 191 | "prefer-stable": false, 192 | "prefer-lowest": false, 193 | "platform": {}, 194 | "platform-dev": {}, 195 | "plugin-api-version": "2.6.0" 196 | } 197 | -------------------------------------------------------------------------------- /languages/en.php: -------------------------------------------------------------------------------- 1 | "Widget Page", 6 | 'collection:object:widget_page' => "Widget Pages", 7 | 8 | // admin menu items 9 | 'admin:widgets' => "Widgets", 10 | 'admin:widgets:manage' => "Manage", 11 | 'admin:widgets:cleanup' => "Cleanup", 12 | 'admin:widgets:pages' => "Widget Pages", 13 | 'admin:widgets:manage:index' => "Manage Index", 14 | 'admin:statistics:widgets' => "Widget Usage", 15 | 16 | // cleanup 17 | 'admin:widgets:cleanup:info' => "Here you can remove all widgets that are currently in the database, even those that are no longer available.", 18 | 19 | // widget edit wrapper 20 | 'widget_manager:widgets:edit:custom_title' => "Custom title", 21 | 'widget_manager:widgets:edit:custom_url' => "Custom title link", 22 | 'widget_manager:widgets:edit:custom_more_title' => "Custom more text", 23 | 'widget_manager:widgets:edit:custom_more_url' => "Custom more link", 24 | 'widget_manager:widgets:edit:hide_header' => "Hide header", 25 | 'widget_manager:widgets:edit:custom_class' => "Custom CSS class", 26 | 'widget_manager:widgets:edit:disable_widget_content_style' => "No widget style", 27 | 'widget_manager:widgets:edit:lazy_load_content' => "Lazy load the content", 28 | 29 | // group 30 | 'widget_manager:groups:enable_widget_manager' => "Enable management of widgets", 31 | 32 | // admin settings 33 | 'widget_manager:settings:index' => "Index", 34 | 'widget_manager:settings:group' => "Group", 35 | 'widget_manager:settings:lazy_loading' => "Lazy Loading", 36 | 'widget_manager:settings:other' => "Other", 37 | 38 | 'widget_manager:settings:custom_index' => "Use Widget Manager custom index?", 39 | 'widget_manager:settings:custom_index:non_loggedin' => "For non-loggedin users only", 40 | 'widget_manager:settings:custom_index:loggedin' => "For loggedin users only", 41 | 'widget_manager:settings:custom_index:all' => "For all users", 42 | 43 | 'widget_manager:settings:widget_layout' => "Choose a widget layout", 44 | 'widget_manager:settings:widget_layout:fluid' => "Fluid layout", 45 | 'widget_manager:settings:widget_layout:33|33|33' => "Default layout (33% per column)", 46 | 'widget_manager:settings:widget_layout:50|25|25' => "Wide left column (50%, 25%, 25%)", 47 | 'widget_manager:settings:widget_layout:25|50|25' => "Wide middle column (25%, 50%, 25%)", 48 | 'widget_manager:settings:widget_layout:25|25|50' => "Wide right column (25%, 25%, 50%)", 49 | 'widget_manager:settings:widget_layout:75|25' => "Two column (75%, 25%)", 50 | 'widget_manager:settings:widget_layout:60|40' => "Two column (60%, 40%)", 51 | 'widget_manager:settings:widget_layout:50|50' => "Two column (50%, 50%)", 52 | 'widget_manager:settings:widget_layout:40|60' => "Two column (40%, 60%)", 53 | 'widget_manager:settings:widget_layout:25|75' => "Two column (25%, 75%)", 54 | 'widget_manager:settings:widget_layout:100' => "Single column (100%)", 55 | 56 | 'widget_manager:settings:index_managers' => "Index managers", 57 | 'widget_manager:settings:show_collapse_content' => "Show widget content toggle on layout edit", 58 | 'widget_manager:settings:show_collapse_content:help' => "If enabled this will show a toggle next to the 'Add widget' button to toggle widget contents. This can help with organizing your widget layout.", 59 | 60 | 'widget_manager:settings:group:enable' => "Enable Widget Manager for groups", 61 | 'widget_manager:settings:group:enable:yes' => "Yes, manageable by group tool option", 62 | 'widget_manager:settings:group:enable:forced' => "Yes, always on", 63 | 'widget_manager:settings:group:option_default_enabled' => "Widget management for groups default enabled", 64 | 'widget_manager:settings:group:option_admin_only' => "Only administrator can enable group widgets", 65 | 'widget_manager:settings:group:force_tool_widgets' => "Enforce group tool widgets", 66 | 'widget_manager:settings:group:force_tool_widgets:confirm' => "Are you sure? This will add/remove all widgets specific to a tool option for all groups (where widget management is enabled).", 67 | 'widget_manager:settings:group:group_column_count' => "Number of widget columns in a group", 68 | 69 | 'widget_manager:settings:extra_contexts:description' => "Enter the page handler name of the new page which will get a layout similar to the index page. You can add as much pages as you need. Be sure not to add a page handler that is already in use. You can also configure the column layout for that page and optionally assign non-admin users as manager of the page.", 70 | 'widget_manager:settings:extra_contexts:page' => "Page", 71 | 'widget_manager:settings:extra_contexts:layout' => "Layout", 72 | 'widget_manager:settings:extra_contexts:layout:help' => "Set the number of columns", 73 | 'widget_manager:settings:extra_contexts:manager' => "Managers", 74 | 'widget_manager:settings:extra_contexts:manager:help' => "Configure the users that are allowed to manage the page", 75 | 76 | 'widget_manager:settings:lazy_loading:enabled' => "Enable Lazy Loading", 77 | 'widget_manager:settings:lazy_loading:enabled:help' => "Globally enables the lazy loading feature", 78 | 'widget_manager:settings:lazy_loading:lazy_loading_mobile_columns' => "Lazy Load columns on mobile", 79 | 'widget_manager:settings:lazy_loading:lazy_loading_mobile_columns:help' => "If on a mobile device the second and third column in a widget layout will always lazy load", 80 | 'widget_manager:settings:lazy_loading:lazy_loading_under_fold' => "Under the fold threshold", 81 | 'widget_manager:settings:lazy_loading:lazy_loading_under_fold:help' => "Widgets in the same column will lazy load if they are after this threshold", 82 | 83 | // views 84 | // settings 85 | 'widget_manager:forms:manage_widgets:no_widgets' => "No widgets to manage", 86 | 'widget_manager:forms:manage_widgets:context' => 'Available in the context', 87 | 'widget_manager:forms:manage_widgets:can_add' => "Can be added", 88 | 'widget_manager:forms:manage_widgets:multiple' => "Multiple widgets allowed", 89 | 'widget_manager:forms:manage_widgets:always_lazy_load' => "Always lazy load", 90 | 'widget_manager:forms:manage_widgets:non_default' => "This setting is different from the default setting", 91 | 'widget_manager:forms:manage_widgets:unsupported_context:confirm' => "Are you sure you wish to enable this widget for this context? If the widget does not support the context this could cause issues.", 92 | 93 | // groups widget access 94 | 'widget_manager:forms:groups_widget_access:title' => "Widget Access", 95 | 'widget_manager:forms:groups_widget_access:description' => "This action allows you to update the access level of all widgets in this group to the given access level.", 96 | 97 | 'widget_manager:widget_page:title:help' => "Setting a title, will display a title on the widget page. This is optional.", 98 | 'widget_manager:widget_page:show_description' => "Show description on the page", 99 | 'widget_manager:widget_page:show_description:help' => "If enabled the description will be shown on the widget page. If disabled you will probably only see it in listings like in search.", 100 | 101 | // actions 102 | // manage 103 | 'widget_manager:action:manage:success' => "Widget configuration saved successfully", 104 | 105 | // force tool widgets 106 | 'widget_manager:action:force_tool_widgets:error:not_enabled' => "Widget management for groups is not enabled", 107 | 'widget_manager:action:force_tool_widgets:success' => "Enforced tool specific widgets for %s groups", 108 | 109 | // groups update widget access 110 | 'widget_manager:action:groups:update_widget_access:success' => "Access to all widgets in this group is updated", 111 | 112 | // widgets 113 | 'widget_manager:widgets:edit:advanced' => "Advanced", 114 | 'widget_manager:layout:content:hide' => "Hide widget content", 115 | 'widget_manager:layout:content:show' => "Show widget content", 116 | 117 | ]; 118 | -------------------------------------------------------------------------------- /languages/nl.php: -------------------------------------------------------------------------------- 1 | 'Fluïde layout', 9 | 'widget_manager:settings:other' => 'Overig', 10 | 'widget_manager:settings:show_collapse_content' => 'Toon widget content verbergen optie bij bewerken layout', 11 | 'widget_manager:settings:show_collapse_content:help' => 'Indien geactiveerd toont er naast de \'Widget toevoegen\' knop een optie om de inhoud van widgets te verbergen. Dit kan helpen bij het organiseren van de widget layout.', 12 | 'widget_manager:layout:content:hide' => 'Verberg widget inhoud', 13 | 'widget_manager:layout:content:show' => 'Toon widget inhoud', 14 | 'widget_manager:settings:lazy_loading' => 'Lazy Loading', 15 | 'widget_manager:widgets:edit:lazy_load_content' => 'Laad widget content vertraagd in', 16 | 'widget_manager:settings:lazy_loading:enabled' => 'Lazy Loading activeren', 17 | 'widget_manager:settings:lazy_loading:enabled:help' => 'Activeert de functionaliteit om widget content vertraagd in te laden', 18 | 'widget_manager:settings:lazy_loading:lazy_loading_mobile_columns' => 'Kolommen vertraagd laden op mobiele devices', 19 | 'widget_manager:settings:lazy_loading:lazy_loading_mobile_columns:help' => 'Widgets in de 2e en 3e kolom zullen altijd vertraagd inladen op mobiele devices', 20 | 'widget_manager:settings:lazy_loading:lazy_loading_under_fold' => 'Onder de vouw limiet', 21 | 'widget_manager:forms:manage_widgets:always_lazy_load' => 'Altijd vertraagd laden', 22 | 'widget_manager:settings:lazy_loading:lazy_loading_under_fold:help' => 'Widgets in dezelfde kolom zullen vertraagd inladen indien gepositioneerd na deze waarde', 23 | 'item:object:widget_page' => 'Widget Pagina', 24 | 'collection:object:widget_page' => 'Widget Pagina\'s', 25 | 'admin:widgets:cleanup' => 'Opschonen', 26 | 'admin:widgets:pages' => 'Widget Pagina\'s', 27 | 'admin:widgets:cleanup:info' => 'Hier kun je alle widgets opruimen die zich op dit moment in de database bevinden, ook diegene die niet meer beschikbaar zijn.', 28 | 'widget_manager:settings:index_managers' => 'Voorpagina beheerders', 29 | 'widget_manager:widget_page:title:help' => 'Het optioneel opslaan van een titel toont deze op de widget pagina.', 30 | 'widget_manager:widget_page:show_description' => "Toon omschrijving op widget pagina", 31 | 'widget_manager:widget_page:show_description:help' => "Toont de omschrijving op de widget indien geactiveerd. Als het niet is geactiveerd zie je de omschrijving mogelijk enkel in lijsten en zoekresultaten.", 32 | 'widget_manager:forms:manage_widgets:context' => 'Beschikbaar in de context', 33 | 'widget_manager:forms:manage_widgets:can_add' => 'Kan worden toegevoegd', 34 | 'widget_manager:forms:manage_widgets:multiple' => 'Meerdere widgets toegestaan', 35 | 'widget_manager:forms:manage_widgets:non_default' => 'Deze instelling wijkt af van de standaard instelling', 36 | 'widget_manager:forms:manage_widgets:unsupported_context:confirm' => 'Weet je zeker dat je deze widget voor deze context wilt activeren? Indien de widget dit niet ondersteunt kan dat voor problemen zorgen.', 37 | 'widget_manager:forms:groups_widget_access:title' => 'Widget Zichtbaarheid', 38 | 'widget_manager:forms:groups_widget_access:description' => 'Deze actie geeft je de mogelijkheid om de zichtbaarheid van alle widgets in deze groep aan te passen naar het opgegeven toegangsniveau.', 39 | 'widget_manager:action:groups:update_widget_access:success' => 'Zichtbaarheid van alle widgets in deze groep is bijgewerkt', 40 | 'widget_manager:widgets:edit:custom_more_title' => 'Aangepaste lees meer tekst', 41 | 'widget_manager:widgets:edit:custom_more_url' => 'Aangepaste lees meer link', 42 | 'widget_manager:settings:group:enable:yes' => 'Ja, beheerbaar met groep tool optie', 43 | 'widget_manager:settings:group:enable:forced' => 'Ja, altijd aan', 44 | 'widget_manager:settings:group:force_tool_widgets' => 'Forceer groep tool widgets', 45 | 'widget_manager:settings:group:force_tool_widgets:confirm' => 'Weet je het zeker? Dit zal widgets toevoegen/verwijderen die beheerd worden door een groep tool optie (voor de groepen waar widget management is ingeschakeld)', 46 | 'widget_manager:settings:extra_contexts:page' => 'Pagina', 47 | 'widget_manager:settings:extra_contexts:layout' => 'Layout', 48 | 'widget_manager:settings:extra_contexts:layout:help' => 'Bepaald het aantal kolommen', 49 | 'widget_manager:settings:extra_contexts:manager' => 'Beheerders', 50 | 'widget_manager:settings:extra_contexts:manager:help' => 'Gebruikers die deze pagina kunnen beheren', 51 | 'widget_manager:action:force_tool_widgets:error:not_enabled' => 'Widget beheer voor groepen is niet ingeschakeld', 52 | 'widget_manager:action:force_tool_widgets:success' => 'Widgets voor groep tool geforceerd bij %s groepen', 53 | 'widget_manager:settings:extra_contexts:description' => 'Voer de url in van de nieuwe pagina welke een layout gelijk de widget manager startpagina krijgt. U kunt zoveel pagina\'s toevoegen als nodig is. Vergewis uzelve dat opgegeven pagina nog niet in gebruik is. Alle widgets die normaliter op de voorpagina beschikbaar zijn, zullen ook op deze nieuwe pagina beschikbaar komen. U kunt ook de kolom indeling kiezen en optioneel gewone gebruikers als manager van deze pagina toewijzen.', 54 | 'admin:widgets' => 'Widgets', 55 | 'admin:widgets:manage' => 'Beheren', 56 | 'admin:widgets:manage:index' => 'Beheer startpagina', 57 | 'admin:statistics:widgets' => 'Widget gebruik', 58 | 'widget_manager:widgets:edit:custom_url' => 'Aangepaste titel link', 59 | 'widget_manager:widgets:edit:custom_class' => 'Aangepaste CSS class', 60 | 'widget_manager:groups:enable_widget_manager' => 'Schakelen het beheer van widgets in', 61 | 'widget_manager:settings:index' => 'Startpagina', 62 | 'widget_manager:settings:group' => 'Groep', 63 | 'widget_manager:settings:group:enable' => 'Schakel Widget Manager in voor groepen', 64 | 'widget_manager:settings:group:option_default_enabled' => 'Widgets beheer voor groepen standaard ingeschakeld', 65 | 'widget_manager:settings:group:option_admin_only' => 'Alleen beheerders kunnen groep widgets inschakelen', 66 | 'widget_manager:settings:group:group_column_count' => 'Aantal widget kolommen in een groep', 67 | 'widget_manager:forms:manage_widgets:no_widgets' => 'Geen widgets om te beheren', 68 | 'widget_manager:widgets:edit:advanced' => 'Geavanceerd', 69 | 'widget_manager:widgets:edit:disable_widget_content_style' => 'Verwijder widget stijl', 70 | 'widget_manager:widgets:edit:custom_title' => 'Eigen titel', 71 | 'widget_manager:widgets:edit:hide_header' => 'Verberg titel', 72 | 'widget_manager:settings:custom_index' => 'Gebruik Widget Manager aangepaste index?', 73 | 'widget_manager:settings:custom_index:non_loggedin' => 'Alleen voor niet aangemelde gebruikers', 74 | 'widget_manager:settings:custom_index:loggedin' => 'Alleen voor aangemelde gebruikers', 75 | 'widget_manager:settings:custom_index:all' => 'Voor alle gebruikers', 76 | 'widget_manager:settings:widget_layout' => 'Kies een widget indeling', 77 | 'widget_manager:settings:widget_layout:33|33|33' => 'Standaard indeling (33% per kolom)', 78 | 'widget_manager:settings:widget_layout:50|25|25' => 'Brede linker kolom (50%, 25%, 25%)', 79 | 'widget_manager:settings:widget_layout:25|50|25' => 'Brede middelste kolom (25%, 50%, 25%)', 80 | 'widget_manager:settings:widget_layout:25|25|50' => 'Brede rechter kolom (25%, 25%, 50%)', 81 | 'widget_manager:settings:widget_layout:75|25' => 'Twee kolommen (75%, 25%)', 82 | 'widget_manager:settings:widget_layout:60|40' => 'Twee kolommen (60%, 40%)', 83 | 'widget_manager:settings:widget_layout:50|50' => 'Twee kolommen (50%, 50%)', 84 | 'widget_manager:settings:widget_layout:40|60' => 'Twee kolommen (40%, 60%)', 85 | 'widget_manager:settings:widget_layout:25|75' => 'Twee kolommen (25%, 75%)', 86 | 'widget_manager:settings:widget_layout:100' => 'Enkele kolom (100%)', 87 | 'widget_manager:action:manage:success' => 'Widget configuratie succesvol opgeslagen', 88 | ); 89 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/Access.php: -------------------------------------------------------------------------------- 1 | getParam('input_params', []); 22 | if (elgg_extract('entity_type', $input_params) !== 'object' || elgg_extract('entity_subtype', $input_params) !== 'widget') { 23 | return null; 24 | } 25 | 26 | $container = get_entity(elgg_extract('container_guid', $input_params)); 27 | if (!$container instanceof \ElggEntity) { 28 | return null; 29 | } 30 | 31 | if ($container instanceof \ElggGroup) { 32 | $acl = $container->getOwnedAccessCollection('group_acl'); 33 | if ($acl) { 34 | return [ 35 | $acl->id => elgg_echo('groups:access:group'), 36 | ACCESS_LOGGED_IN => elgg_echo('access:label:logged_in'), 37 | ACCESS_PUBLIC => elgg_echo('access:label:public'), 38 | ]; 39 | } 40 | } elseif ($container instanceof \ElggSite) { 41 | // special options for index widgets 42 | 43 | $widget = elgg_extract('entity', $input_params); 44 | if (!$widget instanceof \ElggWidget) { 45 | return null; 46 | } 47 | 48 | if (elgg_can_edit_widget_layout($widget->context)) { 49 | return [ 50 | ACCESS_PRIVATE => elgg_echo('access:admin_only'), 51 | ACCESS_LOGGED_IN => elgg_echo('access:label:logged_in'), 52 | ACCESS_LOGGED_OUT => elgg_echo('access:label:logged_out'), 53 | ACCESS_PUBLIC => elgg_echo('access:label:public'), 54 | ]; 55 | } 56 | } 57 | 58 | return null; 59 | } 60 | 61 | /** 62 | * Allow write access for index managers 63 | * 64 | * @param \Elgg\Event $event 'permissions_check', 'site' 65 | * 66 | * @return null|bool 67 | */ 68 | public static function writeAccessForIndexManagers(\Elgg\Event $event): ?bool { 69 | $result = $event->getValue(); 70 | if ($result) { 71 | return $result; 72 | } 73 | 74 | $entity = $event->getEntityParam(); 75 | if (!$entity instanceof \ElggSite) { 76 | return null; 77 | } 78 | 79 | $user = $event->getUserParam(); 80 | if (!$user instanceof \ElggUser) { 81 | return null; 82 | } 83 | 84 | $index_managers = explode(',', elgg_get_plugin_setting('index_managers', 'widget_manager', '')); 85 | return in_array($user->guid, $index_managers) ?: null; 86 | } 87 | 88 | /** 89 | * Creates the ability to see content only for logged_out users 90 | * 91 | * @param \Elgg\Event $event 'access:collections:read', 'user' 92 | * 93 | * @return null|array 94 | */ 95 | public static function addLoggedOutReadAccess(\Elgg\Event $event): ?array { 96 | 97 | if (elgg_is_logged_in() && !elgg_is_admin_logged_in()) { 98 | return null; 99 | } 100 | 101 | $return_value = $event->getValue() ?: []; 102 | 103 | if (!is_array($return_value)) { 104 | $return_value = [$return_value]; 105 | } 106 | 107 | $return_value[] = ACCESS_LOGGED_OUT; 108 | 109 | return $return_value; 110 | } 111 | 112 | /** 113 | * Checks if current user can edit a widget if it is in a context he/she can manage 114 | * 115 | * @param \Elgg\Event $event 'permissions_check', 'object' 116 | * 117 | * @return null|bool 118 | */ 119 | public static function canEditWidgetOnManagedLayout(\Elgg\Event $event): ?bool { 120 | $user = $event->getUserParam(); 121 | $entity = $event->getEntityParam(); 122 | 123 | if ($event->getValue() || !$user instanceof \ElggUser || !$entity instanceof \ElggWidget) { 124 | return null; 125 | } 126 | 127 | if (!$entity->getOwnerEntity() instanceof \ElggSite) { 128 | // special permission is only for widget owned by site 129 | return null; 130 | } 131 | 132 | $context = $entity->context; 133 | return $context ? elgg_can_edit_widget_layout($context, $user->guid) : null; 134 | } 135 | 136 | /** 137 | * Registers the extra context permissions check event 138 | * 139 | * @param \Elgg\Event $event 'action:validate', 'widgets/[add|delete|move|save]' 140 | * 141 | * @return void 142 | */ 143 | public static function moreRightsForWidgetManager(\Elgg\Event $event): void { 144 | if ($event->getType() === 'widgets/add') { 145 | elgg_register_event_handler('permissions_check', 'site', '\ColdTrick\WidgetManager\Access::writeAccessForIndexManagers'); 146 | return; 147 | } 148 | 149 | $widget_guid = (int) get_input('widget_guid', get_input('guid')); 150 | if (empty($widget_guid)) { 151 | return; 152 | } 153 | 154 | $widget = elgg_call(ELGG_IGNORE_ACCESS, function() use ($widget_guid) { 155 | return get_entity($widget_guid); 156 | }); 157 | 158 | if (!$widget instanceof \ElggWidget) { 159 | return; 160 | } 161 | 162 | if ($widget->canEdit()) { 163 | // the widgets action might not be able to get privately owned index widgets 164 | self::registerSQLBypass($widget_guid); 165 | } 166 | 167 | if ($event->getType() === 'widgets/move') { 168 | // allow 'index' widgets to be added to the same context as the current widget 169 | $widget_context = $widget->context; 170 | 171 | $index_widgets = elgg_get_widget_types('index'); 172 | 173 | foreach ($index_widgets as $handler => $index_widget) { 174 | $contexts = $index_widget->context; 175 | $contexts[] = $widget_context; 176 | elgg_register_widget_type([ 177 | 'id' => $handler, 178 | 'name' => $index_widget->name, 179 | 'description' => $index_widget->description, 180 | 'context' => $contexts, 181 | 'multiple' => $index_widget->multiple, 182 | ]); 183 | } 184 | } 185 | } 186 | 187 | /** 188 | * Registers a bypass sql suffix 189 | * 190 | * @param int $guid GUID of the entity to register sql bypass for 191 | * 192 | * @return void 193 | */ 194 | protected static function registerSQLBypass(int $guid): void { 195 | elgg_register_event_handler('get_sql', 'access', function(\Elgg\Event $event) use ($guid) { 196 | if ($event->getParam('ignore_access')) { 197 | // no need to give extra access 198 | return null; 199 | } 200 | 201 | /** 202 | * @var QueryBuilder $qb 203 | */ 204 | $qb = $event->getParam('query_builder'); 205 | $table_alias = $event->getParam('table_alias'); 206 | $guid_column = $event->getParam('guid_column'); 207 | 208 | $alias = function ($column) use ($table_alias) { 209 | return $table_alias ? "{$table_alias}.{$column}" : $column; 210 | }; 211 | 212 | $result = $event->getValue(); 213 | 214 | $result['ors']['special_widget_access'] = $qb->compare($alias($guid_column), '=', $guid); 215 | 216 | return $result; 217 | }); 218 | } 219 | 220 | /** 221 | * Only allow widget page delete by admins 222 | * 223 | * @param \Elgg\Event $event 'permissions_check:delete', 'object' 224 | * 225 | * @return boolean 226 | */ 227 | public static function onlyAdminsCanDeleteWidgetPages(\Elgg\Event $event) { 228 | $user = $event->getUserParam(); 229 | $entity = $event->getEntityParam(); 230 | 231 | if (!$user instanceof \ElggUser || !$entity instanceof \WidgetPage) { 232 | return; 233 | } 234 | 235 | return $user->isAdmin(); 236 | } 237 | 238 | /** 239 | * Only allow widget edit for private widgets 240 | * 241 | * @param \Elgg\Event $event 'view_vars', 'object/widget/edit' 242 | * 243 | * @return null|array 244 | */ 245 | public static function allowPrivateWidgetEdit(\Elgg\Event $event): ?array { 246 | $result = $event->getValue(); 247 | if (elgg_extract('entity', $result) instanceof \ElggEntity) { 248 | return $result; 249 | } 250 | 251 | $guid = (int) elgg_extract('guid', $result); 252 | $entity = elgg_call(ELGG_IGNORE_ACCESS, function() use ($guid) { 253 | return get_entity($guid); 254 | }); 255 | 256 | if ($entity->canEdit()) { 257 | $result['entity'] = $entity; 258 | self::registerSQLBypass($guid); 259 | } 260 | 261 | return $result; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/Widgets.php: -------------------------------------------------------------------------------- 1 | getObject(); 21 | if (!$object instanceof \ElggWidget) { 22 | return; 23 | } 24 | 25 | if ((int) $object->access_id !== ACCESS_PRIVATE) { 26 | return; 27 | } 28 | 29 | // Updates access for privately created widgets in a group or on site 30 | elgg_call(ELGG_IGNORE_ACCESS, function() use ($object) { 31 | $owner = $object->getOwnerEntity(); 32 | 33 | if ($owner instanceof \ElggGroup) { 34 | $object->access_id = $owner->getOwnedAccessCollection('group_acl')->id; 35 | $object->save(); 36 | } elseif ($owner instanceof \ElggSite) { 37 | $object->access_id = ACCESS_PUBLIC; 38 | $object->save(); 39 | } 40 | }); 41 | } 42 | 43 | /** 44 | * Applies the saved widgets config 45 | * 46 | * @param \Elgg\Event $event 'handlers', 'widgets' 47 | * 48 | * @return array 49 | */ 50 | public static function applyWidgetsConfig(\Elgg\Event $event): array { 51 | $return_value = $event->getValue(); 52 | foreach ($return_value as $id => $widget_definition) { 53 | if (!isset($widget_definition->originals)) { 54 | $widget_definition->originals = [ 55 | 'multiple' => $widget_definition->multiple, 56 | 'context' => $widget_definition->context, 57 | ]; 58 | } 59 | 60 | $widget_config = WidgetsSettingsConfig::instance()->getAll($widget_definition->id); 61 | if (empty($widget_config)) { 62 | continue; 63 | } 64 | 65 | // fix multiple 66 | if (isset($widget_config['multiple'])) { 67 | $widget_definition->multiple = (bool) elgg_extract('multiple', $widget_config); 68 | } 69 | 70 | // fix contexts 71 | $contexts = elgg_extract('contexts', $widget_config); 72 | if (empty($contexts)) { 73 | continue; 74 | } 75 | 76 | foreach ($contexts as $context => $context_config) { 77 | if (!isset($context_config['enabled'])) { 78 | continue; 79 | } 80 | 81 | $enabled = elgg_extract('enabled', $context_config); 82 | $existing_key = array_search($context, $widget_definition->context); 83 | if ($existing_key !== false) { 84 | // already existing in default contexts 85 | if (!$enabled) { 86 | // remove if disabled in config 87 | unset($widget_definition->context[$existing_key]); 88 | } 89 | } elseif ($enabled) { 90 | // add if not existing 91 | $widget_definition->context[] = $context; 92 | } 93 | } 94 | 95 | $return_value[$id] = $widget_definition; 96 | } 97 | 98 | return $return_value; 99 | } 100 | 101 | /** 102 | * Adds manage_widgets context so the widgets always show up in admin/manage/widgets 103 | * 104 | * @param \Elgg\Event $event 'handlers', 'widgets' 105 | * 106 | * @return array 107 | */ 108 | public static function addManageWidgetsContext(\Elgg\Event $event): array { 109 | $return_value = $event->getValue(); 110 | foreach ($return_value as $id => $widget_definition) { 111 | $widget_definition->context[] = 'manage_widgets'; 112 | $return_value[$id] = $widget_definition; 113 | } 114 | 115 | return $return_value; 116 | } 117 | 118 | /** 119 | * Returns widget content from cache 120 | * 121 | * @param \Elgg\Event $event 'view_vars', 'object/widget/body' 122 | * 123 | * @return null|array 124 | */ 125 | public static function getContentFromCache(\Elgg\Event $event): ?array { 126 | $widget = elgg_extract('entity', $event->getValue()); 127 | if (!$widget instanceof \ElggWidget) { 128 | return null; 129 | } 130 | 131 | if (!self::isCacheableWidget($widget)) { 132 | return null; 133 | } 134 | 135 | $current_language = elgg_get_current_language(); 136 | $cached_data = elgg_load_system_cache("widget_cache_{$widget->guid}_{$current_language}"); 137 | if (empty($cached_data)) { 138 | return null; 139 | } 140 | 141 | $result = $event->getValue(); 142 | $result[\Elgg\ViewsService::OUTPUT_KEY] = $cached_data; 143 | 144 | return $result; 145 | } 146 | 147 | /** 148 | * Prevent widget controls 149 | * 150 | * @param \Elgg\Event $event 'view_vars', 'object/widget/elements/controls' 151 | * 152 | * @return null|array 153 | */ 154 | public static function preventControls(\Elgg\Event $event): ?array { 155 | $widget = elgg_extract('widget', $event->getValue()); 156 | if (!$widget instanceof \ElggWidget) { 157 | return null; 158 | } 159 | 160 | if ($widget->widget_manager_hide_header !== 'yes') { 161 | return null; 162 | } 163 | 164 | if ($widget->canEdit()) { 165 | return null; 166 | } 167 | 168 | $result = $event->getValue(); 169 | $result[\Elgg\ViewsService::OUTPUT_KEY] = ''; 170 | 171 | return $result; 172 | } 173 | 174 | /** 175 | * Returns widget content from cache 176 | * 177 | * @param \Elgg\Event $event 'view', 'object/widget/body' 178 | * 179 | * @return void 180 | */ 181 | public static function saveContentInCache(\Elgg\Event $event): void { 182 | $widget = elgg_extract('entity', elgg_extract('vars', $event->getParams())); 183 | if (!$widget instanceof \ElggWidget) { 184 | return; 185 | } 186 | 187 | if (!self::isCacheableWidget($widget)) { 188 | return; 189 | } 190 | 191 | $current_language = elgg_get_current_language(); 192 | elgg_save_system_cache("widget_cache_{$widget->guid}_{$current_language}", $event->getValue()); 193 | } 194 | 195 | /** 196 | * Checks if the provide widget is registered as a cacheable widget 197 | * 198 | * @param \ElggWidget $widget widget to check 199 | * 200 | * @return bool 201 | */ 202 | public static function isCacheableWidget(\ElggWidget $widget): bool { 203 | static $cacheable_handlers; 204 | if (!isset($cacheable_handlers)) { 205 | $cacheable_handlers = elgg_trigger_event_results('cacheable_handlers', 'widget_manager', [], []); 206 | } 207 | 208 | return in_array($widget->handler, $cacheable_handlers); 209 | } 210 | 211 | /** 212 | * Fallback widget title urls for non widget manager widgets 213 | * 214 | * @param \Elgg\Event $event 'entity:url', 'object' 215 | * 216 | * @return null|string 217 | */ 218 | public static function getWidgetURL(\Elgg\Event $event): ?string { 219 | $widget = $event->getEntityParam(); 220 | if (!$widget instanceof \ElggWidget) { 221 | return null; 222 | } 223 | 224 | // custom urls always trump existing values 225 | return $widget->widget_manager_custom_url ?: null; 226 | } 227 | 228 | /** 229 | * Register the discussions widget to groups context 230 | * 231 | * @param \Elgg\Event $event 'handlers', 'widgets' 232 | * 233 | * @return null|\Elgg\WidgetDefinition[] 234 | */ 235 | public static function addDiscussionsWidgetToGroup(\Elgg\Event $event): ?array { 236 | 237 | $context = $event->getParam('context'); 238 | if ($context !== 'groups') { 239 | return null; 240 | } 241 | 242 | $container = $event->getParam('container'); 243 | if (!$container instanceof \ElggGroup || !$container->isToolEnabled('forum')) { 244 | return null; 245 | } 246 | 247 | $return_value = $event->getValue(); 248 | 249 | $return_value[] = WidgetDefinition::factory([ 250 | 'id' => 'discussions', 251 | 'context' => ['groups'], 252 | ]); 253 | 254 | return $return_value; 255 | } 256 | 257 | /** 258 | * Add or remove widgets based on the group tool option 259 | * 260 | * @param \Elgg\Event $event 'group_tool_widgets', 'widget_manager' 261 | * 262 | * @return null|array 263 | */ 264 | public static function groupToolWidgets(\Elgg\Event $event): ?array { 265 | 266 | $entity = $event->getEntityParam(); 267 | if (!$entity instanceof \ElggGroup) { 268 | return null; 269 | } 270 | 271 | $return_value = $event->getValue(); 272 | if (!is_array($return_value)) { 273 | return null; 274 | } 275 | 276 | // check different group tools for which we supply widgets 277 | if ($entity->isToolEnabled('forum')) { 278 | $return_value['enable'][] = 'discussions'; 279 | } else { 280 | $return_value['disable'][] = 'discussions'; 281 | } 282 | 283 | return $return_value; 284 | } 285 | 286 | /** 287 | * Checks if a user can manage current widget layout 288 | * 289 | * @param \Elgg\Event $event 'permissions_check', 'widget_layout' 290 | * 291 | * @return null|bool 292 | */ 293 | public static function layoutPermissionsCheck(\Elgg\Event $event): ?bool { 294 | $user = $event->getUserParam(); 295 | if (!$user instanceof \ElggUser || $event->getValue()) { 296 | return null; 297 | } 298 | 299 | // check if widgetpage manager can manage 300 | $page_owner = $event->getParam('page_owner'); 301 | if ($page_owner instanceof \WidgetPage) { 302 | if ($page_owner->canEdit()) { 303 | return true; 304 | } 305 | 306 | return null; 307 | } 308 | 309 | // check if it is an index manager 310 | if ($event->getParam('context') !== 'index') { 311 | return null; 312 | } 313 | 314 | $index_managers = explode(',', elgg_get_plugin_setting('index_managers', 'widget_manager', '')); 315 | return in_array($user->guid, $index_managers) ?: null; 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /classes/ColdTrick/WidgetManager/Groups.php: -------------------------------------------------------------------------------- 1 | getValue(); 31 | 32 | if (!is_array($return_value)) { 33 | $return_value = []; 34 | } 35 | 36 | $return_value[] = [ 37 | 'name' => elgg_echo('groups'), 38 | 'widget_context' => 'groups', 39 | 'widget_columns' => 2, 40 | 'event' => 'create', 41 | 'entity_type' => 'group', 42 | 'entity_subtype' => null, 43 | ]; 44 | 45 | return $return_value; 46 | } 47 | 48 | /** 49 | * Sets the widget manager tool option. This is needed because in some situation the tool option is not available. 50 | * 51 | * And add/remove tool enabled widgets 52 | * 53 | * @param \Elgg\Event $event 'update', 'group' 54 | * 55 | * @return void 56 | */ 57 | public static function updateGroupWidgets(\Elgg\Event $event): void { 58 | $object = $event->getObject(); 59 | if (!$object instanceof \ElggGroup || !elgg_is_active_plugin('groups')) { 60 | return; 61 | } 62 | 63 | $plugin_settings = elgg_get_plugin_setting('group_enable', 'widget_manager'); 64 | if (!in_array($plugin_settings, ['yes', 'forced'])) { 65 | return; 66 | } 67 | 68 | if ($plugin_settings === 'forced') { 69 | // make widget management mandatory 70 | $object->widget_manager_enable = 'yes'; 71 | } elseif ($object->widget_manager_enable !== 'yes') { 72 | // if optional but not enabled for this group 73 | return; 74 | } 75 | 76 | // add/remove tool enabled widgets 77 | $result = ['enable' => [], 'disable' => []]; 78 | $params = ['entity' => $object]; 79 | $result = (array) elgg_trigger_event_results('group_tool_widgets', 'widget_manager', $params, $result); 80 | 81 | if (empty($result)) { 82 | return; 83 | } 84 | 85 | // push an extra context for others to know what's going on 86 | elgg_push_context('widget_manager_group_tool_widgets'); 87 | 88 | $current_widgets = elgg_get_widgets($object->guid, 'groups'); 89 | 90 | // disable widgets 91 | $disable_widget_handlers = (array) elgg_extract('disable', $result, []); 92 | if (!empty($disable_widget_handlers) && !empty($current_widgets)) { 93 | foreach ($current_widgets as $column => $widgets) { 94 | if (!is_array($widgets) || empty($widgets)) { 95 | continue; 96 | } 97 | 98 | foreach ($widgets as $order => $widget) { 99 | // check if a widget should be removed 100 | if (!in_array($widget->handler, $disable_widget_handlers)) { 101 | continue; 102 | } 103 | 104 | // yes, so remove the widget 105 | $widget->delete(); 106 | 107 | unset($current_widgets[$column][$order]); 108 | } 109 | } 110 | } 111 | 112 | // enable widgets 113 | $enable_widget_handlers = (array) elgg_extract('enable', $result, []); 114 | if (empty($enable_widget_handlers)) { 115 | // remove additional context 116 | elgg_pop_context(); 117 | 118 | return; 119 | } 120 | 121 | $column_counts = []; 122 | $max_columns = elgg_trigger_event_results('groups:column_count', 'widget_manager', [], elgg_get_plugin_setting('group_column_count', 'widget_manager')); 123 | for ($i = 1; $i <= $max_columns; $i++) { 124 | $column_counts[$i] = 0; 125 | } 126 | 127 | // ignore access restrictions 128 | // because if a group is created with a visibility of only group members 129 | // the group owner is not yet added to the acl and thus can't edit the newly created widgets 130 | elgg_call(ELGG_IGNORE_ACCESS, function() use ($object, $current_widgets, $enable_widget_handlers, $column_counts) { 131 | if (!empty($current_widgets) && is_array($current_widgets)) { 132 | foreach ($current_widgets as $column => $widgets) { 133 | // count for later balancing 134 | $column_counts[$column] = count($widgets); 135 | 136 | if (empty($widgets) || !is_array($widgets)) { 137 | continue; 138 | } 139 | 140 | foreach ($widgets as $order => $widget) { 141 | // check if a widget which should be enabled isn't already enabled 142 | $enable_index = array_search($widget->handler, $enable_widget_handlers); 143 | if ($enable_index !== false) { 144 | // already enabled, do add duplicate 145 | unset($enable_widget_handlers[$enable_index]); 146 | } 147 | } 148 | } 149 | } 150 | 151 | $determine_target_column = function($column_counts) { 152 | 153 | $current_target = 1; 154 | $current_min = elgg_extract($current_target, $column_counts, 0); 155 | 156 | foreach ($column_counts as $column => $column_count) { 157 | if ($column_count < $current_min) { 158 | $current_target = $column; 159 | $current_min = $column_count; 160 | } 161 | } 162 | 163 | return $current_target; 164 | }; 165 | 166 | // check blacklist 167 | $blacklist = $object->widget_manager_widget_blacklist; 168 | if (!empty($blacklist)) { 169 | $blacklist = json_decode($blacklist, true); 170 | foreach ($blacklist as $handler) { 171 | $enable_index = array_search($handler, $enable_widget_handlers); 172 | if ($enable_index === false) { 173 | // blacklisted item wasn't going to be added 174 | continue; 175 | } 176 | 177 | // widget was removed manually, don't add it automagically 178 | unset($enable_widget_handlers[$enable_index]); 179 | } 180 | } 181 | 182 | // add new widgets 183 | if (!empty($enable_widget_handlers)) { 184 | $widget_access_id = $object->access_id; 185 | if ($widget_access_id === ACCESS_PUBLIC && elgg_get_config('walled_garden')) { 186 | $widget_access_id = ACCESS_LOGGED_IN; 187 | } 188 | 189 | foreach ($enable_widget_handlers as $handler) { 190 | $widget_guid = elgg_create_widget($object->guid, $handler, 'groups', $widget_access_id); 191 | if (empty($widget_guid)) { 192 | continue; 193 | } 194 | 195 | $widget = get_entity($widget_guid); 196 | 197 | $target_column = $determine_target_column($column_counts); 198 | 199 | // move to the end of the target column 200 | $widget->move($target_column, 9000); 201 | $column_counts[$target_column]++; 202 | } 203 | } 204 | }); 205 | 206 | // remove additional context 207 | elgg_pop_context(); 208 | } 209 | 210 | /** 211 | * Bypasses the widgets content on the group profile 212 | * 213 | * @param \Elgg\Event $event 'view_vars', 'groups/profile/widgets' 214 | * 215 | * @return null|array 216 | */ 217 | public static function getGroupWidgetsLayout(\Elgg\Event $event): ?array { 218 | $vars = $event->getValue(); 219 | $group = elgg_extract('entity', $vars); 220 | 221 | if (!$group instanceof \ElggGroup) { 222 | return null; 223 | } 224 | 225 | $group_enable = elgg_get_plugin_setting('group_enable', 'widget_manager'); 226 | if (!in_array($group_enable, ['forced', 'yes'])) { 227 | return null; 228 | } 229 | 230 | if ($group_enable === 'yes' && !$group->isToolEnabled('widget_manager')) { 231 | return null; 232 | } 233 | 234 | // need context = groups to fix the issue with the new group_profile context 235 | elgg_push_context('groups'); 236 | 237 | $num_columns = (int) elgg_extract('num_columns', $vars, elgg_get_plugin_setting('group_column_count', 'widget_manager')); 238 | 239 | $vars[\Elgg\ViewsService::OUTPUT_KEY] = elgg_view_layout('widgets', [ 240 | 'num_columns' => $num_columns, 241 | 'class' => [ 242 | "widgets-{$num_columns}-columns", 243 | ], 244 | ]); 245 | 246 | elgg_pop_context(); 247 | 248 | return $vars; 249 | } 250 | 251 | /** 252 | * Adds the group tool option 253 | * 254 | * @param \Elgg\Event $event 'tool_options', 'group' 255 | * 256 | * @return null|Collection 257 | */ 258 | public static function registerGroupWidgetsTool(\Elgg\Event $event): ?Collection { 259 | $plugin = elgg_get_plugin_from_id('widget_manager'); 260 | if ($plugin->getSetting('group_enable') !== 'yes') { 261 | return null; 262 | } 263 | 264 | $result = $event->getValue(); 265 | 266 | if ($plugin->getSetting('group_option_admin_only') !== 'yes' || elgg_is_admin_logged_in()) { 267 | // add the tool option for group admins 268 | $result[] = new Tool('widget_manager', [ 269 | 'label' => elgg_echo('widget_manager:groups:enable_widget_manager'), 270 | 'default_on' => $plugin->getSetting('group_option_default_enabled') === 'yes', 271 | ]); 272 | } 273 | 274 | return $result; 275 | } 276 | 277 | /** 278 | * Prepare for group widget blacklist update when adding a widget manually 279 | * 280 | * @param \Elgg\Event $event 'create', 'object' 281 | * 282 | * @return void 283 | */ 284 | public static function addGroupWidget(\Elgg\Event $event): void { 285 | $object = $event->getObject(); 286 | if (!$object instanceof \ElggWidget || elgg_in_context('widget_manager_group_tool_widgets') || !elgg_is_active_plugin('groups')) { 287 | return; 288 | } 289 | 290 | $group_enable = elgg_get_plugin_setting('group_enable', 'widget_manager'); 291 | if (!in_array($group_enable, ['yes', 'forced'])) { 292 | return; 293 | } 294 | 295 | $owner = $object->getOwnerEntity(); 296 | if (!$owner instanceof \ElggGroup) { 297 | // not a group widget 298 | return; 299 | } 300 | 301 | $blacklist = $owner->widget_manager_widget_blacklist; 302 | if (empty($blacklist)) { 303 | // no blacklisted widgets, so no cleanup needed 304 | return; 305 | } 306 | 307 | global $widget_manager_group_guids; 308 | if (!isset($widget_manager_group_guids)) { 309 | $widget_manager_group_guids = []; 310 | 311 | elgg_register_event_handler('shutdown', 'system', self::class . '::addGroupWidgetShutdown'); 312 | } 313 | 314 | $widget_manager_group_guids[$owner->guid][] = $object->guid; 315 | } 316 | 317 | /** 318 | * Update the group widget blacklist when adding a widget manually 319 | * 320 | * @param \Elgg\Event $event 'shutdown', 'system' 321 | * 322 | * @return void 323 | */ 324 | public static function addGroupWidgetShutdown(\Elgg\Event $event): void { 325 | global $widget_manager_group_guids; 326 | 327 | if (empty($widget_manager_group_guids)) { 328 | return; 329 | } 330 | 331 | elgg_call(ELGG_IGNORE_ACCESS, function() use ($widget_manager_group_guids) { 332 | 333 | foreach ($widget_manager_group_guids as $owner_guid => $widget_guids) { 334 | if (empty($widget_guids)) { 335 | continue; 336 | } 337 | 338 | $owner = get_entity($owner_guid); 339 | if (!$owner instanceof \ElggGroup) { 340 | continue; 341 | } 342 | 343 | $blacklist = $owner->widget_manager_widget_blacklist; 344 | if (empty($blacklist)) { 345 | // no blacklisted widgets, so no cleanup needed 346 | continue; 347 | } 348 | 349 | $blacklist = json_decode($blacklist, true); 350 | 351 | foreach ($widget_guids as $guid) { 352 | $widget = get_entity($guid); 353 | if (!$widget instanceof \ElggWidget) { 354 | continue; 355 | } 356 | 357 | if (!in_array($widget->handler, $blacklist)) { 358 | // not blacklisted, so no cleanup needed 359 | continue; 360 | } 361 | 362 | $key = array_search($widget->handler, $blacklist); 363 | unset($blacklist[$key]); 364 | } 365 | 366 | if (empty($blacklist)) { 367 | unset($owner->widget_manager_widget_blacklist); 368 | } else { 369 | $owner->widget_manager_widget_blacklist = json_encode($blacklist); 370 | } 371 | } 372 | }); 373 | } 374 | 375 | /** 376 | * Update the group widget blacklist when removing a widget manually 377 | * 378 | * @param \Elgg\Event $event 'delete', 'object' 379 | * 380 | * @return void 381 | */ 382 | public static function deleteGroupWidget(\Elgg\Event $event): void { 383 | $object = $event->getObject(); 384 | if (!$object instanceof \ElggWidget || elgg_in_context('widget_manager_group_tool_widgets') || !elgg_is_active_plugin('groups')) { 385 | return; 386 | } 387 | 388 | $group_enable = elgg_get_plugin_setting('group_enable', 'widget_manager'); 389 | if (!in_array($group_enable, ['yes', 'forced'])) { 390 | return; 391 | } 392 | 393 | $owner = $object->getOwnerEntity(); 394 | if (!$owner instanceof \ElggGroup) { 395 | // not a group widget 396 | return; 397 | } 398 | 399 | $handlers = elgg_call(ELGG_IGNORE_ACCESS, function() use ($owner, $object) { 400 | // get all group widgets 401 | $group_widgets = elgg_get_widgets($owner->guid, 'groups'); 402 | $handlers = []; 403 | if (!empty($group_widgets)) { 404 | foreach ($group_widgets as $column => $widgets) { 405 | if (empty($widgets)) { 406 | continue; 407 | } 408 | 409 | /* @var $widget \ElggWidget */ 410 | foreach ($widgets as $widget) { 411 | if ($widget->guid === $object->guid) { 412 | // don't add yourself 413 | continue; 414 | } 415 | 416 | $handlers[] = $widget->handler; 417 | } 418 | } 419 | 420 | $handlers = array_unique($handlers); 421 | } 422 | 423 | return $handlers; 424 | }); 425 | 426 | if (in_array($object->handler, $handlers)) { 427 | // not the last widget of it's type 428 | return; 429 | } 430 | 431 | $blacklist = $owner->widget_manager_widget_blacklist; 432 | if (!empty($blacklist)) { 433 | // blacklisted widgets 434 | $blacklist = json_decode($blacklist, true); 435 | } else { 436 | $blacklist = []; 437 | } 438 | 439 | if (in_array($object->handler, $blacklist)) { 440 | // already on the blacklist 441 | return; 442 | } 443 | 444 | $blacklist[] = $object->handler; 445 | 446 | // store new blacklist 447 | $owner->widget_manager_widget_blacklist = json_encode($blacklist); 448 | } 449 | } 450 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | Version history 2 | =============== 3 | 4 | 17.1.2 (2025-10-17): 5 | 6 | - fixed: PHP implicit null declaration 7 | - fixed: use correct return type in group tool event handler 8 | - fixed: widget managers should be able to manage private widgets 9 | 10 | 17.1.1 (2025-01-23): 11 | 12 | - fixed: use flex grow strategy for different column sizes 13 | 14 | 17.1 (2024-10-10): 15 | 16 | - added: widget cache now store cache per language 17 | - changed: invalidating widget cache using entity invalidateCache function 18 | - fixed: unable to create new widget page 19 | 20 | 16.2 (2024-08-07): 21 | 22 | - added: widget page manager can now edit page on the front end 23 | 24 | 17.0 (2024-06-28): 25 | 26 | - changed: updated for Elgg 6.0 27 | 28 | 16.1.1 (2024-06-05): 29 | 30 | - several small chores/fixes 31 | 32 | 16.1 (2024-04-03): 33 | 34 | - added: widget pages now can have a description and are searchable 35 | 36 | 16.0.2 (2024-03-14): 37 | 38 | - several small chores/fixes 39 | 40 | 16.0.1 (2024-01-12): 41 | 42 | - fixed: toggling widget body should not influence layout 43 | 44 | 16.0 (2023-11-30): 45 | 46 | - changed: updated for Elgg 5.1 47 | 48 | 15.1.1 (2023-07-26): 49 | 50 | - fixed: buttons have a discernible text 51 | 52 | 15.1 (2023-07-05): 53 | 54 | - added: widget page database seeder 55 | - fixed: return correct type 56 | 57 | 15.0 (2023-06-02): 58 | 59 | - added: migration for old widget settings 60 | - changed: conflict with Elgg < 5.0 61 | - changed: updated for Elgg 5 62 | - fixed: expand access to allow editing private widgets on index pages 63 | - fixed: prevent error in shutdown hook 64 | 65 | 14.1.1 (2022-10-27): 66 | 67 | - fixed: Dutch translation file encoding 68 | 69 | 14.1 (2022-10-07): 70 | 71 | - added: toggle link for widget layout editors to hide/show widget content 72 | - changed: fluid layout widget are lazy loaded after 3 * the fold limit 73 | - changed: widget page display name falls back to url 74 | - fixed: correctly pass show_access to newly added widgets 75 | 76 | 14.0 (2022-08-24): 77 | 78 | - changed: updated for Elgg 4.3 79 | 80 | 13.1 (2022-06-22): 81 | 82 | - removed: no longer provide widget toggle/collapse features 83 | 84 | 13.0 (2022-06-01): 85 | 86 | - added: discussions widget is now available in groups context 87 | - added: plugin setting to control number of columns in group widgets 88 | - changed: updated for Elgg 4.2 89 | 90 | 12.0.3 (2022-02-16): 91 | 92 | - fixed: cast widget context as string 93 | 94 | 12.0.2 (2021-10-27): 95 | 96 | - fixed: use route generation instead of fixed url 97 | 98 | 12.0.1 (2021-10-12): 99 | 100 | - fixed: added missing class in group widget layout 101 | - fixed: check for array when saving settings 102 | - fixed: get all widgets when checking for collapsed state 103 | 104 | 12.0 (2021-09-27): 105 | 106 | - changed: updated for Elgg 4 107 | - fix: prevent widgets being auto created public on walled garden site 108 | - removed: no longer supports a top row column in widget layout 109 | - removed: widgets can no longer be positioned fixed in a layout 110 | 111 | 11.4 (2021-07-06): 112 | 113 | - changed: replaced packer with muuri for fluid layouts 114 | 115 | 11.3 (2021-06-22): 116 | 117 | - added: fluid/packery/masonry index layout 118 | 119 | 11.2.1 (2021-06-08): 120 | 121 | - fixed: correctly pass widget entity in lazy loading hook 122 | - fixed: multiple layouts should not connect with eachother 123 | 124 | 11.2 (2021-05-25): 125 | 126 | - added: improved core widget logic if you have multiple layouts on a page 127 | - fixed: correctly detected if lazy loading is needed for a widget layout 128 | 129 | 11.1 (2021-01-05): 130 | 131 | - added: lazy loading widget features 132 | 133 | 11.0.1 (2020-12-08): 134 | 135 | - fixed: make sure custom link for widgets are always used 136 | - fixed: update widget title correctly with new title and href 137 | 138 | 11.0 (2020-05-26): 139 | 140 | - changed: increased minimal Elgg requirement to 3.3 141 | - changed: layouts now use the default layout 142 | - changed: manage widget now instant saves setting 143 | - changed: the widget settings config can now be used as a service 144 | - fixed: do not crash when viewing unsaved widgets 145 | - fixed: make sure all widgets are manageable 146 | - removed: unused function widget_manager_sort_widgets 147 | 148 | 10.0.2 (2019-10-15): 149 | 150 | - fixed: bulk update widget access in groups now show correct access opts 151 | - fixed: removed widgets blacklist correctly populated 152 | 153 | 10.0.1 (2019-09-18): 154 | 155 | - fixed: index manager now can manage admin only widgets 156 | 157 | 10.0 (2019-08-22): 158 | 159 | - changed: minimal Elgg requirement is now 3.1 160 | - changed: moved last hooks lib functions to classes 161 | 162 | 9.0.4 (2019-08-07): 163 | 164 | - fixed: correctly check if user is allowed to manage a widget page 165 | 166 | 9.0.3 (2019-07-11): 167 | 168 | - fixed: index manager can create widgets again 169 | - fixed: provide correct write access arrays 170 | 171 | 9.0.2 (2019-05-02): 172 | 173 | - changed: widget pages urls are stored in system cache for performance 174 | 175 | 9.0.1 (2019-04-17): 176 | 177 | - several small chores/fixes 178 | 179 | 9.0 (2019-03-27): 180 | 181 | - added: admin feature to delete widgets from the database 182 | - added: max columns for group tool based widgets can now be hooked 183 | - added: setting a widget page title sets it as a page title 184 | - added: widget pages are now their own entities 185 | - changed: advanced widget settings now show in a tab 186 | - changed: custom widget title is now returned from getDisplayName 187 | - changed: more changes for Elgg 3.0 188 | - changed: more changes for Elgg 3.0 189 | - changed: moved extra contexts page handler to route registration 190 | - changed: moved widget_manager_check_collapsed_state function into class 191 | - changed: started updating code for Elgg 3.0 192 | - changed: toggle form elements now are switches 193 | - changed: widget cache now uses system_cache 194 | - changed: widget settings save now uses elgg/Ajax 195 | - changed: widgets no longer cache private settings, now done by core 196 | - feat: added collapse/expand functions to widget class 197 | - fixed: force correct column widths for widgets 198 | - removed: no longer support fixed height setting on widgets 199 | - Spanish translation 200 | 201 | 8.3.2 (2018-10-04): 202 | 203 | - changed: fallback logic for widget setting 204 | 205 | 8.3.1 (2018-06-28): 206 | 207 | - changed: group tool widgets are less persistent 208 | 209 | 8.3 (2018-06-15): 210 | 211 | - added: plugin setting to allow normal users to manage index widgets 212 | 213 | 8.2.2 (2018-03-28): 214 | 215 | - fix: memcache could contain the incorrect entity class for new widgets 216 | 217 | 8.2.1.1 (2018-03-22): 218 | 219 | - several small chores/fixes 220 | 221 | 8.2.1 (2018-03-22): 222 | 223 | - changed: override getContext to be able to use cache 224 | 225 | 8.2 (2017-11-15): 226 | 227 | - added: widget collapse capability is now hookable 228 | 229 | 8.1 (2017-10-19): 230 | 231 | - changed: allow to set a custom more link on every widget on all contexts 232 | - changed: use elgg_view_field function for widget form fields 233 | 234 | 8.0 (2017-02-27): 235 | 236 | - changed: updated min requirements to Elgg 2.3 237 | 238 | 7.0.1 (2016-10-27): 239 | 240 | - fixed: it was not possible to add widgets to extra widgets pages 241 | - fixed: widget default collapsed state correctly set for logged out user 242 | 243 | 7.0 (2016-10-06): 244 | 245 | - added: new way of widget management with ability to change contexts 246 | - changed: refactored widget object view 247 | - fixed: replace usage of deprecated elgg.ui.widgets.add 248 | - fixed: require correct Elgg version for elgg/widgets AMD module 249 | - removed: moved multi_dashboard features to own plugin 250 | - removed: moved widgets to a separate widget_pack plugin 251 | 252 | 6.2.1 (2016-08-26): 253 | 254 | - fixed: php notice when page already routed 255 | 256 | 6.2 (2016-06-16): 257 | 258 | - changed: content_by_tag widget now preloads owners and containers 259 | - changed: do not include container object in results content_by_tag 260 | - changed: moved event functions to class files 261 | - changed: moved pagehandler functions to class functions 262 | - changed: moved pagesetup actions to separate class functions 263 | - changed: moved plugin hooks into the init function 264 | - changed: moved router hooks to class functions 265 | - changed: moved some more hooks to class functions 266 | - changed: moved the widget_manager_update_widget registration in init 267 | - changed: moved various hooks to class functions 268 | - changed: split widget_manager_init into multiple init functions 269 | - changed: use translatable short month/day in slim view content_by_tag 270 | - fixed: correctly get widget collapse state due to event order change 271 | - fixed: declare elgg/widgets js dependancy to prevent timing issues 272 | 273 | 6.1 (2016-05-26): 274 | 275 | - added: dashboard entity URLs can now be filtered 276 | - fixed: MultiDashboard::save() no longer sets attributes 277 | - fixed: only show edit icon if dashboard is editable 278 | - fixed: do not crash the loader if there are no private settings 279 | - fixed: moved elgg-widgets sortable modifications to system init event 280 | 281 | 6.0.1 (2016-05-04): 282 | 283 | - fixed: widget cache was not resetting correctly on cache flush 284 | 285 | 6.0 (2016-04-13): 286 | 287 | - added: cached data for cacheable widgets is now reset on cache flush 288 | - fixed: accidentally deactivated registration cacheable widgets 289 | - fixed: widget cache clear took too long 290 | 291 | 6.0-beta (2016-01-26): 292 | 293 | - added: support for Elgg 2.0 added 294 | - removed: no longer provide favorites widget 295 | - removed: no longer provide index_bookmarks widget 296 | - removed: no longer provide tagcloud widget 297 | 298 | 5.2 (2016-01-20): 299 | 300 | - added: ability for group owners to update all widgets access level 301 | - added: content by tag widget now supports static pages 302 | - added: option to alphabetically sort content in content_by_tag widget 303 | - added: support for poll and questions in content_by_tag widget 304 | - added: allow additional contexts based on default widgets config 305 | - added: use title menu for Add widgets button 306 | - changed: content_by_tag supported plugins list is now centralized 307 | - changed: moved favorites extras menu item to a register hook 308 | - changed: widget add panel contents are fetched on demand 309 | - changed: convert favorites widgets JS to an AMD module 310 | - changed: convert site and admin JS to AMD 311 | - changed: dutch translation 312 | - fixed: community favorites working again and fixed saving of title 313 | - fixed: incorrect access options available in specific widgets contexts 314 | - fixed: more specific selector for widget insert 315 | - fixed: only output icon if there is any 316 | - fixed: user search widget content not loaded when search with spaces 317 | - fixed: wrong composer include 318 | 319 | 5.1 (2015-07-08): 320 | 321 | - added: mechanisme to permanently cache widgets based on their handler 322 | - added: support for object/thewire entities 323 | - added: composer support 324 | - added: widget edit and delete icon now only show when hovering widget 325 | - changed: reorganized javascript 326 | - changed: Simplepie library is now loaded by composer 327 | - changed: delayed loading of the AMD modules to document ready to prevent conflicts 328 | - fixed: correctly ajax load search results for user_search widget 329 | - fixed: performance of rss server widget 330 | - fixed: replaced deprecate ->get calls with variable variables 331 | - fixed: replaced deprecated use of class in ElggMenuItem::factory 332 | - fixed: output/confirmlink deprecation notices 333 | - fixed: content by tag widget case insensitive tag search 334 | - fixed: moved favorites toggle action file to correct location 335 | - fixed: non admins can not correctly manage widgets on landing pages 336 | - fixed: twitter search embed code parsing failed 337 | - fixed: use input/text view to output search input on addpanel 338 | 339 | 5.0 (2014-12-08): 340 | 341 | - added: widget collapsed state is remember for logged in users in the database 342 | - added: widget advanced setting to set default collapse state 343 | - added: widget advanced setting to set availability of collapse toggle button 344 | - added: custom more link for widgets 345 | - added: group forum topics added to content by tag widget 346 | - added: content by tag support multi tag search link if search_advanced is available 347 | - added: widget setting is showed in a lightbox 348 | - added: support for bookmark_tools link setting 349 | - added: extra css class to widget layout so there is more styling control 350 | - added: client side rss widget using Google Feed API 351 | - added: plugin setting to force use of group widgets 352 | - added: hooks to have widgets added/removed when related group tool option is toggled 353 | - added: admin action to update widgets on all groups to verify group tool option 354 | - added: single column layout 355 | - added: iframe widget 356 | - added: rss widget now has option to show feed contents in lightbox 357 | - added: per rss widget setting of feed timeout 358 | - added: content by tag widget now also has an exclusion list of tags 359 | - added: extra context pages now also support top row config 360 | - changed: hide header widget option also hides for admins 361 | - changed: free html widget now uses long text so there is an editor 362 | - changed: updated for Elgg 1.9 363 | - changed: split css views 364 | - changed: updated to Elgg coding standards 365 | - changed: replaced some language keys with core versions 366 | - changed: relocated widgets code 367 | - changed: plugin settings view only shows applicable 368 | - fixed: widgets created in groups get unusable access #25 369 | - fixed: ability to globally hide a widget on the widgets manage page 370 | - fixed: issue with extra minus sign in content by tag widget #31 371 | - deprecated: widgets url hook is no longer usable, use the entity url hook if you want to set widget title links 372 | - removed: widget url links for non core plugins (plugins should provide themselves) 373 | 374 | 4.8 (2014-03-07): 375 | 376 | - added: ability to add custom pages with index widgets 377 | - added: content_by_tag widget now support multiple content_types selected 378 | - added: plugin setting to control default dashboard column layout 379 | - added: content_by_tag now also support event_manager, tasks and videolist entities 380 | - added: rss widget excerpt added to title of rss post link 381 | - changed: applied Elgg coding standards on almost all code 382 | - removed: obsolete group_news widget code (was already moved to group_tools) 383 | - fixed: content_by_tag widget bookmarks entities now have direct address link 384 | 385 | 4.7 (2013-08-30): 386 | 387 | - added: group default widget support (thanks to Connecting Conservation) 388 | - added: show avatar and show timestamp options to content_by_tag widget 389 | - fixed: respect limit set in tagcloud widget settings 390 | 391 | 4.6 (2013-06-27): 392 | 393 | - added: content_by_tag widget also support bookmarks 394 | - added: content_by_tag widget has an optional search link 395 | - added: optional fixed widget height 396 | - changed: twitter_search widget now uses twitters new widget embed code 397 | - changed: rss widget uses new version of SimplePie library 398 | - fixed: memcache reset on widget settings save 399 | 400 | 4.5 (2013-05-06): 401 | 402 | - added: admin users search widget (also searches disabled/blocked/unvalidated users and by email) 403 | - fixed: incorrect has_widget check for favorites widget 404 | - fixed: some php notices 405 | 406 | 4.4 (2013-04-05): 407 | 408 | - added: experimental likes widget 409 | - changed: use system cache for some widget settings 410 | - removed: widget statistics in favor of advanced_statistics plugin 411 | - fixed: access options should not show up after adding a widget to dashboard 412 | - fixed: tags are stripped from dashboard title 413 | - fixed: slider output not closing anchor when using link 414 | 415 | 4.3 (2012-07-26): 416 | 417 | - added: favorites widget 418 | - added: updates widget titles without page refresh (thanks to Matt Beckett) 419 | - added: new 'simple' layout in content_by_tag widget 420 | - changed: freehtml is now contained within a div with elgg-output class (better styling) 421 | - changed: manifest requirements (needs to be after twitter and tagcloud plugins) 422 | - changed: Natural sorting of widgets (for example in add panel) 423 | - changed: content by tag widget now allows to show all site content from within a group 424 | - removed: index_file and group_files widgets moved to file_tools plugin 425 | - removed: unused language keys 426 | - removed: index_pages widget (moved to pages_tools plugin) 427 | - removed: widgets_pagesetup event trigger 428 | - fixed: support for old widget settings in index_activity widget 429 | - fixed: add panel keeps correct layout if no widget description is given 430 | - fixed: some layout issues when using IE7 431 | - fixed: no need for flooding apache error logs with the error message that a rss feed could not be found (SimplePie) 432 | 433 | 4.2 (2012-05-10): 434 | 435 | - added: re-enabled index_activity widget 436 | - added: widget class now is able to handle arrays as widget settings 437 | - added: re-enabled entity statistics widget 438 | - changed: now admin can always drag and edit/remove fixed widgets 439 | - changed: better/extendable check for advanced widget features 440 | - changed: widget title links can now be set with a hook 441 | - changed: default hide feed title in rss widget 442 | - changed: reduced language keys by replacing them with default core language keys 443 | - changed: available widgets context is now adjustable with a plugin hook 444 | - changed: widgetmanager controls widget class 445 | - removed: unnecessary widget context check, we trust all contexts 446 | - removed: group related widgets (moved to group_tools plugin) 447 | - fixed: a lot of php notices / warnings 448 | - fixed: group top row missing 449 | - fixed: a nasty z-index in twitter search widget 450 | - fixed: 2 column index layout do not merge column 2 and 3 451 | - fixed: multidashboards are showing on the admin side 452 | - fixed: edit twitter search not remembering height param 453 | - fixed: some default core widget title urls 454 | - fixed: only show multidashboard extras button when logged in 455 | 456 | 4.1.1 (2012-03-19): 457 | 458 | - changed: button styling on advanced widget settings 459 | - changed: add tab is now a tab instead of a button 460 | - fixed: group admins can not add/edit widgets on a group profile page 461 | 462 | 4.1 (2012-02-12): 463 | 464 | - added: Multi Dashboard support 465 | - changed: updated the Dutch language file 466 | - changed: required Elgg version to 1.8.3 467 | 468 | 4.0 beta (2011-12-29): 469 | 470 | - added: support for Elgg 1.8 471 | - added: widget sortable tolerance changes to pointer instead of intersect (needed for index widgets top row) 472 | - added: widget usage statistics page 473 | - changed: temporarily disabled the following widgets (index_activity, entity_statistics, river_widget); awaiting core fix 474 | - changed: fix mechanisme for default widgets 475 | - removed: run_once fix for elgg widget object/subtype class (obsolete) 476 | - removed: run_once migration from group_custom_layout plugin 477 | - removed: lazy loading plugin setting 478 | - removed: target column setting 479 | - removed: auto-cleanup of broken widgets 480 | - removed: plugin option to show broken widgets 481 | - removed: option to configure if a widget is multiple or single (this is now handled correct by Elgg core) 482 | - removed: option to configure if a widget is deletable (use 'fix widget' instead) 483 | - removed: group_event_calendar widget 484 | - removed: top row 2 column right option for index page 485 | - removed: various widgets without a valid 1.8 version of their plugin (izap_videos, tidypics, videolist, tasks) 486 | 487 | 3.9 (2011-11-22): 488 | 489 | - added: messages widget (show new messages from inbox) 490 | - added: entity_statistics widget (index only) 491 | - added: images to rss feed items (only if showing excerpt) in rss widget 492 | - added: flexslider as a new slider type for image slider widget 493 | - changed: content_by_tag widget now also available as group widget 494 | - changed: activity widgets can be filtered by multiple type/subtypes 495 | 496 | 3.8 (2011-11-09): 497 | 498 | - added: new widget header layout (getting used to Elgg 1.8) 499 | - added: optional filter for index_activity and river_widget to filter content based on type/subtype 500 | 501 | 3.7.2 (2011-10-04): 502 | 503 | - fixed: icon error in content_by_tag widget in slim mode 504 | - fixed: group widget access pulldown not showing correct groupname 505 | - changed: moved event functions to different file 506 | - changed: moved pluginhook functions to different file 507 | 508 | 3.7.1 (2011-08-15): 509 | 510 | - removed: all the wire widgets (moved to thewire_tools plugin) 511 | 512 | 3.7 (2011-08-11): 513 | 514 | - changed: major performance gains 515 | 516 | 3.6.1 (2011-07-22): 517 | 518 | - changed: layout of tagcloud 519 | - changed: updated some js to be smaller 520 | - changed: loading of js and css of image_slider and twitter widget 521 | - fixed: wrong key when no data for tagcloud 522 | 523 | 3.6 (2011-06-21): 524 | 525 | - added: auto cleanup for broken widgets (if broken -> delete) 526 | - fixed: widget edit link is not showing if a widget is loaded collapsed 527 | - fixed: with broken widgets on some widgets crash the site 528 | - fixed: group widgets settings not being saved 529 | - fixed: fancybox ie alpha functions url 530 | 531 | 3.5 (2011-06-17): 532 | 533 | - added: group news widget 534 | - fixed: the_wire extend should not be visible when not loggedin 535 | 536 | 3.4 (2011-06-07): 537 | 538 | - added: group tasks widgets (tasks plugin) 539 | - added: tagcloud widget 540 | - added: optionally prepends the thewire widget with post form (default on) 541 | - changed: ordering of widgets in lightbox are now alphabetically 542 | - fixed: page crashes on default widgets configuration => no longer showing widget body 543 | - fixed: wrong class handler for object => widget 544 | - fixed: slider widget fix no text -> no slide over 545 | - removed: group_river_widget moved to group_tools plugin 546 | 547 | 3.3 (2011-05-24): 548 | 549 | - added: custom url for widget title added for group and index widgets 550 | - changed: widget options for content_by_tag and rss widget 551 | - fixed: free_html admin ignore option not working 552 | - fixed: coding error in rss widget view 553 | - fixed: Fancybox CSS errors in IE 554 | 555 | 3.2 (2011-05-06): 556 | 557 | - added: thewire_post widget to post directly to the wire from a widget 558 | - fixed: socialink layout in index_login widget 559 | - fixed: group_forum_topics widget when nog items 560 | - fixed: z-index to low with new fancybox 561 | 562 | 3.1 (2011-05-03): 563 | 564 | - added: new widget group_river_widget to show activity from a specific group 565 | - added: support for categories plugin to content_by_tag widget 566 | - added: default twitter widget view override to support https 567 | - added: option to enable group widget management by default 568 | - added: option to only allow admins to enable group widget management 569 | - added: group tool option to enable/disable widget manager group management 570 | - added: caching of rss feeds in dataroot + cron cache cleanup job 571 | - changed: custom title made available for all widgets on all contexts 572 | - changed: custom hook to set ACL options for widgets 573 | - changed: access input options on group widgets are limited to "group", "loggedin", "public" 574 | - changed: updated fancybox to 1.3.4 (be aware of conflicts with other plugins using fancybox) 575 | - changed: rss widget now available on profile and dashboard 576 | - fixed: default access on group widget is group_acl 577 | - fixed: dropping widgets on empty right column in group layout not possible 578 | - fixed: wrong context in lightbox when adding widgets to default profile/dashboard 579 | - fixed: input pulldown in widget edit panel need to be restricted to the edit panel 580 | - fixed: river_widget title link location (activity) 581 | 582 | 3.0 beta (2011-04-05): 583 | 584 | - added: new way to manage default widgets (profile/dashboard) (not perfect yet, but it works). It now allows default settings for widgets. 585 | - added: option to disallow editing / collapsing default widgets 586 | - added: manage group widgets with widget manager (enable in plugin settings). This replaces widget features from group custom layout plugin 587 | - added: group widgets 588 | - added: css class on fixed widget headers 589 | - added: search widgets in lightbox popup 590 | - added: various widgets to various contexts 591 | - changed: grouped admin only widget edit options into 'Advanced' section 592 | - changed: widgets are consolidated in widget_manager/widgets 593 | - fixed: calling undefined function in /lib/functions.php (line 234) when using Elgg < 1.7 594 | - fixed: adding widgets to other users profile uses wrong page_owner in lightbox 595 | - removed: support for dropdown list of widgets. Now always in a lightbox 596 | - removed: old default widget placement mechanism 597 | 598 | 2.1 (2011-01-24): 599 | 600 | - added: indicator for widget without header for admins 601 | - added: option to disable widget content styling 602 | - added: twitter_search widget 603 | - added: content_by_tag widget 604 | - added: first row on front page 605 | - added: custom_index widget links 606 | - changed: frontpage layout to div layout 607 | - fixed: widget edit options now more secure 608 | 609 | 2.0 (2011-01-13): 610 | 611 | - added: option to design custom index with widgets 612 | - added: custom index widget layouts (in admin options) 613 | - added: custom index widgets 614 | - added: hide widget title (on custom index only) 615 | - added: custom widget title (on custom index only) 616 | - fixed: issue with context when hiding broken widgets 617 | 618 | 1.1 (2010-12-24): 619 | 620 | - added: admin option to hide a widget (also existing widgets on profiles will be hidden with this feature) 621 | - fixed: using wrong functions for ignoring access on Elgg 1.7.x 622 | - fixed: widget titles for Elgg 1.7.5 623 | 624 | 1.0.1 beta (2010-08-27): 625 | 626 | - fixed: header also changes on group widgets (with some plugins) 627 | - fixed: adding non existing widgets 628 | - changed: create user widget timestamps 629 | 630 | 1.0 beta (2010-08-13): 631 | 632 | - replacement of Draggable widgets --------------------------------------------------------------------------------