Added box links
authorMarcel Werk <burntime@woltlab.com>
Tue, 3 May 2016 09:35:26 +0000 (11:35 +0200)
committerMarcel Werk <burntime@woltlab.com>
Tue, 3 May 2016 09:35:32 +0000 (11:35 +0200)
wcfsetup/install/files/acp/templates/boxAdd.tpl
wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Box/Handler.js [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/BoxAddForm.class.php
wcfsetup/install/files/lib/acp/form/BoxEditForm.class.php
wcfsetup/install/files/lib/data/box/Box.class.php
wcfsetup/setup/db/install.sql

index 57ef98a87c57ab1d52b19c6857bd5ee1603f6833..5f190776f2741f5b3e534f1a67880ad4a17cd923 100644 (file)
                                }
                        });
                });
+               
+               require(['Dictionary', 'Language', 'WoltLab/WCF/Acp/Ui/Box/Handler'], function(Dictionary, Language, AcpUiBoxHandler) {
+                       Language.addObject({
+                               'wcf.page.pageObjectID.search.noResults': '{lang}wcf.page.pageObjectID.search.noResults{/lang}',
+                               'wcf.page.pageObjectID.search.results': '{lang}wcf.page.pageObjectID.search.results{/lang}',
+                               'wcf.page.pageObjectID.search.results.description': '{lang}wcf.page.pageObjectID.search.results.description{/lang}',
+                               'wcf.page.pageObjectID.search.terms': '{lang}wcf.page.pageObjectID.search.terms{/lang}',
+                               'wcf.page.pageObjectID.search.terms.description': '{lang}wcf.page.pageObjectID.search.terms.description{/lang}'
+                       });
+                       
+                       var handlers = new Dictionary();
+                       {foreach from=$pageHandlers key=handlerPageID item=requireObjectID}
+                               handlers.set({@$handlerPageID}, {if $requireObjectID}true{else}false{/if});
+                       {/foreach}
+                       
+                       AcpUiBoxHandler.init(handlers);
+               });
        </script>
 {/if}
 
                {event name='dataFields'}
        </section>
        
+       <section class="section">
+               <h2 class="sectionTitle">{lang}wcf.acp.box.link{/lang}</h2>
+               
+               <dl>
+                       <dt></dt>
+                       <dd class="floated">
+                               <label><input type="radio" name="linkType" value="none"{if $linkType == 'none'} checked="checked"{/if} /> {lang}wcf.acp.box.linkType.none{/lang}</label>
+                               <label><input type="radio" name="linkType" value="internal"{if $linkType == 'internal'} checked="checked"{/if} /> {lang}wcf.acp.box.linkType.internal{/lang}</label>
+                               <label><input type="radio" name="linkType" value="external"{if $linkType == 'external'} checked="checked"{/if} /> {lang}wcf.acp.box.linkType.external{/lang}</label>
+                       </dd>
+               </dl>
+               
+               <dl id="linkPageIDContainer"{if $errorField == 'linkPageID'} class="formError"{/if}{if $linkType != 'internal'} style="display: none;"{/if}>
+                       <dt><label for="linkPageID">{lang}wcf.acp.box.linkPageID{/lang}</label></dt>
+                       <dd>
+                               <select name="linkPageID" id="linkPageID">
+                                       <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                       
+                                       {foreach from=$pageNodeList item=pageNode}
+                                               <option value="{@$pageNode->getPage()->pageID}"{if $pageNode->getPage()->pageID == $linkPageID} selected="selected"{/if}>{if $pageNode->getDepth() > 1}{@"&nbsp;&nbsp;&nbsp;&nbsp;"|str_repeat:($pageNode->getDepth() - 1)}{/if}{$pageNode->getPage()->name}</option>
+                                       {/foreach}
+                               </select>
+                               {if $errorField == 'linkPageID'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.box.linkPageID.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl id="linkPageObjectIDContainer"{if $errorField == 'linkPageObjectID'} class="formError"{/if}{if !$linkPageID || !$pageHandler[$linkPageID]|isset} style="display: none;"{/if}>
+                       <dt><label for="linkPageObjectID">{lang}wcf.acp.box.linkPageObjectID{/lang}</label></dt>
+                       <dd>
+                               <div class="inputAddon">
+                                       <input type="text" id="linkPageObjectID" name="linkPageObjectID" value="{$linkPageObjectID}" class="short">
+                                       <a href="#" id="searchLinkPageObjectID" class="inputSuffix button jsTooltip" title="{lang}wcf.acp.page.objectID.search{/lang}"><span class="icon icon16 fa-search"></span></a>
+                               </div>
+                               {if $errorField == 'linkPageObjectID'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.box.linkPageObjectID.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl id="externalURLContainer"{if $errorField == 'externalURL'} class="formError"{/if}{if $linkType != 'external'} style="display: none;"{/if}>
+                       <dt><label for="externalURL">{lang}wcf.acp.box.link.externalURL{/lang}</label></dt>
+                       <dd>
+                               <input type="text" name="externalURL" id="externalURL" value="{$externalURL}" class="long" />
+                               {if $errorField == 'externalURL'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.box.link.externalURL.error.{$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               {event name='linkFields'}
+       </section>
+       
        {if !$isMultilingual}
                <section class="section">
                        <h2 class="sectionTitle">content</h2>
diff --git a/wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Box/Handler.js b/wcfsetup/install/files/js/WoltLab/WCF/Acp/Ui/Box/Handler.js
new file mode 100644 (file)
index 0000000..456e7a4
--- /dev/null
@@ -0,0 +1,132 @@
+/**
+ * Provides the interface logic to add and edit boxes.
+ * 
+ * @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/Box/Handler
+ */
+define(['Dictionary', 'WoltLab/Wcf/Ui/Page/Search/Handler'], function(Dictionary, UiPageSearchHandler) {
+       "use strict";
+       
+       var _activePageId = 0;
+       var _cache;
+       var _containerExternalLink;
+       var _containerPageID;
+       var _containerPageObjectId = null;
+       var _handlers;
+       var _pageId;
+       var _pageObjectId;
+       
+       /**
+        * @exports     WoltLab/WCF/Acp/Ui/Box/Handler
+        */
+       return {
+               /**
+                * Initializes the interface logic.
+                * 
+                * @param       {Dictionary}    handlers        list of handlers by page id supporting page object ids
+                */
+               init: function(handlers) {
+                       _handlers = handlers;
+                       
+                       _containerPageID = elById('linkPageIDContainer');
+                       _containerExternalLink = elById('externalURLContainer');
+                       _containerPageObjectId = elById('linkPageObjectIDContainer');
+                       
+                       if (_handlers.size) {
+                               _pageId = elById('linkPageID');
+                               _pageId.addEventListener('change', this._togglePageId.bind(this));
+                               
+                               _pageObjectId = elById('linkPageObjectID');
+                               
+                               _cache = new Dictionary();
+                               _activePageId = ~~_pageId.value;
+                               if (_activePageId && _handlers.has(_activePageId)) {
+                                       _cache.set(_activePageId, ~~_pageObjectId.value);
+                               }
+                               
+                               elById('searchLinkPageObjectID').addEventListener(WCF_CLICK_EVENT, this._openSearch.bind(this));
+                               
+                               // toggle page object id container on init
+                               if (_handlers.has(~~_pageId.value)) {
+                                       elShow(_containerPageObjectId);
+                               }
+                       }
+                       
+                       elBySelAll('input[name="linkType"]', null, (function(input) {
+                               input.addEventListener('change', this._toggleLinkType.bind(this, input.value));
+                               
+                               if (input.checked) {
+                                       this._toggleLinkType(input.value);
+                               }
+                       }).bind(this));
+               },
+               
+               /**
+                * Toggles between the interface for internal and external links.
+                * 
+                * @param       {string}        value   selected option value
+                * @protected
+                */
+               _toggleLinkType: function(value) {
+                       if (value == 'none') {
+                               elHide(_containerPageID);
+                               elHide(_containerPageObjectId);
+                               elHide(_containerExternalLink);
+                       }
+                       if (value == 'internal') {
+                               elShow(_containerPageID);
+                               elHide(_containerExternalLink);
+                               this._togglePageId();
+                       }
+                       if (value == 'external') {
+                               elHide(_containerPageID);
+                               elHide(_containerPageObjectId);
+                               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, _pageId.options[_pageId.selectedIndex].textContent, function(objectId) {
+                               _pageObjectId.value = objectId;
+                               _cache.set(_activePageId, objectId);
+                       });
+               }
+       };
+});
index a309a6b53bb927e5e22eeae956cbec63d463c3bf..596d15725aefceb6f9f0851f3f6ed25c71494bbf 100644 (file)
@@ -5,11 +5,13 @@ use wcf\data\box\BoxAction;
 use wcf\data\box\BoxEditor;
 use wcf\data\media\Media;
 use wcf\data\media\ViewableMediaList;
+use wcf\data\page\Page;
 use wcf\data\page\PageNodeTree;
 use wcf\form\AbstractForm;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\UserInputException;
 use wcf\system\language\LanguageFactory;
+use wcf\system\page\handler\ILookupPageHandler;
 use wcf\system\WCF;
 use wcf\util\ArrayUtil;
 use wcf\util\StringUtil;
@@ -120,6 +122,42 @@ class BoxAddForm extends AbstractForm {
         */
        public $pageIDs = [];
        
+       /**
+        * link type
+        * @var string
+        */
+       public $linkType = 'none';
+       
+       /**
+        * link page id
+        * @var int
+        */
+       public $linkPageID = 0;
+       
+       /**
+        * link page object id
+        * @var int
+        */
+       public $linkPageObjectID = 0;
+       
+       /**
+        * link external URL
+        * @var string
+        */
+       public $externalURL = '';
+       
+       /**
+        * list of page handlers by page id
+        * @var \wcf\system\page\handler\IMenuPageHandler[]
+        */
+       public $pageHandlers = [];
+       
+       /**
+        * nested list of page nodes
+        * @var \RecursiveIteratorIterator
+        */
+       public $pageNodeList;
+       
        /**
         * @inheritDoc
         */
@@ -127,6 +165,18 @@ class BoxAddForm extends AbstractForm {
                parent::readParameters();
        
                if (!empty($_REQUEST['isMultilingual'])) $this->isMultilingual = 1;
+               
+               $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;
+                               }
+                       }
+               }
        }
        
        /**
@@ -146,6 +196,11 @@ class BoxAddForm extends AbstractForm {
                if (isset($_POST['controller'])) $this->controller = StringUtil::trim($_POST['controller']);
                if (isset($_POST['pageIDs']) && is_array($_POST['pageIDs'])) $this->pageIDs = ArrayUtil::toIntegerArray($_POST['pageIDs']);
                
+               if (isset($_POST['linkType'])) $this->linkType = $_POST['linkType'];
+               if (!empty($_POST['linkPageID'])) $this->linkPageID = intval($_POST['linkPageID']);
+               if (!empty($_POST['linkPageObjectID'])) $this->linkPageObjectID = intval($_POST['linkPageObjectID']);
+               if (isset($_POST['externalURL'])) $this->externalURL = StringUtil::trim($_POST['externalURL']);
+               
                if (isset($_POST['title']) && is_array($_POST['title'])) $this->title = ArrayUtil::trim($_POST['title']);
                if (isset($_POST['content']) && is_array($_POST['content'])) $this->content = ArrayUtil::trim($_POST['content']);
                
@@ -202,6 +257,43 @@ class BoxAddForm extends AbstractForm {
                        // @todo check controller
                }
                
+               // validate link
+               if ($this->linkType == 'internal') {
+                       $this->externalURL = '';
+                       
+                       if (!$this->linkPageID) {
+                               throw new UserInputException('linkPageID');
+                       }
+                       $page = new Page($this->linkPageID);
+                       if (!$page->pageID) {
+                               throw new UserInputException('linkPageID', 'invalid');
+                       }
+                       
+                       // validate page object id
+                       if (isset($this->pageHandlers[$page->pageID])) {
+                               if ($this->pageHandlers[$page->pageID] && !$this->linkPageObjectID) {
+                                       throw new UserInputException('linkPageObjectID');
+                               }
+                               
+                               /** @var ILookupPageHandler $handler */
+                               $handler = $page->getHandler();
+                               if ($this->linkPageObjectID && !$handler->isValid($this->linkPageObjectID)) {
+                                       throw new UserInputException('linkPageObjectID', 'invalid');
+                               }
+                       }
+               }
+               else if ($this->linkType == 'external') {
+                       $this->linkPageID = $this->linkPageObjectID = null;
+                       
+                       if (empty($this->externalURL)) {
+                               throw new UserInputException('externalURL');
+                       }
+               }
+               else {
+                       $this->linkPageID = $this->linkPageObjectID = null;
+                       $this->externalURL = '';
+               }
+               
                // validate page ids
                if (!empty($this->pageIDs)) {
                        $conditionBuilder = new PreparedStatementConditionBuilder();
@@ -274,6 +366,9 @@ class BoxAddForm extends AbstractForm {
                        'cssClassName' => $this->cssClassName,
                        'showHeader' => $this->showHeader,
                        'controller' => $this->controller,
+                       'linkPageID' => $this->linkPageID,
+                       'linkPageObjectID' => ($this->linkPageObjectID ?: 0),
+                       'externalURL' => $this->externalURL,
                        'identifier' => ''
                ]), 'content' => $content, 'pageIDs' => $this->pageIDs ]);
                $returnValues = $this->objectAction->executeAction();
@@ -319,10 +414,15 @@ class BoxAddForm extends AbstractForm {
                        'imageID' => $this->imageID,
                        'images' => $this->images,
                        'pageIDs' => $this->pageIDs,
+                       'linkType' => $this->linkType,
+                       'linkPageID' => $this->linkPageID,
+                       'linkPageObjectID' => $this->linkPageObjectID,
+                       'externalURL' => $this->externalURL,
                        'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
                        'availableBoxTypes' => Box::$availableBoxTypes,
                        'availablePositions' => Box::$availablePositions,
-                       'pageNodeList' => (new PageNodeTree())->getNodeList()
+                       'pageNodeList' => $this->pageNodeList,
+                       'pageHandlers' => $this->pageHandlers
                ]);
        }
 }
index 817e1adaca7f25933badbdf415562b966ae07316..2634407ed3969f39b0f8819348f2df4475b54426 100644 (file)
@@ -132,6 +132,11 @@ class BoxEditForm extends BoxAddForm {
                        if ($this->box->visibleEverywhere) $this->visibleEverywhere = 1;
                        else $this->visibleEverywhere = 0;
                        $this->pageIDs = $this->box->getPageIDs();
+                       $this->linkPageID = $this->box->linkPageID;
+                       $this->linkPageObjectID = $this->box->linkPageObjectID;
+                       $this->externalURL = $this->box->externalURL;
+                       if ($this->linkPageID) $this->linkType = 'internal';
+                       if ($this->externalURL) $this->linkType = 'external';
                        
                        foreach ($this->box->getBoxContent() as $languageID => $content) {
                                $this->title[$languageID] = $content['title'];
index b361ed0e5311298b11ef146e171bc415095c0729..05a72bd062dd3fa0b22de16c097c58d76fc9956e 100644 (file)
@@ -4,6 +4,11 @@ use wcf\data\media\ViewableMedia;
 use wcf\data\menu\Menu;
 use wcf\data\menu\MenuCache;
 use wcf\data\DatabaseObject;
+use wcf\data\page\Page;
+use wcf\data\page\PageCache;
+use wcf\system\exception\SystemException;
+use wcf\system\page\handler\ILookupPageHandler;
+use wcf\system\page\handler\IMenuPageHandler;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -32,6 +37,9 @@ use wcf\util\StringUtil;
  * @property-read      integer         $packageID
  * @property-read      string          $controller
  * @property-read      integer|null    $menuID
+ * @property-read      integer         $linkPageID
+ * @property-read      integer         $linkPageObjectID
+ * @property-read      string          $externalURL
  */
 class Box extends DatabaseObject {
        /**
@@ -92,6 +100,17 @@ class Box extends DatabaseObject {
         */
        protected $__controller;
        
+       /**
+        * @var IMenuPageHandler
+        */
+       protected $linkPageHandler;
+       
+       /**
+        * page object
+        * @var Page
+        */
+       protected $linkPage;
+       
        /**
         * Returns true if the active user can delete this box.
         * 
@@ -308,14 +327,68 @@ class Box extends DatabaseObject {
                return (isset($boxContent[0]) && $boxContent[0]['imageID']);
        }
        
+       /**
+        * Returns the URL of this box.
+        *
+        * @return      string
+        */
        public function getLink() {
-               // @todo
-               return '';
+               if ($this->linkPageObjectID) {
+                       $handler = $this->getLinkPageHandler();
+                       if ($handler && $handler instanceof ILookupPageHandler) {
+                               return $handler->getLink($this->linkPageObjectID);
+                       }
+               }
+               
+               if ($this->linkPageID) {
+                       return $this->getLinkPage()->getLink();
+               }
+               else {
+                       return $this->externalURL;
+               }
        }
        
+       /**
+        * Returns true if this box has a link.
+        *
+        * @return      boolean
+        */
        public function hasLink() {
-               // @todo
-               return false;
+               return ($this->linkPageID || !empty($this->externalURL));
+       }
+       
+       /**
+        * Returns the IMenuPageHandler of the linked page.
+        *
+        * @return      IMenuPageHandler|null
+        * @throws      SystemException
+        */
+       protected function getLinkPageHandler() {
+               $page = $this->getLinkPage();
+               if ($page !== null && $page->handler) {
+                       if ($this->linkPageHandler === null) {
+                               $className = $page->handler;
+                               $this->linkPageHandler = new $className;
+                               if (!($this->linkPageHandler instanceof IMenuPageHandler)) {
+                                       throw new SystemException("Expected a valid handler implementing '" . IMenuPageHandler::class . "'.");
+                               }
+                       }
+               }
+               
+               return $this->linkPageHandler;
+       }
+       
+       /**
+        * Returns the page that is linked by this box.
+        *
+        * @return      Page|null
+        */
+       public function getLinkPage() {
+               if ($this->linkPage === null && $this->linkPageID) {
+                       $this->linkPage = PageCache::getInstance()->getPage($this->linkPageID);
+               }
+               
+               return $this->linkPage;
        }
        
        /**
index a7a4950c0c1002f3aec79679eafcc682344b130d..4d976be425d26842c10c216447aa0b14cfed4e7a 100644 (file)
@@ -238,7 +238,10 @@ CREATE TABLE wcf1_box (
        originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
        packageID INT(10) NOT NULL,
        controller VARCHAR(255) NOT NULL DEFAULT '',
-       menuID INT(10) NULL
+       menuID INT(10),
+       linkPageID INT(10),
+       linkPageObjectID INT(10) NOT NULL DEFAULT 0,
+       externalURL VARCHAR(255) NOT NULL DEFAULT ''
 );
 
 DROP TABLE IF EXISTS wcf1_box_content;
@@ -1639,6 +1642,7 @@ ALTER TABLE wcf1_bbcode_attribute ADD FOREIGN KEY (bbcodeID) REFERENCES wcf1_bbc
 
 ALTER TABLE wcf1_box ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 ALTER TABLE wcf1_box ADD FOREIGN KEY (menuID) REFERENCES wcf1_menu (menuID) ON DELETE CASCADE;
+ALTER TABLE wcf1_box ADD FOREIGN KEY (linkPageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL;
 
 ALTER TABLE wcf1_box_content ADD FOREIGN KEY (boxID) REFERENCES wcf1_box (boxID) ON DELETE CASCADE;
 ALTER TABLE wcf1_box_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;