From 6578e5e5f3af48ac4b7f594d9ce74dce4703d3f4 Mon Sep 17 00:00:00 2001 From: Kim Nguyen Date: Tue, 15 Mar 2016 12:14:21 -0400 Subject: [PATCH] Merge 'develop' into 'file-layout'. --- README.md | 9 +- application/Module.php | 3 +- application/asset/css/style.css | 12 +- application/asset/js/global.js | 81 +- application/asset/js/resource-form.js | 13 +- application/asset/js/resource-selector.js | 1 - application/asset/sass/_screen.scss | 9 +- application/asset/sass/_tablet.scss | 4 +- application/config/module.config.php | 5 +- application/config/navigation.config.php | 21 +- application/src/Api/Adapter/SiteAdapter.php | 82 +- .../src/Api/Adapter/SitePageAdapter.php | 32 +- application/src/Api/Adapter/SiteSlugTrait.php | 71 + ...ResourceTemplatePropertyRepresentation.php | 9 + .../src/Controller/Admin/ItemController.php | 26 +- .../Controller/Admin/ItemSetController.php | 20 +- .../src/Controller/Admin/JobController.php | 31 +- .../src/Controller/Admin/MediaController.php | 20 +- .../src/Controller/Admin/ModuleController.php | 24 +- .../Admin/ResourceTemplateController.php | 18 +- .../src/Controller/Admin/UserController.php | 16 +- .../Controller/Admin/VocabularyController.php | 18 +- .../Controller/SiteAdmin/IndexController.php | 57 +- .../Controller/SiteAdmin/PageController.php | 21 + application/src/Form/SiteForm.php | 14 +- application/src/Form/SitePageForm.php | 14 +- .../src/Job/Strategy/SynchronousStrategy.php | 26 + application/src/Mvc/MvcListeners.php | 46 +- application/src/Service/AclFactory.php | 5 - .../Service/ViewHelperThemeSettingFactory.php | 29 + application/src/Session/SaveHandler/Db.php | 2 +- application/src/Settings/SiteSettings.php | 33 +- .../src/Site/BlockLayout/ItemWithMetadata.php | 59 + .../src/Site/BlockLayout/TableOfContents.php | 8 +- .../src/Site/Navigation/Link/Browse.php | 5 +- .../src/Site/Navigation/Link/Fallback.php | 3 +- .../Site/Navigation/Link/LinkInterface.php | 5 +- application/src/Site/Navigation/Link/Page.php | 12 +- application/src/Site/Navigation/Link/Url.php | 3 +- .../src/Site/Navigation/Translator.php | 7 +- application/src/Site/Theme/Theme.php | 13 + application/src/View/Helper/DeleteConfirm.php | 17 + .../src/View/Helper/DeleteConfirmForm.php | 30 + .../src/View/Helper/PropertySelector.php | 2 +- application/src/View/Helper/ThemeSetting.php | 44 + application/src/View/Helper/Trigger.php | 9 +- .../common/delete-confirm-details.phtml | 12 + .../view-admin/common/delete-confirm.phtml | 10 + application/view-admin/layout/header.phtml | 12 +- .../view-admin/omeka/admin/item-set/add.phtml | 4 +- .../omeka/admin/item-set/browse.phtml | 23 +- .../omeka/admin/item-set/edit.phtml | 20 +- .../omeka/admin/item-set/form.phtml | 4 +- .../omeka/admin/item-set/search.phtml | 2 +- .../omeka/admin/item-set/show-details.phtml | 12 +- .../omeka/admin/item-set/show.phtml | 14 +- .../omeka/admin/item-set/sidebar-select.phtml | 2 +- .../view-admin/omeka/admin/item/add.phtml | 7 +- .../view-admin/omeka/admin/item/browse.phtml | 23 +- .../view-admin/omeka/admin/item/edit.phtml | 23 +- .../view-admin/omeka/admin/item/form.phtml | 26 +- .../omeka/admin/item/manage-media.phtml | 6 +- .../view-admin/omeka/admin/item/search.phtml | 2 +- .../omeka/admin/item/show-details.phtml | 16 +- .../view-admin/omeka/admin/item/show.phtml | 2 +- .../view-admin/omeka/admin/job/browse.phtml | 34 +- .../job/{show-details.phtml => show.phtml} | 19 +- .../view-admin/omeka/admin/media/browse.phtml | 21 +- .../view-admin/omeka/admin/media/edit.phtml | 25 +- .../view-admin/omeka/admin/media/search.phtml | 2 +- .../omeka/admin/media/show-details.phtml | 16 +- .../view-admin/omeka/admin/media/show.phtml | 2 +- .../omeka/admin/module/browse.phtml | 24 +- .../admin/module/uninstall-confirm.phtml | 9 + .../omeka/admin/resource-template/add.phtml | 4 +- .../admin/resource-template/browse.phtml | 24 +- .../omeka/admin/resource-template/edit.phtml | 4 +- .../omeka/admin/resource-template/form.phtml | 19 +- .../resource-template/show-details.phtml | 6 +- .../resource-template/show-property-row.phtml | 1 + .../omeka/admin/resource-template/show.phtml | 2 + .../view-admin/omeka/admin/user/browse.phtml | 16 +- .../omeka/admin/user/change-password.phtml | 2 +- .../omeka/admin/user/edit-keys.phtml | 2 +- .../omeka/admin/user/show-details.phtml | 6 +- .../omeka/admin/vocabulary/browse.phtml | 21 +- .../omeka/admin/vocabulary/classes.phtml | 2 +- .../omeka/admin/vocabulary/edit.phtml | 2 + .../omeka/admin/vocabulary/properties.phtml | 2 +- .../omeka/admin/vocabulary/show-details.phtml | 6 +- .../omeka/site-admin/index/edit.phtml | 15 +- .../omeka/site-admin/index/index.phtml | 22 +- .../omeka/site-admin/index/show-details.phtml | 2 +- .../omeka/site-admin/index/show.phtml | 6 +- .../omeka/site-admin/index/theme.phtml | 15 + .../omeka/site-admin/index/users.phtml | 10 +- .../omeka/site-admin/page/edit.phtml | 16 +- .../omeka/site-admin/page/index.phtml | 27 +- .../omeka/site-admin/page/show-details.phtml | 3 + .../view-shared/common/advanced-search.phtml | 4 +- .../block-layout/table-of-contents.phtml | 8 +- application/view-shared/common/form-row.phtml | 2 +- .../view-shared/common/resource-fields.phtml | 2 +- .../view-shared/omeka/site/index/index.phtml | 5 - .../omeka/site/item-set/show.phtml | 7 +- .../view-shared/omeka/site/item/browse.phtml | 6 +- .../view-shared/omeka/site/item/show.phtml | 5 - .../view-shared/omeka/site/media/show.phtml | 5 - .../view-shared/omeka/site/page/show.phtml | 26 +- composer.json | 56 +- composer.lock | 4011 ++++++++--------- themes/default/asset/css/sass/_desktop.scss | 7 +- themes/default/asset/css/sass/_screen.scss | 52 +- themes/default/asset/css/style.css | 59 +- themes/default/config/theme.ini | 8 + themes/default/view/layout/layout.phtml | 12 + 116 files changed, 3175 insertions(+), 2696 deletions(-) create mode 100644 application/src/Api/Adapter/SiteSlugTrait.php create mode 100644 application/src/Service/ViewHelperThemeSettingFactory.php create mode 100644 application/src/Site/BlockLayout/ItemWithMetadata.php create mode 100644 application/src/View/Helper/DeleteConfirm.php create mode 100644 application/src/View/Helper/DeleteConfirmForm.php create mode 100644 application/src/View/Helper/ThemeSetting.php create mode 100644 application/view-admin/common/delete-confirm-details.phtml create mode 100644 application/view-admin/common/delete-confirm.phtml rename application/view-admin/omeka/admin/job/{show-details.phtml => show.phtml} (75%) create mode 100644 application/view-admin/omeka/admin/module/uninstall-confirm.phtml create mode 100644 application/view-admin/omeka/site-admin/index/theme.phtml create mode 100644 application/view-admin/omeka/site-admin/page/show-details.phtml diff --git a/README.md b/README.md index 4a8ab29e34..3f79a05cec 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,14 @@ You can find Omeka-specific code under `application/`. ## local.config.php options * `thumbnailer` Default is `Omeka\File\ImageMagickThumbnailer`. Also available are `Omeka\File\IMagickThumbnailer` and `Omeka\File\GdThumbnailer` -* `phpcli_path` Default is to attempt to detect correct path to PHP. Use this option to specify a path if needed in your server configuration. +* `phpcli_path` Default is to attempt to detect correct path to PHP. Use this option to specify a path if needed in your server configuration. For example: +``` + 'cli' => array( + 'phpcli_path' => '/usr/bin/php55', + ), + +``` + ## Libraries diff --git a/application/Module.php b/application/Module.php index b3ae6b956d..869cacc898 100644 --- a/application/Module.php +++ b/application/Module.php @@ -20,7 +20,7 @@ class Module extends AbstractModule /** * This Omeka version. */ - const VERSION = '0.4.5-alpha'; + const VERSION = '0.5.0-alpha'; /** * @var array View helpers that need service manager injection @@ -37,6 +37,7 @@ class Module extends AbstractModule 'blockLayout' => 'Omeka\View\Helper\BlockLayout', 'userIsAllowed' => 'Omeka\View\Helper\UserIsAllowed', 'navigationLink' => 'Omeka\View\Helper\NavigationLink', + 'deleteConfirmForm' => 'Omeka\View\Helper\DeleteConfirmForm' ]; /** diff --git a/application/asset/css/style.css b/application/asset/css/style.css index 08dde1dcb4..77d9a6dd18 100755 --- a/application/asset/css/style.css +++ b/application/asset/css/style.css @@ -1207,6 +1207,10 @@ th { content: ""; } + #menu a.theme:before { + content: ""; + } + /* @end */ /* @end */ /* @group ----- Wrappers ----- */ @@ -2661,6 +2665,7 @@ th { position: fixed; top: 5.25em; left: 100%; + visibility: hidden; bottom: 0; background-color: #f7f7f7; z-index: 3; @@ -2673,8 +2678,9 @@ th { -webkit-transition-duration: 0.5s; transition-duration: 0.5s; } - .sidebar.active { + .sidebar.active, .sidebar.always-open { left: 75.52083%; + visibility: visible; } .sidebar .o-icon-close { position: absolute; @@ -3734,11 +3740,11 @@ th { width: 100%; } - .sidebar.active { + .sidebar.always-open { left: 100%; } - .sidebar.mobile.active, + .sidebar.active, #resource-details.active #select-item, .confirm-panel { left: 0; diff --git a/application/asset/js/global.js b/application/asset/js/global.js index aea75c1f4c..c74a03ee88 100644 --- a/application/asset/js/global.js +++ b/application/asset/js/global.js @@ -3,7 +3,7 @@ var Omeka = { //close delete sidebar if open if (!context.hasClass('delete')) { if ($('#delete').hasClass('active')) { - $('#delete').removeClass('mobile active'); + $('#delete').removeClass('active'); } } @@ -19,38 +19,22 @@ var Omeka = { if (!$('body').hasClass('sidebar-open')) { $('body').addClass('sidebar-open'); } - var sidebarConfirm = $('#sidebar-confirm'); - if (context.hasClass('sidebar-confirm')) { - sidebarConfirm.show(); - $('#sidebar-confirm form').attr( - 'action', context.data('sidebar-confirm-url')); - } else { - sidebarConfirm.hide(); - } + if (context.attr('data-sidebar-content-url')) { this.populateSidebarContent(context, sidebar); } - sidebar.addClass('mobile active'); + sidebar.addClass('active'); return sidebar; }, closeSidebar : function(context) { - if (context.hasClass('mobile-only')) { - context.closest('.active').removeClass('mobile'); - } else { - context.closest('.active').removeClass('mobile active'); - if ($('.active.sidebar').length < 1) { - $('body').removeClass('sidebar-open'); - } + context.removeClass('active'); + context.closest('.active').removeClass('active'); + if ($('.active.sidebar').length < 1 && $('.always-open.sidebar').length < 1) { + $('body').removeClass('sidebar-open'); } }, - switchActiveSection: function (section) { - $('.section.active, .section-nav li.active').removeClass('active'); - section.addClass('active'); - $('.section-nav a[href="#' + section.attr('id') + '"]').parent().addClass('active'); - }, - populateSidebarContent : function(context, sidebar) { var url = context.data('sidebar-content-url'); var sidebarContent = sidebar.find('.sidebar-content'); @@ -66,6 +50,12 @@ var Omeka = { }); }, + switchActiveSection: function (section) { + $('.section.active, .section-nav li.active').removeClass('active'); + section.addClass('active'); + $('.section-nav a[href="#' + section.attr('id') + '"]').parent().addClass('active'); + }, + filterSelector : function() { var filter = $(this).val().toLowerCase(); var selector = $(this).closest('.selector'); @@ -126,15 +116,37 @@ var Omeka = { $('#content').on('click', 'a.sidebar-content', function(e) { e.preventDefault(); - Omeka.openSidebar($(this)); + var sidebarSelector = $(this).data('sidebar-selector'); + Omeka.openSidebar($(this), sidebarSelector); }); - // Attach sidebar triggers - $('#content').on('click', 'a.sidebar-confirm', function(e) { + $('#content').on('click', '.button.delete, button.delete', function(e) { e.preventDefault(); Omeka.openSidebar($(this), '#delete'); }); + if ($('.always-open.sidebar').length > 0) { + $('#content').addClass('sidebar-open'); + } + + $('.sidebar').find('.sidebar-close').click(function(e) { + e.preventDefault(); + Omeka.closeSidebar($(this)); + }); + + // Open sidebars on mobile + $('button.mobile-only').on('click', function(e) { + e.preventDefault(); + var mobileButton = $(this); + var sidebarId = mobileButton.attr('id'); + sidebarId = '#' + sidebarId.replace('-button', ''); + $(sidebarId).addClass('active'); + mobileButton.parents('form').bind('DOMSubtreeModified', function() { + $('.sidebar.always-open').removeClass('active'); + $(this).unbind('DOMSubtreeModified'); + }); + }); + // Make resource public or private $('#content').on('click', 'a.o-icon-private, a.o-icon-public', function(e) { e.preventDefault(); @@ -152,15 +164,6 @@ var Omeka = { } }); - if ($('.active.sidebar').length > 0) { - $('#content').addClass('sidebar-open'); - } - - $('.sidebar').find('.sidebar-close').click(function(e) { - e.preventDefault(); - Omeka.closeSidebar($(this)); - }); - // Skip to content button. See http://www.bignerdranch.com/blog/web-accessibility-skip-navigation-links/ $('.skip').click(function(e) { $('#main').attr('tabindex', -1).on('blur focusout', function() { @@ -261,7 +264,7 @@ var Omeka = { $('#search-form').change(Omeka.updateSearch); Omeka.updateSearch(); }); - + $(window).load(function() { var setSubmittedFlag = function () { $(this).data('omekaFormSubmitted', true); @@ -277,12 +280,16 @@ var Omeka = { var preventNav = false; $('form[method=POST]').each(function () { var form = $(this); + var originalData = form.data('omekaFormOriginalData'); if (form.data('omekaFormSubmitted')) { return; } form.trigger('o:before-form-unload'); - if (form.data('omekaFormDirty') || form.data('omekaFormOriginalData') !== form.serialize()) { + + if (form.data('omekaFormDirty') + || (originalData && originalData !== form.serialize()) + ) { preventNav = true; return false; } diff --git a/application/asset/js/resource-form.js b/application/asset/js/resource-form.js index 7f8fc5ff9a..914b337615 100644 --- a/application/asset/js/resource-form.js +++ b/application/asset/js/resource-form.js @@ -1,18 +1,6 @@ (function($) { $(document).ready( function() { - // Open sidebars on mobile - $('button.mobile-only').on('click', function(e) { - e.preventDefault(); - var mobileButton = $(this); - var sidebarId = mobileButton.attr('id'); - sidebarId = sidebarId.replace('-button', ''); - $('#' + sidebarId).addClass('mobile'); - mobileButton.parents('form').bind('DOMSubtreeModified', function() { - $('.sidebar.active.mobile').removeClass('mobile'); - $(this).unbind('DOMSubtreeModified'); - }); - }); // Select property $('#property-selector li.selector-child').on('click', function(e) { @@ -98,6 +86,7 @@ var valueObj = $('.resource-details').data('resource-values'); var namePrefix = value.data('name-prefix'); prepareResource(value, valueObj, namePrefix); + Omeka.closeSidebar($('#select-resource .sidebar-close')); }); $('.button.resource-select').on('click', function(e) { diff --git a/application/asset/js/resource-selector.js b/application/asset/js/resource-selector.js index 6375acc560..a61897314d 100644 --- a/application/asset/js/resource-selector.js +++ b/application/asset/js/resource-selector.js @@ -31,7 +31,6 @@ e.preventDefault(); var context = $(this); Omeka.closeSidebar(context); - context.parents('.mobile').removeClass('mobile'); context.trigger('o:resource-selected'); }); diff --git a/application/asset/sass/_screen.scss b/application/asset/sass/_screen.scss index d2e9dc2e8c..a17c86fedf 100755 --- a/application/asset/sass/_screen.scss +++ b/application/asset/sass/_screen.scss @@ -802,6 +802,10 @@ nav.pagination + *:not(.sorting) { content: $fa-var-gears; } + #menu a.theme:before { + content: $fa-var-paint-brush; + } + /* @end */ /* @end */ @@ -2297,6 +2301,7 @@ input[type="text"].value-language, position: fixed; top: rhythm(3.5); left: 100%; + visibility: hidden; bottom: 0; background-color: #f7f7f7; z-index: 3; @@ -2307,8 +2312,10 @@ input[type="text"].value-language, width: span(4 of 16) - gutter(32); @include transition-duration(.5s); - &.active { + &.active, + &.always-open { left: span(12 of 16) + gutter(32); + visibility: visible; } .o-icon-close { diff --git a/application/asset/sass/_tablet.scss b/application/asset/sass/_tablet.scss index 3ced63a17c..a54287063b 100644 --- a/application/asset/sass/_tablet.scss +++ b/application/asset/sass/_tablet.scss @@ -223,11 +223,11 @@ body.sidebar-open #content { width: 100%; } -.sidebar.active { +.sidebar.always-open { left: 100%; } -.sidebar.mobile.active, +.sidebar.active, #resource-details.active #select-item, .confirm-panel { left: 0; diff --git a/application/config/module.config.php b/application/config/module.config.php index e2507df09f..b16c6cb4b8 100644 --- a/application/config/module.config.php +++ b/application/config/module.config.php @@ -250,12 +250,14 @@ 'itemSetSelect' => 'Omeka\View\Helper\ItemSetSelect', 'formPropertyInputs' => 'Omeka\View\Helper\PropertyInputs', 'resourceClassSelect' => 'Omeka\View\Helper\ResourceClassSelect', + 'deleteConfirm' => 'Omeka\View\Helper\DeleteConfirm', 'searchFilters' => 'Omeka\View\Helper\SearchFilters', 'ckEditor' => 'Omeka\View\Helper\CkEditor', 'sitePagePagination' => 'Omeka\View\Helper\SitePagePagination', ], 'factories' => [ 'assetUrl' => 'Omeka\Service\ViewHelperAssetUrlFactory', + 'themeSetting' => 'Omeka\Service\ViewHelperThemeSettingFactory', ], ], 'data_types' => [ @@ -273,7 +275,8 @@ 'tableOfContents' => 'Omeka\Site\BlockLayout\TableOfContents', 'fileWithText' => 'Omeka\Site\BlockLayout\FileWithText', 'file' => 'Omeka\Site\BlockLayout\File', - 'lineBreak' => 'Omeka\Site\BlockLayout\LineBreak' + 'lineBreak' => 'Omeka\Site\BlockLayout\LineBreak', + 'itemWithMetadata' => 'Omeka\Site\BlockLayout\ItemWithMetadata', ], ], 'navigation_links' => [ diff --git a/application/config/navigation.config.php b/application/config/navigation.config.php index 93208e36c5..abeebeda34 100644 --- a/application/config/navigation.config.php +++ b/application/config/navigation.config.php @@ -2,6 +2,13 @@ return [ 'navigation' => [ 'admin' => [ + [ + 'label' => 'Sites', + 'class' => 'sites', + 'route' => 'admin/site', + 'resource' => 'Omeka\Controller\SiteAdmin\Index', + 'privilege' => 'index', + ], [ 'label' => 'Items', 'class' => 'items', @@ -105,13 +112,6 @@ 'resource' => 'Omeka\Controller\Admin\Job', 'privilege' => 'browse', ], - [ - 'label' => 'Sites', - 'class' => 'sites', - 'route' => 'admin/site', - 'resource' => 'Omeka\Controller\SiteAdmin\Index', - 'privilege' => 'index', - ], [ 'label' => 'Settings', 'class' => 'settings', @@ -189,6 +189,13 @@ 'action' => 'users', 'useRouteMatch' => true ], + [ + 'label' => 'Theme', + 'class' => 'theme', + 'route' => 'admin/site/default', + 'action' => 'theme', + 'useRouteMatch' => true + ], [ 'label' => 'Settings', 'class' => 'settings', diff --git a/application/src/Api/Adapter/SiteAdapter.php b/application/src/Api/Adapter/SiteAdapter.php index a1570dc541..731ccbe5ac 100644 --- a/application/src/Api/Adapter/SiteAdapter.php +++ b/application/src/Api/Adapter/SiteAdapter.php @@ -10,6 +10,8 @@ class SiteAdapter extends AbstractEntityAdapter { + use SiteSlugTrait; + /** * {@inheritDoc} */ @@ -41,17 +43,33 @@ public function hydrate(Request $request, EntityInterface $entity, ErrorStore $errorStore ) { $this->hydrateOwner($request, $entity); - if ($this->shouldHydrate($request, 'o:slug')) { - $entity->setSlug($request->getValue('o:slug')); - } + $title = null; + if ($this->shouldHydrate($request, 'o:theme')) { $entity->setTheme($request->getValue('o:theme')); } if ($this->shouldHydrate($request, 'o:title')) { - $entity->setTitle($request->getValue('o:title')); + $title = trim($request->getValue('o:title', '')); + $entity->setTitle($title); + } + if ($this->shouldHydrate($request, 'o:slug')) { + $default = null; + $slug = trim($request->getValue('o:slug', '')); + if ($slug === '' + && $request->getOperation() === Request::CREATE + && is_string($title) + && $title !== '' + ) { + $slug = $this->getAutomaticSlug($title); + } + $entity->setSlug($slug); } if ($this->shouldHydrate($request, 'o:navigation')) { - $entity->setNavigation($request->getValue('o:navigation', [])); + $default = []; + if ($request->getOperation() === Request::CREATE) { + $default = $this->getDefaultNavigation(); + } + $entity->setNavigation($request->getValue('o:navigation', $default)); } if ($this->shouldHydrate($request, 'o:item_pool')) { $entity->setItemPool($request->getValue('o:item_pool', [])); @@ -78,6 +96,29 @@ public function hydrate(Request $request, EntityInterface $entity, $pages->removeElement($page); } } + + if ($request->getOperation() === Request::CREATE) { + $class = $adapter->getEntityClass(); + $page = new $class; + $page->setSite($entity); + $translator = $this->getServiceLocator()->get('MvcTranslator'); + $subErrorStore = new ErrorStore; + $subrequest = new Request(Request::CREATE, 'site_pages'); + $subrequest->setContent( + [ + 'o:title' => $translator->translate('Welcome'), + 'o:slug' => $translator->translate('welcome'), + 'o:block' => [ + [ + 'o:layout' => 'html', + 'o:data' => ['html' => $translator->translate('Welcome to your new site. This is an example page.')] + ] + ] + ] + ); + $adapter->hydrateEntity($subrequest, $page, $subErrorStore); + $pages->add($page); + } } $sitePermissionsData = $request->getValue('o:site_permission'); @@ -126,6 +167,10 @@ public function hydrate(Request $request, EntityInterface $entity, */ public function validateEntity(EntityInterface $entity, ErrorStore $errorStore) { + $title = $entity->getTitle(); + if (!is_string($title) || $title === '') { + $errorStore->addError('o:title', 'A site must have a title.'); + } $slug = $entity->getSlug(); if (!is_string($slug) || $slug === '') { $errorStore->addError('o:slug', 'The slug cannot be empty.'); @@ -141,10 +186,6 @@ public function validateEntity(EntityInterface $entity, ErrorStore $errorStore) )); } - if (false == $entity->getTitle()) { - $errorStore->addError('o:title', 'A site must have a title.'); - } - if (false == $entity->getTheme()) { $errorStore->addError('o:theme', 'A site must have a theme.'); } @@ -223,4 +264,27 @@ protected function validateNavigation(EntityInterface $entity, }; $validateLinks($navigation); } + + /** + * Get the default nav array for new sites with no specified + * navigation. + * + * The default is to just include a link to the browse page. + * + * @return array + */ + protected function getDefaultNavigation() + { + $translator = $this->getServiceLocator()->get('MvcTranslator'); + return [ + [ + 'type' => 'browse', + 'data' => [ + 'label' => $translator->translate('Browse'), + 'query' => '', + ], + 'links' => [], + ] + ]; + } } diff --git a/application/src/Api/Adapter/SitePageAdapter.php b/application/src/Api/Adapter/SitePageAdapter.php index 73b6c84590..d52cd8b0ce 100644 --- a/application/src/Api/Adapter/SitePageAdapter.php +++ b/application/src/Api/Adapter/SitePageAdapter.php @@ -11,6 +11,8 @@ class SitePageAdapter extends AbstractEntityAdapter { + use SiteSlugTrait; + /** * {@inheritDoc} */ @@ -50,6 +52,7 @@ public function getEntityClass() public function hydrate(Request $request, EntityInterface $entity, ErrorStore $errorStore ) { + $title = null; $data = $request->getContent(); if (Request::CREATE === $request->getOperation() && isset($data['o:site']['o:id']) @@ -58,11 +61,20 @@ public function hydrate(Request $request, EntityInterface $entity, $this->authorize($site, 'add-page'); $entity->setSite($site); } - if ($this->shouldHydrate($request, 'o:slug')) { - $entity->setSlug($request->getValue('o:slug')); - } if ($this->shouldHydrate($request, 'o:title')) { - $entity->setTitle($request->getValue('o:title')); + $title = trim($request->getValue('o:title', '')); + $entity->setTitle($title); + } + if ($this->shouldHydrate($request, 'o:slug')) { + $slug = trim($request->getValue('o:slug', '')); + if ($slug === '' + && $request->getOperation() === Request::CREATE + && is_string($title) && $title !== '' + && isset($site) + ) { + $slug = $this->getAutomaticSlug($title, $site); + } + $entity->setSlug($slug); } $appendBlocks = $request->getOperation() === Request::UPDATE @@ -80,6 +92,11 @@ public function validateEntity(EntityInterface $entity, ErrorStore $errorStore) $errorStore->addError('o:site', 'A page must belong to a site.'); } + $title = $entity->getTitle(); + if (!is_string($title) || $title === '') { + $errorStore->addError('o:title', 'A page must have a title.'); + } + $slug = $entity->getSlug(); if (!is_string($slug) || $slug === '') { $errorStore->addError('o:slug', 'The slug cannot be empty.'); @@ -88,7 +105,8 @@ public function validateEntity(EntityInterface $entity, ErrorStore $errorStore) $errorStore->addError('o:slug', 'A slug can only contain letters, numbers, and hyphens.'); } - if ($entity->getSite() && !$this->isUnique($entity, [ + $site = $entity->getSite(); + if ($site && $site->getId() && !$this->isUnique($entity, [ 'slug' => $slug, 'site' => $entity->getSite() ])) { @@ -97,10 +115,6 @@ public function validateEntity(EntityInterface $entity, ErrorStore $errorStore) $slug )); } - - if (!$entity->getTitle()) { - $errorStore->addError('o:title', 'A page must have a title.'); - } } /** diff --git a/application/src/Api/Adapter/SiteSlugTrait.php b/application/src/Api/Adapter/SiteSlugTrait.php new file mode 100644 index 0000000000..9355469344 --- /dev/null +++ b/application/src/Api/Adapter/SiteSlugTrait.php @@ -0,0 +1,71 @@ +getId(); + if (!$siteId) { + return null; + } + } + + $slug = $this->slugify($title); + + $em = $this->getServiceLocator()->get('Omeka\EntityManager'); + $conn = $this->getServiceLocator()->get('Omeka\Connection'); + $table = $em->getClassMetadata($this->getEntityClass())->getTableName(); + $qb = $conn->createQueryBuilder(); + $where = 'slug LIKE ' . $qb->createPositionalParameter($slug . '%'); + if ($site) { + $where = $qb->expr()->andX( + $where, + 'site_id = ' . $qb->createPositionalParameter($siteId) + ); + } + $qb->select('slug') + ->from($table) + ->where($where); + $stmt = $qb->execute(); + $similarSlugs = $stmt->fetchAll(PDO::FETCH_COLUMN); + + if (!($similarSlugs && in_array($slug, $similarSlugs))) { + return $slug; + } + + $suffix = 1; + while (in_array($slug . $suffix, $similarSlugs)) { + $suffix++; + } + return $slug . $suffix; + } + + /** + * Transform the given string into a valid URL slug + * + * @param string $input + * @return string + */ + protected function slugify($input) + { + $slug = mb_strtolower($input, 'UTF-8'); + $slug = preg_replace('/[^a-z0-9-]+/u', '-', $slug); + $slug = preg_replace('/-{2,}/', '-', $slug); + $slug = preg_replace('/-*$/', '', $slug); + return $slug; + } +} diff --git a/application/src/Api/Representation/ResourceTemplatePropertyRepresentation.php b/application/src/Api/Representation/ResourceTemplatePropertyRepresentation.php index 9f5e017abc..1471412459 100644 --- a/application/src/Api/Representation/ResourceTemplatePropertyRepresentation.php +++ b/application/src/Api/Representation/ResourceTemplatePropertyRepresentation.php @@ -86,4 +86,13 @@ public function dataType() { return $this->templateProperty->getDataType(); } + + /** + * @return string + */ + public function dataTypeLabel() + { + return $this->getServiceLocator()->get('Omeka\DataTypeManager') + ->get($this->dataType())->getLabel(); + } } diff --git a/application/src/Controller/Admin/ItemController.php b/application/src/Controller/Admin/ItemController.php index 97f14b24a0..22cae1e8f0 100644 --- a/application/src/Controller/Admin/ItemController.php +++ b/application/src/Controller/Admin/ItemController.php @@ -54,7 +54,7 @@ public function showDetailsAction() $view = new ViewModel; $view->setTerminal(true); $view->setVariable('linkTitle', $linkTitle); - $view->setVariable('item', $item); + $view->setVariable('resource', $item); $view->setVariable('values', json_encode($values)); return $view; } @@ -74,6 +74,25 @@ public function sidebarSelectAction() return $view; } + public function deleteConfirmAction() + { + $linkTitle = (bool) $this->params()->fromQuery('link-title', true); + $response = $this->api()->read('items', $this->params('id')); + $item = $response->getContent(); + $values = $item->valueRepresentation(); + + $view = new ViewModel; + $view->setTerminal(true); + $view->setTemplate('common/delete-confirm-details'); + $view->setVariable('partialPath', 'omeka/admin/item/show-details'); + $view->setVariable('resourceLabel', 'item'); + $view->setVariable('linkTitle', $linkTitle); + $view->setVariable('resource', $item); + $view->setVariable('item', $item); + $view->setVariable('values', json_encode($values)); + return $view; + } + public function deleteAction() { if ($this->getRequest()->isPost()) { @@ -140,11 +159,6 @@ public function editAction() $view->setVariable('form', $form); $view->setVariable('item', $item); $view->setVariable('mediaForms', $this->getMediaForms()); - $view->setVariable('confirmForm', new ConfirmForm( - $this->getServiceLocator(), null, [ - 'button_value' => $this->translate('Confirm Delete'), - ] - )); if ($this->getRequest()->isPost()) { $data = $this->params()->fromPost(); diff --git a/application/src/Controller/Admin/ItemSetController.php b/application/src/Controller/Admin/ItemSetController.php index fdc6f4d0be..8424a88a7b 100644 --- a/application/src/Controller/Admin/ItemSetController.php +++ b/application/src/Controller/Admin/ItemSetController.php @@ -112,7 +112,7 @@ public function showDetailsAction() $view = new ViewModel; $view->setTerminal(true); $view->setVariable('linkTitle', $linkTitle); - $view->setVariable('itemSet', $itemSet); + $view->setVariable('resource', $itemSet); $view->setVariable('values', json_encode($values)); return $view; } @@ -131,6 +131,24 @@ public function sidebarSelectAction() return $view; } + public function deleteConfirmAction() + { + $linkTitle = (bool) $this->params()->fromQuery('link-title', true); + $response = $this->api()->read('item_sets', $this->params('id')); + $itemSet = $response->getContent(); + $values = $itemSet->valueRepresentation(); + + $view = new ViewModel; + $view->setTerminal(true); + $view->setTemplate('common/delete-confirm-details'); + $view->setVariable('partialPath', 'omeka/admin/item-set/show-details'); + $view->setVariable('resourceLabel', 'item set'); + $view->setVariable('linkTitle', $linkTitle); + $view->setVariable('resource', $itemSet); + $view->setVariable('values', json_encode($values)); + return $view; + } + public function deleteAction() { if ($this->getRequest()->isPost()) { diff --git a/application/src/Controller/Admin/JobController.php b/application/src/Controller/Admin/JobController.php index 72fcbdff24..a79754c233 100644 --- a/application/src/Controller/Admin/JobController.php +++ b/application/src/Controller/Admin/JobController.php @@ -16,32 +16,27 @@ public function browseAction() $view = new ViewModel; $view->setVariable('jobs', $response->getContent()); - $view->setVariable('confirmForm', new ConfirmForm( - $this->getServiceLocator(), null, [ - 'button_value' => $this->translate('Attempt Stop'), - ] - )); return $view; } - public function showDetailsAction() + public function showAction() { $response = $this->api()->read('jobs', $this->params('id')); $view = new ViewModel; - $view->setTerminal(true); - $view->setVariable('job', $response->getContent()); - return $view; - } + $job = $response->getContent(); + $view->setVariable('job', $job); + $view->setVariable('resource', $job); - public function argsAction() - { - $job = $this->api()->read('jobs', $this->params('id'))->getContent(); - $args = json_encode($job->args(), JSON_PRETTY_PRINT); - $response = $this->getResponse(); - $response->getHeaders()->addHeaderLine('Content-Type', 'text/plain; charset=utf-8'); - $response->setContent($args); - return $response; + $form = new ConfirmForm( + $this->getServiceLocator(), null, [ + 'button_value' => $this->translate('Attempt Stop'), + ] + ); + $form->setAttribute('action', $job->url('stop')); + $view->setVariable('confirmForm', $form); + + return $view; } public function logAction() diff --git a/application/src/Controller/Admin/MediaController.php b/application/src/Controller/Admin/MediaController.php index 770cbea637..de8489542b 100644 --- a/application/src/Controller/Admin/MediaController.php +++ b/application/src/Controller/Admin/MediaController.php @@ -83,7 +83,25 @@ public function showDetailsAction() $view = new ViewModel; $view->setTerminal(true); $view->setVariable('linkTitle', $linkTitle); - $view->setVariable('media', $media); + $view->setVariable('resource', $media); + $view->setVariable('values', json_encode($values)); + return $view; + } + + public function deleteConfirmAction() + { + $linkTitle = (bool) $this->params()->fromQuery('link-title', true); + $response = $this->api()->read('media', $this->params('id')); + $media = $response->getContent(); + $values = $media->valueRepresentation(); + + $view = new ViewModel; + $view->setTerminal(true); + $view->setTemplate('common/delete-confirm-details'); + $view->setVariable('partialPath', 'omeka/admin/media/show-details'); + $view->setVariable('resourceLabel', 'media'); + $view->setVariable('linkTitle', $linkTitle); + $view->setVariable('resource', $media); $view->setVariable('values', json_encode($values)); return $view; } diff --git a/application/src/Controller/Admin/ModuleController.php b/application/src/Controller/Admin/ModuleController.php index 61cc7bd520..a86157f195 100644 --- a/application/src/Controller/Admin/ModuleController.php +++ b/application/src/Controller/Admin/ModuleController.php @@ -57,9 +57,6 @@ public function browseAction() ['module_action' => $action, 'module_id' => $id] ); }); - $view->setVariable('uninstallForm', new ModuleUninstallForm( - $this->getServiceLocator(), 'uninstall' - )); return $view; } @@ -100,6 +97,27 @@ public function installAction() return $this->redirect()->toRoute(null, ['action' => 'browse'], true); } + public function uninstallConfirmAction() + { + $id = $this->params()->fromQuery('id'); + $manager = $this->getServiceLocator()->get('Omeka\ModuleManager'); + $module = $manager->getModule($id); + if (!$module) { + throw new Exception\NotFoundException; + } + $uninstallForm = new ModuleUninstallForm( + $this->getServiceLocator(), 'uninstall' + ); + $uninstallForm->setAttribute('action', $this->url()->fromRoute(null, ['action' => 'uninstall'], ['query' => ['id' => $module->getId()]], true)); + + $view = new ViewModel; + $view->setTerminal(true); + $view->setTemplate('omeka/admin/module/uninstall-confirm'); + $view->setVariable('uninstallForm', $uninstallForm); + $view->setVariable('module', $module); + return $view; + } + /** * Uninstall a module. */ diff --git a/application/src/Controller/Admin/ResourceTemplateController.php b/application/src/Controller/Admin/ResourceTemplateController.php index 510d226801..4bbda2a132 100644 --- a/application/src/Controller/Admin/ResourceTemplateController.php +++ b/application/src/Controller/Admin/ResourceTemplateController.php @@ -1,4 +1,4 @@ -setTerminal(true); - $view->setVariable('resourceTemplate', $response->getContent()); + $view->setVariable('resource', $response->getContent()); + return $view; + } + + public function deleteConfirmAction() + { + $response = $this->api()->read('resource_templates', $this->params('id')); + $resourceTemplate = $response->getContent(); + + $view = new ViewModel; + $view->setTerminal(true); + $view->setTemplate('common/delete-confirm-details'); + $view->setVariable('partialPath', 'omeka/admin/resource-template/show-details'); + $view->setVariable('resourceLabel', 'resource template'); + $view->setVariable('resource', $resourceTemplate); return $view; } diff --git a/application/src/Controller/Admin/UserController.php b/application/src/Controller/Admin/UserController.php index 43d0786744..9261fcc6ff 100644 --- a/application/src/Controller/Admin/UserController.php +++ b/application/src/Controller/Admin/UserController.php @@ -80,7 +80,21 @@ public function showDetailsAction() $view = new ViewModel; $view->setTerminal(true); - $view->setVariable('user', $response->getContent()); + $view->setVariable('resource', $response->getContent()); + return $view; + } + + public function deleteConfirmAction() + { + $response = $this->api()->read('users', $this->params('id')); + $user = $response->getContent(); + + $view = new ViewModel; + $view->setTerminal(true); + $view->setTemplate('common/delete-confirm-details'); + $view->setVariable('partialPath', 'omeka/admin/user/show-details'); + $view->setVariable('resourceLabel', 'user'); + $view->setVariable('resource', $user); return $view; } diff --git a/application/src/Controller/Admin/VocabularyController.php b/application/src/Controller/Admin/VocabularyController.php index 33edb37e7f..7c160092bc 100644 --- a/application/src/Controller/Admin/VocabularyController.php +++ b/application/src/Controller/Admin/VocabularyController.php @@ -1,4 +1,4 @@ -setTerminal(true); - $view->setVariable('vocabulary', $response->getContent()); + $view->setVariable('resource', $response->getContent()); + return $view; + } + + public function deleteConfirmAction() + { + $response = $this->api()->read('vocabularies', $this->params('id')); + $vocabulary = $response->getContent(); + + $view = new ViewModel; + $view->setTerminal(true); + $view->setTemplate('common/delete-confirm-details'); + $view->setVariable('partialPath', 'omeka/admin/vocabulary/show-details'); + $view->setVariable('resourceLabel', 'vocabulary'); + $view->setVariable('resource', $vocabulary); return $view; } diff --git a/application/src/Controller/SiteAdmin/IndexController.php b/application/src/Controller/SiteAdmin/IndexController.php index 4cf51644e9..97d7a97868 100644 --- a/application/src/Controller/SiteAdmin/IndexController.php +++ b/application/src/Controller/SiteAdmin/IndexController.php @@ -56,7 +56,6 @@ public function addAction() public function editAction() { $serviceLocator = $this->getServiceLocator(); - $translator = $serviceLocator->get('Omeka\Site\NavigationTranslator'); $form = new SiteForm($serviceLocator); $readResponse = $this->api()->read('sites', [ 'slug' => $this->params('site-slug') @@ -279,6 +278,53 @@ public function usersAction() return $view; } + public function themeAction() + { + $readResponse = $this->api()->read('sites', [ + 'slug' => $this->params('site-slug') + ]); + $site = $readResponse->getContent(); + + $services = $this->getServiceLocator(); + $tm = $services->get('Omeka\Site\ThemeManager'); + $settings = $services->get('Omeka\SiteSettings'); + + $theme = $tm->getTheme($site->theme()); + $settingsKey = $theme->getSettingsKey(); + $config = $theme->getConfigSpec(); + + $view = new ViewModel; + $this->layout()->setVariable('site', $site); + if (!($config && $config['elements'])) { + return $view; + } + + $form = new Form($services); + foreach ($config['elements'] as $elementSpec) { + $form->add($elementSpec); + } + + $oldSettings = $settings->get($theme->getSettingsKey()); + if ($oldSettings) { + $form->setData($oldSettings); + } + + if ($this->getRequest()->isPost()) { + $form->setData($this->params()->fromPost()); + if ($form->isValid()) { + $data = $form->getData(); + unset($data['csrf']); + $settings->set($settingsKey, $data); + $this->messenger()->addSuccess('Theme settings updated.'); + return $this->redirect()->refresh(); + } else { + $this->messenger()->addError('There was an error during validation'); + } + } + $view->setVariable('form', $form); + return $view; + } + public function deleteAction() { if ($this->getRequest()->isPost()) { @@ -313,16 +359,19 @@ public function showAction() return $view; } - public function showDetailsAction() + public function deleteConfirmAction() { $response = $this->api()->read('sites', [ 'slug' => $this->params('site-slug') ]); $site = $response->getContent(); + $view = new ViewModel; $view->setTerminal(true); - - $view->setVariable('site', $site); + $view->setTemplate('common/delete-confirm-details'); + $view->setVariable('resourceLabel', 'site'); + $view->setVariable('partialPath', 'omeka/site-admin/index/show-details'); + $view->setVariable('resource', $site); return $view; } diff --git a/application/src/Controller/SiteAdmin/PageController.php b/application/src/Controller/SiteAdmin/PageController.php index 59392c8f5b..6c326aa4c2 100644 --- a/application/src/Controller/SiteAdmin/PageController.php +++ b/application/src/Controller/SiteAdmin/PageController.php @@ -95,6 +95,27 @@ public function indexAction() return $view; } + public function deleteConfirmAction() + { + $response = $this->api()->read('sites', [ + 'slug' => $this->params('site-slug') + ]); + $site = $response->getContent(); + $response = $this->api()->read('site_pages', [ + 'slug' => $this->params('page-slug'), + 'site' => $site->id() + ]); + $page = $response->getContent(); + + $view = new ViewModel; + $view->setTerminal(true); + $view->setTemplate('common/delete-confirm-details'); + $view->setVariable('partialPath', 'omeka/site-admin/page/show-details'); + $view->setVariable('resourceLabel', 'page'); + $view->setVariable('resource', $page); + return $view; + } + public function deleteAction() { if ($this->getRequest()->isPost()) { diff --git a/application/src/Form/SiteForm.php b/application/src/Form/SiteForm.php index b32f6c52fa..1d44adccd2 100644 --- a/application/src/Form/SiteForm.php +++ b/application/src/Form/SiteForm.php @@ -9,25 +9,25 @@ public function buildForm() $this->setAttribute('id', 'site-form'); $this->add([ - 'name' => 'o:slug', + 'name' => 'o:title', 'type' => 'Text', 'options' => [ - 'label' => $translator->translate('URL slug') + 'label' => $translator->translate('Title'), ], 'attributes' => [ - 'id' => 'slug', + 'id' => 'title', 'required' => true, ], ]); $this->add([ - 'name' => 'o:title', + 'name' => 'o:slug', 'type' => 'Text', 'options' => [ - 'label' => $translator->translate('Title'), + 'label' => $translator->translate('URL slug') ], 'attributes' => [ - 'id' => 'title', - 'required' => true, + 'id' => 'slug', + 'required' => false, ], ]); $themeManager = $this->getServiceLocator()->get('Omeka\Site\ThemeManager'); diff --git a/application/src/Form/SitePageForm.php b/application/src/Form/SitePageForm.php index 1ebb154cd4..9147ee2b86 100644 --- a/application/src/Form/SitePageForm.php +++ b/application/src/Form/SitePageForm.php @@ -8,25 +8,25 @@ public function buildForm() $translator = $this->getTranslator(); $this->add([ - 'name' => 'o:slug', + 'name' => 'o:title', 'type' => 'Text', 'options' => [ - 'label' => $translator->translate('URL slug') + 'label' => $translator->translate('Title'), ], 'attributes' => [ - 'id' => 'slug', + 'id' => 'title', 'required' => true, ], ]); $this->add([ - 'name' => 'o:title', + 'name' => 'o:slug', 'type' => 'Text', 'options' => [ - 'label' => $translator->translate('Title'), + 'label' => $translator->translate('URL slug') ], 'attributes' => [ - 'id' => 'title', - 'required' => true, + 'id' => 'slug', + 'required' => false, ], ]); } diff --git a/application/src/Job/Strategy/SynchronousStrategy.php b/application/src/Job/Strategy/SynchronousStrategy.php index eb29c48a79..53a74b5c8e 100644 --- a/application/src/Job/Strategy/SynchronousStrategy.php +++ b/application/src/Job/Strategy/SynchronousStrategy.php @@ -2,6 +2,7 @@ namespace Omeka\Job\Strategy; use DateTime; +use Doctrine\ORM\EntityManager; use Omeka\Entity\Job; use Zend\ServiceManager\ServiceLocatorAwareTrait; @@ -15,6 +16,7 @@ class SynchronousStrategy implements StrategyInterface public function send(Job $job) { $entityManager = $this->getServiceLocator()->get('Omeka\EntityManager'); + register_shutdown_function([$this, 'handleFatalError'], $job, $entityManager); $job->setStatus(Job::STATUS_IN_PROGRESS); $entityManager->flush(); @@ -31,4 +33,28 @@ public function send(Job $job) $job->setEnded(new DateTime('now')); $entityManager->flush(); } + + /** + * Log status and message if job terminates with a fatal error + * + * @param Job $job + * @param EntityManager $entityManager + */ + public function handleFatalError(Job $job, EntityManager $entityManager) + { + $lastError = error_get_last(); + if ($lastError && $lastError['type'] === E_ERROR) { + $job->setStatus(Job::STATUS_ERROR); + $job->addLog(sprintf("Fatal error: %s\nin %s on line %s", + $lastError['message'], + $lastError['file'], + $lastError['line'] + )); + + // Make sure we only flush this Job and nothing else + $entityManager->clear(); + $entityManager->merge($job); + $entityManager->flush(); + } + } } diff --git a/application/src/Mvc/MvcListeners.php b/application/src/Mvc/MvcListeners.php index d9488f8377..d852c5b1f2 100644 --- a/application/src/Mvc/MvcListeners.php +++ b/application/src/Mvc/MvcListeners.php @@ -3,6 +3,7 @@ use Zend\EventManager\EventManagerInterface; use Zend\EventManager\AbstractListenerAggregate; +use Zend\Mvc\Application as ZendApplication; use Zend\Mvc\MvcEvent; class MvcListeners extends AbstractListenerAggregate @@ -193,13 +194,10 @@ public function prepareAdmin(MvcEvent $event) $serviceLocator = $event->getApplication()->getServiceManager(); - if ($routeMatch->getParam('__SITEADMIN__')) { + if ($routeMatch->getParam('__SITEADMIN__') && $routeMatch->getParam('site-slug')) { $site = $this->getSite($serviceLocator, $routeMatch->getParam('site-slug')); - if ($site) { - // Set the current site as the default site for site settings. - $siteSettings = $serviceLocator->get('Omeka\SiteSettings'); - $siteSettings->setSite($site); - } + $siteSettings = $serviceLocator->get('Omeka\SiteSettings'); + $siteSettings->setSite($site); } $serviceLocator->get('ViewTemplatePathStack') @@ -218,30 +216,27 @@ public function prepareSite(MvcEvent $event) return; } - $serviceLocator = $event->getApplication()->getServiceManager(); + $application = $event->getApplication(); + $serviceLocator = $application->getServiceManager(); - $site = $this->getSite($serviceLocator, $routeMatch->getParam('site-slug')); - if (!$site) { - // Site not found, set minimal layout and 404 status - $event->getViewModel()->setTemplate('error/404'); - $event->getResponse()->setStatusCode(404); + try { + $site = $this->getSite($serviceLocator, $routeMatch->getParam('site-slug')); + } catch (\Exception $e) { + $event->setError(ZendApplication::ERROR_EXCEPTION); + $event->setParam('exception', $e); + $application->getEventManager()->trigger(MvcEvent::EVENT_DISPATCH_ERROR, $event); return; } + // Set the site to the top level view model + $event->getViewModel()->site = $site; + // Set the current site as the default site for site settings. $siteSettings = $serviceLocator->get('Omeka\SiteSettings'); $siteSettings->setSite($site); - $acl = $serviceLocator->get('Omeka\Acl'); - if (!$acl->userIsAllowed($site, 'read')) { - // Site is restricted, set minimal layout and 404 status - $event->getViewModel()->setTemplate('error/404'); - $event->getResponse()->setStatusCode(404); - return; - } - // Set the current theme. - $theme = $site->getTheme(); + $theme = $site->theme(); $themeManager = $serviceLocator->get('Omeka\Site\ThemeManager'); $themeManager->setCurrentTheme($theme); @@ -276,13 +271,12 @@ public function prepareSite(MvcEvent $event) * * @param ServiceManager $serviceLocator * @param string $slug - * @return Site|null + * @return SiteRepresentation|null */ protected function getSite($serviceLocator, $slug) { - return $serviceLocator->get('Omeka\EntityManager') - ->createQuery('SELECT s FROM Omeka\Entity\Site s WHERE s.slug = :slug') - ->setParameter('slug', $slug) - ->getOneOrNullResult(); + $api = $serviceLocator->get('Omeka\ApiManager'); + $response = $api->read('sites', ['slug' => $slug]); + return $response->getContent(); } } diff --git a/application/src/Service/AclFactory.php b/application/src/Service/AclFactory.php index ea2cdccd3f..c33e8c9b09 100644 --- a/application/src/Service/AclFactory.php +++ b/application/src/Service/AclFactory.php @@ -737,11 +737,6 @@ protected function addRulesForSiteAdmin(Acl $acl) 'Omeka\Api\Adapter\VocabularyAdapter', ['create', 'update', 'delete'] ); - $acl->deny( - 'site_admin', - 'Omeka\Entity\Media', - ['create', 'update', 'delete'] - ); $acl->deny( 'site_admin', diff --git a/application/src/Service/ViewHelperThemeSettingFactory.php b/application/src/Service/ViewHelperThemeSettingFactory.php new file mode 100644 index 0000000000..d7ac8be81e --- /dev/null +++ b/application/src/Service/ViewHelperThemeSettingFactory.php @@ -0,0 +1,29 @@ +getServiceLocator(); + $currentTheme = $serviceLocator->get('Omeka\Site\ThemeManager')->getCurrentTheme(); + $siteSettings = $serviceLocator->get('Omeka\SiteSettings'); + + $themeSettings = $siteSettings->get($currentTheme->getSettingsKey(), []); + return new ThemeSetting($themeSettings); + } +} diff --git a/application/src/Session/SaveHandler/Db.php b/application/src/Session/SaveHandler/Db.php index 078a1edf8c..4d2ea698bc 100644 --- a/application/src/Session/SaveHandler/Db.php +++ b/application/src/Session/SaveHandler/Db.php @@ -102,7 +102,7 @@ public function destroy($id) */ public function gc($maxlifetime) { - $sql = 'DELETE FROM session WHERE modified > ?'; + $sql = 'DELETE FROM session WHERE modified < ?'; $stmt = $this->conn->prepare($sql); $stmt->bindValue(1, (time() - $this->lifetime)); return $stmt->execute(); diff --git a/application/src/Settings/SiteSettings.php b/application/src/Settings/SiteSettings.php index 6997e7094b..311e72cd7e 100644 --- a/application/src/Settings/SiteSettings.php +++ b/application/src/Settings/SiteSettings.php @@ -1,33 +1,40 @@ site = $site; + if ($site instanceof Site) { + $this->siteId = $site->getId(); + } else if ($site instanceof SiteRepresentation) { + $this->siteId = $site->id(); + } else { + $this->siteId = null; + } } protected function setCache() { - if (!$this->site instanceof Site) { + if (!$this->siteId) { throw new Exception\RuntimeException('Cannot use site settings when no site is set'); } $conn = $this->getConnection(); - $settings = $conn->fetchAll('SELECT * FROM site_setting WHERE site_id = ?', [$this->site->getId()]); + $settings = $conn->fetchAll('SELECT * FROM site_setting WHERE site_id = ?', [$this->siteId]); foreach ($settings as $setting) { $this->cache[$setting['id']] = $conn->convertToPHPValue($setting['value'], 'json_array'); } @@ -35,8 +42,12 @@ protected function setCache() protected function setSetting($id, $value) { + if (!$this->siteId) { + throw new Exception\RuntimeException('Cannot use site settings when no site is set'); + } + $conn = $this->getConnection(); - $siteId = $this->site->getId(); + $siteId = $this->siteId; $setting = $conn->fetchAssoc( 'SELECT * FROM site_setting WHERE id = ? AND site_id = ?', [$id, $siteId] @@ -55,8 +66,12 @@ protected function setSetting($id, $value) protected function deleteSetting($id) { + if (!$this->siteId) { + throw new Exception\RuntimeException('Cannot use site settings when no site is set'); + } + $this->getConnection()->delete('site_setting', [ - 'site_id' => $this->site->getId(), + 'site_id' => $this->siteId, 'id' => $id, ], [\PDO::PARAM_INT]); } diff --git a/application/src/Site/BlockLayout/ItemWithMetadata.php b/application/src/Site/BlockLayout/ItemWithMetadata.php new file mode 100644 index 0000000000..457208b0ed --- /dev/null +++ b/application/src/Site/BlockLayout/ItemWithMetadata.php @@ -0,0 +1,59 @@ +getServiceLocator()->get('MvcTranslator'); + return $translator->translate('Item with Metadata'); + } + + public function form(PhpRenderer $view, SiteRepresentation $site, + SitePageBlockRepresentation $block = null + ) { + return $this->attachmentsForm($view, $site, $block); + } + + public function render(PhpRenderer $view, SitePageBlockRepresentation $block) + { + $attachments = $block->attachments(); + if (!$attachments) { + return 'foo'; + } + + $html = ''; + foreach($attachments as $attachment) { + $item = $attachment->item(); + $translator = $this->getServiceLocator()->get('MvcTranslator'); + $html .= $item->displayValues(); + if($item->itemSets()) { + $html .= '
'; + $html .= '

' . $translator->translate('Item Sets') . '

'; + + foreach ($item->itemSets() as $itemSet) { + $html .= ''; + } + $html .= '
'; + } + } + + if($item->media()) { + $html .= '
'; + + foreach ($item->media() as $media) { + $html .= ''; + $html .= ''; + $html .= '' . $view->escapeHtml(($media->displayTitle())) . ''; + $html .= ''; + } + } + + $html .= '
'; + return $html; + } +} diff --git a/application/src/Site/BlockLayout/TableOfContents.php b/application/src/Site/BlockLayout/TableOfContents.php index cca60730bd..d6aa0fb7b3 100644 --- a/application/src/Site/BlockLayout/TableOfContents.php +++ b/application/src/Site/BlockLayout/TableOfContents.php @@ -47,8 +47,14 @@ public function render(PhpRenderer $view, SitePageBlockRepresentation $block) $nav = $view->navigation('Zend\Navigation\Site'); $container = $nav->getContainer(); $activePage = $nav->findActive($container); + + // Make new copies of the pages so we don't disturb the regular nav $pages = $activePage['page']->getPages(); - $subNav = new Navigation($pages); + $newPages = []; + foreach ($pages as $page) { + $newPages[] = $page->toArray(); + } + $subNav = new Navigation($newPages); $depth = $this->getData($block->data(), 'depth'); if (!isset($depth)) { diff --git a/application/src/Site/Navigation/Link/Browse.php b/application/src/Site/Navigation/Link/Browse.php index 18549f3996..e08a7805ea 100644 --- a/application/src/Site/Navigation/Link/Browse.php +++ b/application/src/Site/Navigation/Link/Browse.php @@ -1,7 +1,6 @@ Query '; } - public function toZend(array $data, Site $site) + public function toZend(array $data, SiteRepresentation $site) { parse_str($data['query'], $query); return [ 'label' => $data['label'], 'route' => 'site/resource', 'params' => [ - 'site-slug' => $site->getSlug(), + 'site-slug' => $site->slug(), 'controller' => 'item', 'action' => 'browse', ], diff --git a/application/src/Site/Navigation/Link/Fallback.php b/application/src/Site/Navigation/Link/Fallback.php index 535595b7b6..38f788b488 100644 --- a/application/src/Site/Navigation/Link/Fallback.php +++ b/application/src/Site/Navigation/Link/Fallback.php @@ -1,7 +1,6 @@ Data '; } - public function toZend(array $data, Site $site) + public function toZend(array $data, SiteRepresentation $site) { return [ 'type' => 'uri', diff --git a/application/src/Site/Navigation/Link/LinkInterface.php b/application/src/Site/Navigation/Link/LinkInterface.php index 797fb03266..cc068c476d 100644 --- a/application/src/Site/Navigation/Link/LinkInterface.php +++ b/application/src/Site/Navigation/Link/LinkInterface.php @@ -1,7 +1,6 @@ Label '; } - public function toZend(array $data, Site $site) + public function toZend(array $data, SiteRepresentation $site) { - $sitePage = $site->getPages()->get($data['id']); - if (!$sitePage) { + $pages = $site->pages(); + if (!isset($pages[$data['id']])) { // Handle an invalid page. $fallback = new Fallback('page'); $fallback->setServiceLocator($this->getServiceLocator()); return $fallback->toZend($data, $site); } + $sitePage = $pages[$data['id']]; return [ 'label' => $data['label'], 'route' => 'site/page', 'params' => [ - 'site-slug' => $site->getSlug(), - 'page-slug' => $sitePage->getSlug(), + 'site-slug' => $site->slug(), + 'page-slug' => $sitePage->slug(), ], ]; } diff --git a/application/src/Site/Navigation/Link/Url.php b/application/src/Site/Navigation/Link/Url.php index 7d6aeb65cf..ae45d27cb6 100644 --- a/application/src/Site/Navigation/Link/Url.php +++ b/application/src/Site/Navigation/Link/Url.php @@ -1,7 +1,6 @@ URL '; } - public function toZend(array $data, Site $site) + public function toZend(array $data, SiteRepresentation $site) { return [ 'type' => 'uri', diff --git a/application/src/Site/Navigation/Translator.php b/application/src/Site/Navigation/Translator.php index ee64592c1d..1a40a98477 100644 --- a/application/src/Site/Navigation/Translator.php +++ b/application/src/Site/Navigation/Translator.php @@ -1,7 +1,6 @@ getServiceLocator()->get('Omeka\Site\NavigationLinkManager'); $buildLinks = function ($linksIn) use (&$buildLinks, $site, $manager) @@ -30,14 +29,14 @@ public function toZend(Site $site) } return $linksOut; }; - $links = $buildLinks($site->getNavigation()); + $links = $buildLinks($site->navigation()); if (!$links) { // The site must have at least one page for navigation to work. $links = [[ 'label' => 'Home', 'route' => 'site', 'params' => [ - 'site-slug' => $site->getSlug(), + 'site-slug' => $site->slug(), ], ]]; } diff --git a/application/src/Site/Theme/Theme.php b/application/src/Site/Theme/Theme.php index 10c7bb056f..e1fa06756a 100644 --- a/application/src/Site/Theme/Theme.php +++ b/application/src/Site/Theme/Theme.php @@ -58,4 +58,17 @@ public function getName() { return $this->getIni('name') ?: $this->getId(); } + + /** + * Get the spec for this theme's configuration form. + */ + public function getConfigSpec() + { + return $this->getIni('config') ?: []; + } + + public function getSettingsKey() + { + return "theme_settings_" . $this->getId(); + } } diff --git a/application/src/View/Helper/DeleteConfirm.php b/application/src/View/Helper/DeleteConfirm.php new file mode 100644 index 0000000000..45d313eccf --- /dev/null +++ b/application/src/View/Helper/DeleteConfirm.php @@ -0,0 +1,17 @@ +getView()->partial( + 'common/delete-confirm', + [ + 'resourceLabel' => $resourceLabel, + 'resource' => $resource + ] + ); + } +} diff --git a/application/src/View/Helper/DeleteConfirmForm.php b/application/src/View/Helper/DeleteConfirmForm.php new file mode 100644 index 0000000000..fcf567354e --- /dev/null +++ b/application/src/View/Helper/DeleteConfirmForm.php @@ -0,0 +1,30 @@ +serviceLocator = $serviceLocator; + } + + public function __invoke($resource, $buttonText = null) { + $translate = $this->getView()->plugin('translate'); + if (!isset($buttonText)) { + $buttonText = $translate('Confirm delete'); + } + $confirmForm = new ConfirmForm( + $this->serviceLocator, null, [ + 'button_value' => $buttonText + ] + ); + $confirmForm->setAttribute('action', $resource->url('delete')); + return $confirmForm; + } +} diff --git a/application/src/View/Helper/PropertySelector.php b/application/src/View/Helper/PropertySelector.php index 0d871c60f2..2b532de36a 100644 --- a/application/src/View/Helper/PropertySelector.php +++ b/application/src/View/Helper/PropertySelector.php @@ -37,7 +37,7 @@ public function __invoke($propertySelectorText = null, $active = true) 'vocabularies' => $vocabResponse->getContent(), 'totalPropertyCount' => $propResponse->getTotalResults(), 'propertySelectorText' => $propertySelectorText, - 'state' => $active ? 'active' : '' + 'state' => $active ? 'always-open' : '' ] ); } diff --git a/application/src/View/Helper/ThemeSetting.php b/application/src/View/Helper/ThemeSetting.php new file mode 100644 index 0000000000..9dadf8c01f --- /dev/null +++ b/application/src/View/Helper/ThemeSetting.php @@ -0,0 +1,44 @@ +settings = $settings; + } + + /** + * Get a setting + * + * By default, will return null if no setting exists with the passed ID, but the default + * can be changed by passing the second argument + * + * @param string $id + * @param mixed $default + * @return mixed + */ + public function __invoke($id, $default = null) + { + if (isset($this->settings[$id])) { + return $this->settings[$id]; + } + + return $default; + } +} diff --git a/application/src/View/Helper/Trigger.php b/application/src/View/Helper/Trigger.php index 0b9f38710b..5fcfd0689f 100644 --- a/application/src/View/Helper/Trigger.php +++ b/application/src/View/Helper/Trigger.php @@ -23,8 +23,9 @@ public function __construct(ServiceLocatorInterface $serviceLocator) * * @param string $name The event name * @param array $params The event parameters + * @param bool $filter Filter and return parameters? */ - public function __invoke($name, array $params = []) + public function __invoke($name, array $params = [], $filter = false) { $routeMatch = $this->serviceLocator->get('Application') ->getMvcEvent()->getRouteMatch(); @@ -35,8 +36,14 @@ public function __invoke($name, array $params = []) // Set the event, using the current controller as the event identifier. $params['services'] = $this->serviceLocator; + if ($filter) { + $params = $this->events->prepareArgs($params); + } $event = new Event($name, $this->getView(), $params); $this->events->setIdentifiers($routeMatch->getParam('controller')); $this->events->trigger($event); + if ($filter) { + return $params; + } } } diff --git a/application/view-admin/common/delete-confirm-details.phtml b/application/view-admin/common/delete-confirm-details.phtml new file mode 100644 index 0000000000..f0203f8f37 --- /dev/null +++ b/application/view-admin/common/delete-confirm-details.phtml @@ -0,0 +1,12 @@ +plugin('escapeHtml'); +$confirmForm = $this->deleteConfirmForm($resource); + ?> + + + partial($partialPath); ?> + \ No newline at end of file diff --git a/application/view-admin/common/delete-confirm.phtml b/application/view-admin/common/delete-confirm.phtml new file mode 100644 index 0000000000..30b9c2fd8c --- /dev/null +++ b/application/view-admin/common/delete-confirm.phtml @@ -0,0 +1,10 @@ +plugin('escapeHtml'); ?> + diff --git a/application/view-admin/layout/header.phtml b/application/view-admin/layout/header.phtml index 13f5faee38..41aa5a9a9b 100644 --- a/application/view-admin/layout/header.phtml +++ b/application/view-admin/layout/header.phtml @@ -10,10 +10,10 @@ $escape = $this->plugin('escapeHtml');
identity()): ?> -

translate('Welcome, %s'), 'url('admin/id', [ 'controller' => 'user', 'id' => $this->identity()->getId(), - )) . '">' . $escape($this->identity()->getName()) . ''); ?>

+ ]) . '">' . $escape($this->identity()->getName()) . ''); ?>

translate('Logout'); ?>

translate('not logged in'); ?>

@@ -27,9 +27,9 @@ $escape = $this->plugin('escapeHtml');
translate('Resource Type'); ?> - - - + + +
@@ -38,7 +38,7 @@ $escape = $this->plugin('escapeHtml');

link($site->title(), 'show'); ?>

- + navigation('Zend\Navigation\Site')->menu(); ?> navigation('Zend\Navigation\Admin')->menu(); ?> diff --git a/application/view-admin/omeka/admin/item-set/add.phtml b/application/view-admin/omeka/admin/item-set/add.phtml index b95e25003f..1a1445a467 100644 --- a/application/view-admin/omeka/admin/item-set/add.phtml +++ b/application/view-admin/omeka/admin/item-set/add.phtml @@ -5,10 +5,10 @@ $this->htmlElement('body')->appendAttribute('class', 'add item-set sidebar-open' pageTitle($this->translate('Add Item Set')); ?> partial('omeka/admin/item-set/form.phtml', array( +echo $this->partial('omeka/admin/item-set/form.phtml', [ 'form' => $form, 'itemSet' => null, -)); +]); ?> trigger('view.add.after'); ?> diff --git a/application/view-admin/omeka/admin/item-set/browse.phtml b/application/view-admin/omeka/admin/item-set/browse.phtml index 90856f1695..c53c0129e8 100644 --- a/application/view-admin/omeka/admin/item-set/browse.phtml +++ b/application/view-admin/omeka/admin/item-set/browse.phtml @@ -1,6 +1,5 @@ htmlElement('body')->appendAttribute('class', 'item-sets browse'); -$confirmForm->prepare(); $escape = $this->plugin('escapeHtml'); $sortHeadings = [ [ @@ -28,13 +27,13 @@ $sortHeadings = [
userIsAllowed('Omeka\Api\Adapter\ItemSetAdapter', 'create')): ?> - hyperlink($this->translate('Add new item set'), $this->url(null, array('action' => 'add'), true), array('class' => 'button')); ?> + hyperlink($this->translate('Add new item set'), $this->url(null, ['action' => 'add'], true), ['class' => 'button']); ?>
0): ?> - hyperlink($this->translate('Advanced item sets search'), $this->url(null, array('action' => 'search'), true), array('class' => 'advanced-search')); ?> + hyperlink($this->translate('Advanced item sets search'), $this->url(null, ['action' => 'search'], true), ['class' => 'advanced-search']); ?> pagination(); ?> @@ -55,10 +54,10 @@ $sortHeadings = [ if ($owner = $itemSet->owner()) { $ownerText = $this->hyperlink( $owner->name(), - $this->url('admin/id', array( + $this->url('admin/id', [ 'controller' => 'user', 'action' => 'show', - 'id' => $owner->id()) + 'id' => $owner->id()] ) ); } else { @@ -75,17 +74,16 @@ $sortHeadings = [