Added page object id lookup
authorAlexander Ebert <ebert@woltlab.com>
Fri, 11 Mar 2016 12:39:15 +0000 (13:39 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 11 Mar 2016 12:39:24 +0000 (13:39 +0100)
wcfsetup/install/files/acp/templates/menuItemAdd.tpl
wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Menu/Item/Handler.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/StringUtil.js
wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search/Handler.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search/Input.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLab/WCF/Ui/Search/Input.js
wcfsetup/install/files/lib/acp/form/MenuItemAddForm.class.php
wcfsetup/install/files/lib/data/page/Page.class.php
wcfsetup/install/files/lib/data/page/PageAction.class.php
wcfsetup/install/files/lib/system/page/handler/ILookupPageHandler.class.php

index 8efc84dacc0726f79c627520e1554a0a164a3999..580f50014154d757948aad0dac5d5eb77872d11d 100644 (file)
@@ -1,27 +1,14 @@
 {include file='header' pageTitle='wcf.acp.menu.item.'|concat:$action}
 
 <script data-relocate="true">
-       //<![CDATA[
-       $(function() {
-               var $isInternalLink = $('input[name=isInternalLink]').filter('[value=1]');
-               var $pageIDContainer = $('#pageIDContainer');
-               var $externalURLContainer = $('#externalURLContainer');
+       require(['Dictionary', 'WoltLab/WCF/Acp/Ui/Menu/Item/Handler'], function(Dictionary, AcpUiMenuItemHandler) {
+               var handlers = new Dictionary();
+               {foreach from=$pageHandlers key=handlerPageID item=requireObjectID}
+                       handlers.set({@$handlerPageID}, {if $requireObjectID}true{else}false{/if});
+               {/foreach}
                
-               function handleIsInternalLink() {
-                       if ($isInternalLink.is(':checked')) {
-                               $pageIDContainer.show();
-                               $externalURLContainer.hide();
-                       }
-                       else {
-                               $pageIDContainer.hide();
-                               $externalURLContainer.show();
-                       }
-               }
-               
-               $('input[name=isInternalLink]').change(handleIsInternalLink);
-               handleIsInternalLink();
+               AcpUiMenuItemHandler.init(handlers);
        });
-       //]]>
 </script>
 
 <header class="contentHeader">
                        </dd>
                </dl>
                
-               <dl id="pageIDContainer"{if $errorField == 'pageID'} class="formError"{/if}>
+               <dl id="pageIDContainer"{if $errorField == 'pageID'} class="formError"{/if}{if !$isInternalLink} style="display: none;"{/if}>
                        <dt><label for="pageID">{lang}wcf.acp.page.parentPageID{/lang}</label></dt>
                        <dd>
                                <select name="pageID" id="pageID">
                        </dd>
                </dl>
                
-               <dl id="externalURLContainer"{if $errorField == 'externalURL'} class="formError"{/if}>
+               <dl id="pageObjectIDContainer"{if $errorField == 'pageObjectID'} class="formError"{/if}{if !$pageID || !$pageHandler[$pageID]|isset} style="display: none;"{/if}>
+                       <dt><label for="pageObjectID">{lang}wcf.acp.page.pageObjectID{/lang}</label></dt>
+                       <dd>
+                               <div class="inputAddon">
+                                       <input type="text" id="pageObjectID" name="pageObjectID" value="{$pageObjectID}" class="short">
+                                       <a href="#" id="searchPageObjectID" class="inputSuffix button jsTooltip" title="TODO: Search"><span class="icon icon16 fa-search"></span></a>
+                               </div>
+                               {if $errorField == 'pageObjectID'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.menu.item.pageObjectID.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl id="externalURLContainer"{if $errorField == 'externalURL'} class="formError"{/if}{if $isInternalLink} style="display: none;"{/if}>
                        <dt><label for="externalURL">{lang}wcf.acp.menu.item.externalURL{/lang}</label></dt>
                        <dd>
                                <input type="text" name="externalURL" id="externalURL" value="{$externalURL}" class="long" />
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Menu/Item/Handler.js b/wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Menu/Item/Handler.js
new file mode 100644 (file)
index 0000000..7d10727
--- /dev/null
@@ -0,0 +1,119 @@
+/**
+ * Provides the interface logic to add and edit menu items.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Acp/Ui/Menu/Item/Handler
+ */
+define(['Dictionary', 'WoltLab/Wcf/Ui/Page/Search/Handler'], function(Dictionary, UiPageSearchHandler) {
+       "use strict";
+       
+       var _activePageId = 0;
+       var _cache;
+       var _containerExternalLink;
+       var _containerInternalLink;
+       var _containerPageObjectId = null;
+       var _handlers;
+       var _pageId;
+       var _pageObjectId;
+       
+       /**
+        * @exports     WoltLab/WCF/Acp/Ui/Menu/Item/Handler
+        */
+       return {
+               /**
+                * Initializes the interface logic.
+                * 
+                * @param       {Dictionary}    handlers        list of handlers by page id supporting page object ids
+                */
+               init: function(handlers) {
+                       _handlers = handlers;
+                       
+                       _containerInternalLink = elById('pageIDContainer');
+                       _containerExternalLink = elById('externalURLContainer');
+                       _containerPageObjectId = elById('pageObjectIDContainer');
+                       
+                       elBySelAll('input[name="isInternalLink"]', null, (function(input) {
+                               input.addEventListener('change', this._toggleIsInternalLink.bind(this, input.value));
+                               
+                               if (input.checked) {
+                                       this._toggleIsInternalLink(input.value);
+                               }
+                       }).bind(this));
+                       
+                       if (_handlers.size) {
+                               _pageId = elById('pageID');
+                               _pageId.addEventListener('change', this._togglePageId.bind(this));
+                               
+                               _pageObjectId = elById('pageObjectID');
+                               
+                               _cache = new Dictionary();
+                               _activePageId = ~~_pageId.value;
+                               if (_activePageId && _handlers.has(_activePageId)) {
+                                       _cache.set(_activePageId, ~~_pageObjectId.value);
+                               }
+                               
+                               elById('searchPageObjectID').addEventListener(WCF_CLICK_EVENT, this._openSearch.bind(this));
+                               
+                               // toggle page object id container on init
+                               if (_handlers.has(~~_pageId.value)) {
+                                       elShow(_containerPageObjectId);
+                               }
+                       }
+               },
+               
+               /**
+                * Toggles between the interface for internal and external links.
+                * 
+                * @param       {string}        value   selected option value
+                * @protected
+                */
+               _toggleIsInternalLink: function(value) {
+                       window[(~~value ? 'elShow' : 'elHide')](_containerInternalLink);
+                       window[(~~value ? 'elHide' : 'elShow')](_containerExternalLink);
+               },
+               
+               /**
+                * Handles the changed page selection.
+                * 
+                * @protected
+                */
+               _togglePageId: function() {
+                       if (_handlers.has(_activePageId)) {
+                               _cache.set(_activePageId, ~~_pageObjectId.value);
+                       }
+                       
+                       _activePageId = ~~_pageId.value;
+                       
+                       // page w/o pageObjectID support, discard value
+                       if (!_handlers.has(_activePageId)) {
+                               _pageObjectId.value = '';
+                               
+                               elHide(_containerPageObjectId);
+                               
+                               return;
+                       }
+                               
+                       var newValue = ~~_cache.get(_activePageId);
+                       _pageObjectId.value = (newValue) ? newValue : '';
+                       
+                       elShow(_containerPageObjectId);
+               },
+               
+               /**
+                * Opens the handler lookup dialog.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _openSearch: function(event) {
+                       event.preventDefault();
+                       
+                       UiPageSearchHandler.open(_activePageId, 'foo', function(objectId) {
+                               _pageObjectId.value = objectId;
+                               _cache.set(_activePageId, objectId);
+                       });
+               }
+       };
+});
index 05699b23164ea907b899e53d953dcfbd2ed90933..ac70df58758e0efb3ac9ced3b105d891f8713e79 100644 (file)
@@ -12,12 +12,12 @@ define(['Language', './NumberUtil'], function(Language, NumberUtil) {
        /**
         * @exports     WoltLab/WCF/StringUtil
         */
-       var StringUtil = {
+       return {
                /**
                 * Adds thousands separators to a given number.
                 * 
                 * @see         http://stackoverflow.com/a/6502556/782822
-                * @param       {*}     number
+                * @param       {?}     number
                 * @return      {String}
                 */
                addThousandsSeparator: function(number) {
@@ -30,7 +30,7 @@ define(['Language', './NumberUtil'], function(Language, NumberUtil) {
                /**
                 * Escapes special HTML-characters within a string
                 * 
-                * @param       {*}     string
+                * @param       {?}     string
                 * @return      {String}
                 */
                escapeHTML: function (string) {
@@ -41,7 +41,7 @@ define(['Language', './NumberUtil'], function(Language, NumberUtil) {
                 * Escapes a String to work with RegExp.
                 * 
                 * @see         https://github.com/sstephenson/prototype/blob/master/src/prototype/lang/regexp.js#L25
-                * @param       {*}     string
+                * @param       {?}     string
                 * @return      {String}
                 */
                escapeRegExp: function(string) {
@@ -51,7 +51,7 @@ define(['Language', './NumberUtil'], function(Language, NumberUtil) {
                /**
                 * Rounds number to given count of floating point digits, localizes decimal-point and inserts thousands separators.
                 * 
-                * @param       {*}             number
+                * @param       {?}             number
                 * @param       {int}           decimalPlaces   The number of decimal places to leave after rounding.
                 * @return      {String}
                 */
@@ -73,7 +73,7 @@ define(['Language', './NumberUtil'], function(Language, NumberUtil) {
                /**
                 * Makes a string's first character lowercase.
                 * 
-                * @param       {*}             string
+                * @param       {?}             string
                 * @return      {String}
                 */
                lcfirst: function(string) {
@@ -83,7 +83,7 @@ define(['Language', './NumberUtil'], function(Language, NumberUtil) {
                /**
                 * Makes a string's first character uppercase.
                 * 
-                * @param       {*}             string
+                * @param       {?}             string
                 * @return      {String}
                 */
                ucfirst: function(string) {
@@ -93,13 +93,11 @@ define(['Language', './NumberUtil'], function(Language, NumberUtil) {
                /**
                 * Unescapes special HTML-characters within a string.
                 * 
-                * @param       {*}             string
+                * @param       {?}             string
                 * @return      {String}
                 */
                unescapeHTML: function (string) {
                        return String(string).replace(/&amp;/g, '&').replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
                }
        };
-       
-       return StringUtil;
 });
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search/Handler.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search/Handler.js
new file mode 100644 (file)
index 0000000..a907428
--- /dev/null
@@ -0,0 +1,176 @@
+/**
+ * Provides access to the lookup function of page handlers, allowing the user to search and
+ * select page object ids.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Page/Search/Handler
+ */
+define(['StringUtil', 'Dom/Util', 'Ui/Dialog', './Input'], function(StringUtil, DomUtil, UiDialog, UiPageSearchInput) {
+       "use strict";
+       
+       var _callback = null;
+       var _searchInput = null;
+       var _searchInputHandler = null;
+       var _resultList = null;
+       var _resultListContainer = null;
+       
+       /**
+        * @exports     WoltLab/WCF/Ui/Page/Search/Handler
+        */
+       return {
+               /**
+                * Opens the lookup overlay for provided page id.
+                * 
+                * @param       {int}           pageId          page id
+                * @param       {string}        title           dialog title
+                * @param       {function}      callback        callback function provided with the user-selected object id
+                */
+               open: function (pageId, title, callback) {
+                       _callback = callback;
+                       
+                       UiDialog.open(this);
+                       UiDialog.setTitle(this, title);
+                       
+                       this._getSearchInputHandler().setPageId(pageId);
+               },
+               
+               /**
+                * Builds the result list.
+                * 
+                * @param       {Object}        data            AJAX response data
+                * @protected
+                */
+               _buildList: function(data) {
+                       this._resetList();
+                       
+                       // no matches
+                       if (!Array.isArray(data.returnValues) || data.returnValues.length === 0) {
+                               var innerError = elCreate('small');
+                               innerError.className = 'innerError';
+                               innerError.textContent = 'TODO: no matches';
+                               DomUtil.insertAfter(innerError, _searchInput);
+                               
+                               return;
+                       }
+                       
+                       var image, item, listItem;
+                       for (var i = 0, length = data.returnValues.length; i < length; i++) {
+                               item = data.returnValues[i];
+                               image = item.image;
+                               if (/^fa-/.test(image)) {
+                                       image = '<span class="icon icon48 ' + image + '"></span>';
+                               }
+                               
+                               listItem = elCreate('li');
+                               elData(listItem, 'object-id', item.objectID);
+                               
+                               listItem.innerHTML = '<div class="box48">'
+                                       + image
+                                       + '<div>'
+                                               + '<div class="containerHeadline">'
+                                                       + '<h3><a href="' + StringUtil.escapeHTML(item.link) + '">' + StringUtil.escapeHTML(item.title) + '</a></h3>'
+                                                       + (item.description ? '<p>' + item.description + '</p>' : '')
+                                               + '</div>'
+                                       + '</div>'
+                               + '</div>';
+                               
+                               listItem.addEventListener(WCF_CLICK_EVENT, this._click.bind(this));
+                               
+                               _resultList.appendChild(listItem);
+                       }
+                       
+                       elShow(_resultListContainer);
+               },
+               
+               /**
+                * Resets the list and removes any error elements.
+                * 
+                * @protected
+                */
+               _resetList: function() {
+                       var innerError = _searchInput.nextElementSibling;
+                       if (innerError && innerError.classList.contains('innerError')) elRemove(innerError);
+                       
+                       _resultList.innerHTML = '';
+                       
+                       elHide(_resultListContainer);
+               },
+               
+               /**
+                * Initializes the search input handler and returns the instance.
+                * 
+                * @returns     {UiPageSearchInput}     search input handler
+                * @protected
+                */
+               _getSearchInputHandler: function() {
+                       if (_searchInputHandler === null) {
+                               var callback = this._buildList.bind(this);
+                               _searchInputHandler = new UiPageSearchInput(elById('wcfUiPageSearchInput'), {
+                                       callbackSuccess: callback
+                               });
+                       }
+                       
+                       return _searchInputHandler;
+               },
+               
+               /**
+                * Handles clicks on the item unless the click occured directly on a link.
+                * 
+                * @param       {Event}         event           event object
+                * @protected
+                */
+               _click: function(event) {
+                       if (event.target.nodeName === 'A') {
+                               return;
+                       }
+                       
+                       event.stopPropagation();
+                       
+                       _callback(elData(event.currentTarget, 'object-id'));
+                       UiDialog.close(this);
+               },
+               
+               _dialogSetup: function() {
+                       return {
+                               id: 'wcfUiPageSearchHandler',
+                               options: {
+                                       onShow: function() {
+                                               if (_searchInput === null) {
+                                                       _searchInput = elById('wcfUiPageSearchInput');
+                                                       _resultList = elById('wcfUiPageSearchResultList');
+                                                       _resultListContainer = elById('wcfUiPageSearchResultListContainer');
+                                               }
+                                               
+                                               // clear search input
+                                               _searchInput.value = '';
+                                               
+                                               // reset results
+                                               elHide(_resultListContainer);
+                                               _resultList.innerHTML = '';
+                                               
+                                               _searchInput.focus();
+                                       },
+                                       title: ''
+                               },
+                               source: '<div class="section">'
+                                               + '<dl>'
+                                                       + '<dt><label for="wcfUiPageSearchInput">TODO: Search terms</label></dt>'
+                                                       + '<dd>'
+                                                               + '<input type="text" id="wcfUiPageSearchInput" class="long">'
+                                                               + '<small>TODO: Enter at least 3 characters to search</small>'
+                                                       + '</dd>'
+                                               + '</dl>'
+                                       + '</div>'
+                                       + '<section id="wcfUiPageSearchResultListContainer" class="section sectionContainerList">'
+                                               + '<header class="sectionHeader">'
+                                                       + '<h2 class="sectionTitle">TODO: results</h2>'
+                                                       + '<small class="sectionDescription">TODO: click on a result to select it</small>'
+                                               + '</header>'
+                                               + '<ul id="wcfUiPageSearchResultList" class="containerList wcfUiPageSearchResultList"></ul>'
+                                       + '</section>'
+                       };
+               }
+       };
+});
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search/Input.js b/wcfsetup/install/files/js/WoltLab/WCF/Ui/Page/Search/Input.js
new file mode 100644 (file)
index 0000000..70c09b5
--- /dev/null
@@ -0,0 +1,60 @@
+/**
+ * Suggestions for page object ids with external response data processing.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module     WoltLab/WCF/Ui/Page/Search/Input
+ * @extends     module:WoltLab/WCF/Ui/Search/Input
+ */
+define(['Core', 'WoltLab/WCF/Ui/Search/Input'], function(Core, UiSearchInput) {
+       "use strict";
+       
+       /**
+        * @param       {Element}       element         input element
+        * @param       {Object=}       options         search options and settings
+        * @constructor
+        */
+       function UiPageSearchInput(element, options) { this.init(element, options); }
+       Core.inherit(UiPageSearchInput, UiSearchInput, {
+               init: function(element, options) {
+                       options = Core.extend({
+                               ajax: {
+                                       className: 'wcf\\data\\page\\PageAction'
+                               },
+                               callbackSuccess: null
+                       }, options);
+                       
+                       if (typeof options.callbackSuccess !== 'function') {
+                               throw new Error("Expected a valid callback function for 'callbackSuccess'.");
+                       }
+                       
+                       UiPageSearchInput._super.prototype.init.call(this, element, options);
+                       
+                       this._pageId = 0;
+               },
+               
+               /**
+                * Sets the target page id.
+                * 
+                * @param       {int}   pageId  target page id
+                */
+               setPageId: function(pageId) {
+                       this._pageId = pageId;
+               },
+               
+               _getParameters: function(value) {
+                       var data = UiPageSearchInput._super.prototype._getParameters.call(this, value);
+                       
+                       data.objectIDs = [this._pageId];
+                       
+                       return data;
+               },
+               
+               _ajaxSuccess: function(data) {
+                       this._options.callbackSuccess(data);
+               }
+       });
+       
+       return UiPageSearchInput;
+});
index 642f37e6974f01decd4caaa9514714b8e484d28b..2aeb3ebf03568b613676b766edcb6a3053c8d3ee 100644 (file)
@@ -145,13 +145,24 @@ define(['Ajax', 'Core', 'EventKey', 'Dom/Util', 'Ui/SimpleDropdown'], function(A
                                this._request.abortPrevious();
                        }
                        
-                       this._request = Ajax.api(this, {
+                       this._request = Ajax.api(this, this._getParameters(value));
+               },
+               
+               /**
+                * Returns additional AJAX parameters.
+                * 
+                * @param       {string}        value   search string
+                * @return      {Object}        additional AJAX parameters
+                * @protected
+                */
+               _getParameters: function(value) {
+                       return {
                                parameters: {
                                        data: {
                                                searchString: value
                                        }
                                }
-                       });
+                       };
                },
                
                /**
index 3fd3800e217f1aa9eab71c8855d6e0d4af23d1ff..4886ba4324c2288a2f486a91e18efdb5ff1e3c7f 100644 (file)
@@ -11,6 +11,7 @@ use wcf\form\AbstractForm;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\UserInputException;
 use wcf\system\language\I18nHandler;
+use wcf\system\page\handler\ILookupPageHandler;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -60,12 +61,24 @@ class MenuItemAddForm extends AbstractForm {
         */
        public $isInternalLink = true;
        
+       /**
+        * list of page handlers by page id
+        * @var \wcf\system\page\handler\IMenuPageHandler[]
+        */
+       public $pageHandlers = [];
+       
        /**
         * page id
         * @var integer
         */
        public $pageID = null;
        
+       /**
+        * page object id
+        * @var integer
+        */
+       public $pageObjectID = null;
+       
        /**
         * menu item title
         * @var string
@@ -96,6 +109,12 @@ class MenuItemAddForm extends AbstractForm {
         */
        public $menuItems = null;
        
+       /**
+        * nested list of page nodes
+        * @var \RecursiveIteratorIterator
+        */
+       public $pageNodeList;
+       
        /**
         * @inheritDoc
         */
@@ -110,6 +129,18 @@ class MenuItemAddForm extends AbstractForm {
                
                I18nHandler::getInstance()->register('title');
                I18nHandler::getInstance()->register('externalURL');
+               
+               $this->pageNodeList = (new PageNodeTree())->getNodeList();
+               
+               // fetch page handlers
+               foreach ($this->pageNodeList as $pageNode) {
+                       $handler = $pageNode->getPage()->getHandler();
+                       if ($handler !== null) {
+                               if ($handler instanceof ILookupPageHandler) {
+                                       $this->pageHandlers[$pageNode->getPage()->pageID] = $pageNode->getPage()->requireObjectID;
+                               }
+                       }
+               }
        }
        
        /**
@@ -126,6 +157,7 @@ class MenuItemAddForm extends AbstractForm {
                $this->isInternalLink = false;
                if (isset($_POST['isInternalLink'])) $this->isInternalLink = (bool) $_POST['isInternalLink'];
                if (!empty($_POST['pageID'])) $this->pageID = intval($_POST['pageID']);
+               if (!empty($_POST['pageObjectID'])) $this->pageObjectID = intval($_POST['pageObjectID']);
                if (!empty($_POST['parentItemID'])) $this->parentItemID = intval($_POST['parentItemID']);
                if (isset($_POST['showOrder'])) $this->showOrder = intval($_POST['showOrder']);
        }
@@ -147,9 +179,22 @@ class MenuItemAddForm extends AbstractForm {
                        if (!$page->pageID) {
                                throw new UserInputException('pageID', 'invalid');
                        }
+                       
+                       // validate page object id
+                       if (isset($this->pageHandlers[$page->pageID])) {
+                               if ($this->pageHandlers[$page->pageID] && !$this->pageObjectID) {
+                                       throw new UserInputException('pageObjectID');
+                               }
+                               
+                               /** @var ILookupPageHandler $handler */
+                               $handler = $page->getHandler();
+                               if ($this->pageObjectID && !$handler->isValid($this->pageObjectID)) {
+                                       throw new UserInputException('pageObjectID', 'invalid');
+                               }
+                       }
                }
                else {
-                       $this->pageID = null;
+                       $this->pageID = $this->pageObjectID = null;
                        
                        // validate external url
                        if (!I18nHandler::getInstance()->validateValue('externalURL')) {
@@ -181,6 +226,7 @@ class MenuItemAddForm extends AbstractForm {
                        'isDisabled' => ($this->isDisabled) ? 1 : 0,
                        'title' => $this->title,
                        'pageID' => $this->pageID,
+                       'pageObjectID' => ($this->pageObjectID ?: 0),
                        'externalURL' => $this->externalURL,
                        'menuID' => $this->menuID,
                        'parentItemID' => $this->parentItemID,
@@ -218,7 +264,7 @@ class MenuItemAddForm extends AbstractForm {
                
                // reset variables
                $this->isDisabled = $this->isInternalLink = false;
-               $this->pageID = $this->parentItemID = null;
+               $this->pageID = $this->pageObjectID = $this->parentItemID = null;
                $this->externalURL = $this->title = '';
                $this->showOrder = 0;
                
@@ -249,12 +295,14 @@ class MenuItemAddForm extends AbstractForm {
                        'isDisabled' => $this->isDisabled,
                        'isInternalLink' => $this->isInternalLink,
                        'pageID' => $this->pageID,
+                       'pageObjectID' => $this->pageObjectID,
                        'title' => $this->title,
                        'externalURL' => $this->externalURL,
                        'parentItemID' => $this->parentItemID,
                        'showOrder' => $this->showOrder,
                        'menuItemNodeList' => $this->menuItems->getNodeList(),
-                       'pageNodeList' => (new PageNodeTree())->getNodeList()
+                       'pageNodeList' => $this->pageNodeList,
+                       'pageHandlers' => $this->pageHandlers
                ]);
        }
 }
index c74bb4315da7b6ce4e572892d162aa9cfe37b198..a8ed4a8025771a8793b6a31b45d861afdc9b8c36 100644 (file)
@@ -27,6 +27,11 @@ class Page extends DatabaseObject {
         */
        protected static $databaseTableIndexName = 'pageID';
        
+       /**
+        * @var \wcf\system\page\handler\IMenuPageHandler
+        */
+       protected $pageHandler;
+       
        /**
         * Returns true if the active user can delete this page.
         * 
@@ -124,6 +129,19 @@ class Page extends DatabaseObject {
                }       
        }
        
+       /**
+        * Returns the associated page handler or null.
+        * 
+        * @return      \wcf\system\page\handler\IMenuPageHandler|null
+        */
+       public function getHandler() {
+               if ($this->handler) {
+                       $this->pageHandler = new $this->handler();
+               }
+               
+               return $this->pageHandler;
+       }
+       
        /**
         * Returns the page's internal name.
         * 
index 7213fea440cf0b436704491a3e2e69b05e627822..31bf36aeae853fbcaaba81dfb56c8828bd0d9d52 100644 (file)
@@ -1,8 +1,11 @@
 <?php
 namespace wcf\data\page;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\ISearchAction;
 use wcf\data\IToggleAction;
 use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\page\handler\ILookupPageHandler;
 use wcf\system\WCF;
 
 /**
@@ -16,31 +19,36 @@ use wcf\system\WCF;
  * @category   Community Framework
  * @since      2.2
  */
-class PageAction extends AbstractDatabaseObjectAction implements IToggleAction {
+class PageAction extends AbstractDatabaseObjectAction implements ISearchAction, IToggleAction {
        /**
         * @inheritDoc
         */
        protected $className = PageEditor::class;
        
+       /**
+        * @var PageEditor
+        */
+       protected $pageEditor;
+       
        /**
         * @inheritDoc
         */
-       protected $permissionsCreate = array('admin.content.cms.canManagePage');
+       protected $permissionsCreate = ['admin.content.cms.canManagePage'];
        
        /**
         * @inheritDoc
         */
-       protected $permissionsDelete = array('admin.content.cms.canManagePage');
+       protected $permissionsDelete = ['admin.content.cms.canManagePage'];
        
        /**
         * @inheritDoc
         */
-       protected $permissionsUpdate = array('admin.content.cms.canManagePage');
+       protected $permissionsUpdate = ['admin.content.cms.canManagePage'];
        
        /**
         * @inheritDoc
         */
-       protected $requireACP = array('create', 'delete', 'toggle', 'update');
+       protected $requireACP = ['create', 'delete', 'toggle', 'update'];
        
        /**
         * @inheritDoc
@@ -56,7 +64,7 @@ class PageAction extends AbstractDatabaseObjectAction implements IToggleAction {
                        $statement = WCF::getDB()->prepareStatement($sql);
                        
                        foreach ($this->parameters['content'] as $languageID => $content) {
-                               $statement->execute(array(
+                               $statement->execute([
                                        $page->pageID,
                                        ($languageID ?: null),
                                        $content['title'],
@@ -64,7 +72,7 @@ class PageAction extends AbstractDatabaseObjectAction implements IToggleAction {
                                        $content['metaDescription'],
                                        $content['metaKeywords'],
                                        $content['customURL']
-                               ));
+                               ]);
                        }
                }
                
@@ -89,10 +97,10 @@ class PageAction extends AbstractDatabaseObjectAction implements IToggleAction {
                        $insertStatement = WCF::getDB()->prepareStatement($sql);
                        
                        foreach ($this->objects as $page) {
-                               $deleteStatement->execute(array($page->pageID));
+                               $deleteStatement->execute([$page->pageID]);
                                
                                foreach ($this->parameters['content'] as $languageID => $content) {
-                                       $insertStatement->execute(array(
+                                       $insertStatement->execute([
                                                $page->pageID,
                                                ($languageID ?: null),
                                                $content['title'],
@@ -100,12 +108,10 @@ class PageAction extends AbstractDatabaseObjectAction implements IToggleAction {
                                                $content['metaDescription'],
                                                $content['metaKeywords'],
                                                $content['customURL']
-                                       ));
+                                       ]);
                                }
                        }
                }
-               
-               return $page;
        }
        
        /**
@@ -139,7 +145,26 @@ class PageAction extends AbstractDatabaseObjectAction implements IToggleAction {
         */
        public function toggle() {
                foreach ($this->objects as $object) {
-                       $object->update(array('isDisabled' => ($object->isDisabled) ? 0 : 1));
+                       $object->update(['isDisabled' => ($object->isDisabled) ? 0 : 1]);
                }
        }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateGetSearchResultList() {
+               $this->pageEditor = $this->getSingleObject();
+               if ($this->pageEditor->getHandler() === null || !($this->pageEditor->getHandler() instanceof ILookupPageHandler)) {
+                       throw new UserInputException('objectIDs');
+               }
+               
+               $this->readString('searchString', false, 'data');
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getSearchResultList() {
+               return $this->pageEditor->getHandler()->lookup($this->parameters['data']['searchString']);
+       }
 }
index ff32b11abdabff820d74efde202770d4f1e38fae..5eb6f9170e2d8e5da570d86ed055003d8db0f88b 100644 (file)
@@ -22,6 +22,14 @@ interface ILookupPageHandler extends IMenuPageHandler {
         */
        public function getLink($objectID);
        
+       /**
+        * Returns true if provided object id exists and is valid.
+        * 
+        * @param       integer         $objectID       page object id
+        * @return      boolean         true if object id is valid
+        */
+       public function isValid($objectID);
+       
        /**
         * Performs a search for pages using a query string, returning an array containing
         * an `objectID => title` relation.