Added box management (WIP)
authorMarcel Werk <burntime@woltlab.com>
Wed, 2 Dec 2015 16:36:45 +0000 (17:36 +0100)
committerMarcel Werk <burntime@woltlab.com>
Wed, 2 Dec 2015 16:36:45 +0000 (17:36 +0100)
13 files changed:
com.woltlab.wcf/acpMenu.xml
com.woltlab.wcf/userGroupOption.xml
wcfsetup/install/files/acp/templates/boxAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/boxList.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/BoxAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/BoxEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/PageAddForm.class.php
wcfsetup/install/files/lib/acp/page/BoxListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/box/Box.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/box/BoxAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/box/BoxEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/box/BoxList.class.php [new file with mode: 0644]
wcfsetup/setup/db/install.sql

index 14da8edbfbbf0127e75b4ce511f0da0e9feb8ac6..65f790266e517f075272624eb5914f647e579102 100644 (file)
                        <permissions>admin.content.cms.canManageMenu</permissions>
                        <icon>fa-plus</icon>
                </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.cms.box.list">
+                       <controller><![CDATA[wcf\acp\page\BoxListPage]]></controller>
+                       <parent>wcf.acp.menu.link.cms</parent>
+                       <permissions>admin.content.cms.canManageBox</permissions>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.cms.box.add">
+                       <controller><![CDATA[wcf\acp\form\BoxAddForm]]></controller>
+                       <parent>wcf.acp.menu.link.cms.box.list</parent>
+                       <permissions>admin.content.cms.canManageBox</permissions>
+                       <icon>fa-plus</icon>
+               </acpmenuitem>
        </import>
 </data>
index 463227ae4cf413013c5f037189507d4767593b0d..7f2b18aea303317291094d08c89021da3c94e945 100644 (file)
@@ -430,6 +430,14 @@ pdf]]></defaultvalue>
                                <usersonly>1</usersonly>
                        </option>
                        
+                       <option name="admin.content.cms.canManageBox">
+                               <categoryname>admin.content</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                               <usersonly>1</usersonly>
+                       </option>
+                       
                        <!-- user.message -->
                        <option name="user.message.canUseSmilies">
                                <categoryname>user.message</categoryname>
diff --git a/wcfsetup/install/files/acp/templates/boxAdd.tpl b/wcfsetup/install/files/acp/templates/boxAdd.tpl
new file mode 100644 (file)
index 0000000..311ded8
--- /dev/null
@@ -0,0 +1,237 @@
+{include file='header' pageTitle='wcf.acp.box.'|concat:$action}
+
+<header class="boxHeadline">
+       <h1>{if $action == 'add'}{if $isMultilingual}{lang}wcf.acp.box.addMultilingual{/lang}{else}{lang}wcf.acp.box.add{/lang}{/if}{else}{lang}wcf.acp.box.edit{/lang}{/if}</h1>
+</header>
+
+{include file='formError'}
+
+{if $success|isset}
+       <p class="success">{lang}wcf.global.success.{$action}{/lang}</p>
+{/if}
+
+<div class="contentNavigation">
+       <nav>
+               <ul>
+                       <li><a href="{link controller='BoxList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.link.cms.box.list{/lang}</span></a></li>
+                               
+                       {event name='contentNavigationButtons'}
+               </ul>
+       </nav>
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='BoxAdd'}{/link}{else}{link controller='BoxEdit' id=$boxID}{/link}{/if}">
+       <section class="marginTop">
+               <h1>{lang}wcf.global.form.data{/lang}</h1>
+               
+               <dl{if $errorField == 'name'} class="formError"{/if}>
+                       <dt><label for="name">{lang}wcf.global.name{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="name" name="name" value="{$name}" required="required" autofocus="autofocus" class="long" />
+                               {if $errorField == 'name'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.box.name.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl{if $errorField == 'boxType'} class="formError"{/if}>
+                       <dt><label for="boxType">{lang}wcf.acp.box.boxType{/lang}</label></dt>
+                       <dd>
+                               <select name="boxType" id="boxType">
+                                       {foreach from=$availableBoxTypes item=availableBoxType}
+                                               <option value="{@$availableBoxType}"{if $availableBoxType == $boxType} selected="selected"{/if}>{lang}wcf.acp.box.boxType.{@$availableBoxType}{/lang}</option>
+                                       {/foreach}
+                               </select>
+                               
+                               {if $errorField == 'boxType'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.box.boxType.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl{if $errorField == 'position'} class="formError"{/if}>
+                       <dt><label for="position">{lang}wcf.acp.box.position{/lang}</label></dt>
+                       <dd>
+                               <select name="position" id="position">
+                                       {foreach from=$availablePositions item=availablePosition}
+                                               <option value="{@$availablePosition}"{if $availablePosition == $position} selected="selected"{/if}>{lang}wcf.acp.box.position.{@$availablePosition}{/lang}</option>
+                                       {/foreach}
+                               </select>
+                               
+                               {if $errorField == 'position'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.box.position.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl>
+                       <dt><label for="showOrder">{lang}wcf.acp.box.showOrder{/lang}</label></dt>
+                       <dd>
+                               <input type="number" id="showOrder" name="showOrder" value="{@$showOrder}" class="tiny" min="0" />
+                       </dd>
+               </dl>
+       
+               <dl{if $errorField == 'cssClassName'} class="formError"{/if}>
+                       <dt><label for="cssClassName">{lang}wcf.acp.box.cssClassName{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="cssClassName" name="cssClassName" value="{$cssClassName}" class="long" />
+                               {if $errorField == 'cssClassName'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.box.cssClassName.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl{if $errorField == 'className'} class="formError"{/if}>
+                       <dt><label for="className">{lang}wcf.acp.box.className{/lang}</label></dt>
+                       <dd>
+                               <input type="text" id="className" name="className" value="{$className}" class="long" />
+                               {if $errorField == 'className'}
+                                       <small class="innerError">
+                                               {if $errorType == 'empty'}
+                                                       {lang}wcf.global.form.error.empty{/lang}
+                                               {else}
+                                                       {lang}wcf.acp.box.className.error.{@$errorType}{/lang}
+                                               {/if}
+                                       </small>
+                               {/if}
+                       </dd>
+               </dl>
+               
+               <dl>
+                       <dt></dt>
+                       <dd>
+                               <label><input type="checkbox" id="showHeader" name="showHeader" value="1" {if $showHeader}checked="checked" {/if}/> {lang}wcf.acp.box.showHeader{/lang}</label>
+                       </dd>
+               </dl>
+                       
+               <dl>
+                       <dt></dt>
+                       <dd>
+                               <label><input type="checkbox" id="visibleEverywhere" name="visibleEverywhere" value="1" {if $visibleEverywhere}checked="checked" {/if}/> {lang}wcf.acp.box.visibleEverywhere{/lang}</label>
+                       </dd>
+               </dl>
+               
+               {event name='dataFields'}
+       </section>
+               
+       {if !$isMultilingual}
+               <fieldset>
+                       <legend>content</legend>
+               
+                       <dl{if $errorField == 'title'} class="formError"{/if}>
+                               <dt><label for="title">{lang}wcf.acp.box.title{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="title" name="title[0]" value="{if !$title[0]|empty}{$title[0]}{/if}" class="long" />
+                                       {if $errorField == 'title'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.box.title.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'content'} class="formError"{/if}>
+                               <dt><label for="content0">{lang}wcf.acp.box.content{/lang}</label></dt>
+                               <dd>
+                                       <textarea name="content[0]" id="content0">{if !$content[0]|empty}{$content[0]}{/if}</textarea>
+                                       {if $errorField == 'content'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.box.content.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+               </fieldset>
+       {else}
+               <div class="tabMenuContainer">
+                       <nav class="tabMenu">
+                               <ul>
+                                       {foreach from=$availableLanguages item=availableLanguage}
+                                               {assign var='containerID' value='language'|concat:$availableLanguage->languageID}
+                                               <li><a href="{@$__wcf->getAnchor($containerID)}">{$availableLanguage->languageName}</a></li>
+                                       {/foreach}
+                               </ul>
+                       </nav>
+                       
+                       {foreach from=$availableLanguages item=availableLanguage}
+                               <div id="language{@$availableLanguage->languageID}" class="tabMenuContent">
+                                       <div>
+                                               <dl{if $errorField == 'title'} class="formError"{/if}>
+                                                       <dt><label for="title{@$availableLanguage->languageID}">{lang}wcf.acp.box.title{/lang}</label></dt>
+                                                       <dd>
+                                                               <input type="text" id="title{@$availableLanguage->languageID}" name="title[{@$availableLanguage->languageID}]" value="{if !$title[$availableLanguage->languageID]|empty}{$title[$availableLanguage->languageID]}{/if}" class="long" />
+                                                               {if $errorField == 'title'}
+                                                                       <small class="innerError">
+                                                                               {if $errorType == 'empty'}
+                                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                                               {else}
+                                                                                       {lang}wcf.acp.box.title.error.{@$errorType}{/lang}
+                                                                               {/if}
+                                                                       </small>
+                                                               {/if}
+                                                       </dd>
+                                               </dl>
+                                               
+                                               <dl{if $errorField == 'content'} class="formError"{/if}>
+                                                       <dt><label for="content{@$availableLanguage->languageID}">{lang}wcf.acp.box.content{/lang}</label></dt>
+                                                       <dd>
+                                                               <textarea name="content[{@$availableLanguage->languageID}]" id="content{@$availableLanguage->languageID}">{if !$content[$availableLanguage->languageID]|empty}{$content[$availableLanguage->languageID]}{/if}</textarea>
+                                                               {if $errorField == 'content'}
+                                                                       <small class="innerError">
+                                                                               {if $errorType == 'empty'}
+                                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                                               {else}
+                                                                                       {lang}wcf.acp.box.content.error.{@$errorType}{/lang}
+                                                                               {/if}
+                                                                       </small>
+                                                               {/if}
+                                                       </dd>
+                                               </dl>
+                                       </div>
+                               </div>
+                       {/foreach}
+               </div>
+       {/if}
+               
+       {event name='sections'}
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+               <input type="hidden" name="isMultilingual" value="{@$isMultilingual}" />
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/boxList.tpl b/wcfsetup/install/files/acp/templates/boxList.tpl
new file mode 100644 (file)
index 0000000..19f8134
--- /dev/null
@@ -0,0 +1,86 @@
+{include file='header' pageTitle='wcf.acp.box.list'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\box\\BoxAction', '.jsBoxRow');
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.box.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+       {pages print=true assign=pagesLinks controller="BoxList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+       
+       <nav>
+               <ul>
+                       <li><a href="{link controller='BoxAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.box.add{/lang}</span></a></li>
+                       <li><a href="{link controller='BoxAdd'}isMultilingual=1{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.box.addMultilingual{/lang}</span></a></li>
+               
+                       {event name='contentNavigationButtonsTop'}
+               </ul>
+       </nav>
+</div>
+
+{if $objects|count}
+       <div class="tabularBox tabularBoxTitle marginTop">
+               <header>
+                       <h2>{lang}wcf.acp.box.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+               </header>
+               
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnPageID{if $sortField == 'boxID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='BoxList'}pageNo={@$pageNo}&sortField=boxID&sortOrder={if $sortField == 'boxID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle columnName{if $sortField == 'name'} active {@$sortOrder}{/if}"><a href="{link controller='BoxList'}pageNo={@$pageNo}&sortField=name&sortOrder={if $sortField == 'name' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.name{/lang}</a></th>
+                                       <th class="columnText columnBoxType{if $sortField == 'boxType'} active {@$sortOrder}{/if}"><a href="{link controller='BoxList'}pageNo={@$pageNo}&sortField=boxType&sortOrder={if $sortField == 'boxType' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.box.boxType{/lang}</a></th>
+                                       <th class="columnText columnPosition{if $sortField == 'position'} active {@$sortOrder}{/if}"><a href="{link controller='BoxList'}pageNo={@$pageNo}&sortField=position&sortOrder={if $sortField == 'position' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.box.position{/lang}</a></th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$objects item=box}
+                                       <tr class="jsBoxRow">
+                                               <td class="columnIcon">
+                                                       <a href="{link controller='BoxEdit' id=$box->boxID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 fa-pencil"></span></a>
+                                                       {if $box->canDelete()}
+                                                               <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$box->boxID}" data-confirm-message="{lang}wcf.acp.box.delete.confirmMessage{/lang}"></span>
+                                                       {else}
+                                                               <span class="icon icon16 fa-times disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
+                                                       {/if}
+                                                       
+                                                       {event name='rowButtons'}
+                                               </td>
+                                               <td class="columnID columnBoxID">{@$box->boxID}</td>
+                                               <td class="columnTitle columnName"><a href="{link controller='BoxEdit' id=$box->boxID}{/link}">{$box->name}</a></td>
+                                               <td class="columnText columnBoxType">{$box->boxType}</td>
+                                               <td class="columnText columnPosition">{$box->position}</td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+       </div>
+       
+       <div class="contentNavigation">
+               {@$pagesLinks}
+               
+               
+               <nav>
+                       <ul>
+                               <li><a href="{link controller='BoxAdd'}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.box.add{/lang}</span></a></li>
+                               <li><a href="{link controller='BoxAdd'}isMultilingual=1{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.box.addMultilingual{/lang}</span></a></li>
+               
+                               {event name='contentNavigationButtonsBottom'}
+                       </ul>
+               </nav>
+       </div>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/lib/acp/form/BoxAddForm.class.php b/wcfsetup/install/files/lib/acp/form/BoxAddForm.class.php
new file mode 100644 (file)
index 0000000..4dac330
--- /dev/null
@@ -0,0 +1,251 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\box\Box;
+use wcf\data\box\BoxAction;
+use wcf\data\box\BoxEditor;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+use wcf\util\StringUtil;
+
+/**
+ * Shows the box add form.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class BoxAddForm extends AbstractForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.box.add';
+       
+       /**
+        * @inheritDoc
+        */
+       public $neededPermissions = ['admin.content.cms.canManageBox'];
+       
+       /**
+        * true if created box is multi-lingual
+        * @var boolean
+        */
+       public $isMultilingual = 0;
+       
+       /**
+        * box type
+        * @var string
+        */
+       public $boxType = '';
+       
+       /**
+        * box position
+        * @var string
+        */
+       public $position = '';
+       
+       /**
+        * show order
+        * @var integer
+        */
+       public $showOrder = 0;
+       
+       /**
+        * true if created box is visible everywhere 
+        * @var boolean
+        */
+       public $visibleEverywhere = 1;
+       
+       /**
+        * css class name of created box
+        * @var string
+        */
+       public $cssClassName = '';
+       
+       /**
+        * true if box header is visible
+        * @var boolean
+        */
+       public $showHeader = 1;
+       
+       /**
+        * php class name
+        * @var string
+        */
+       public $className = '';
+       
+       /**
+        * box name
+        * @var string
+        */
+       public $name = '';
+       
+       /**
+        * page titles
+        * @var array<string>
+        */
+       public $title = [];
+       
+       /**
+        * page contents
+        * @var array<string>
+        */
+       public $content = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+       
+               if (!empty($_REQUEST['isMultilingual'])) $this->isMultilingual = 1;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               $this->visibleEverywhere = $this->showOrder = 0;
+               if (isset($_POST['name'])) $this->name = StringUtil::trim($_POST['name']);
+               if (isset($_POST['boxType'])) $this->boxType = $_POST['boxType'];
+               if (isset($_POST['position'])) $this->position = $_POST['position'];
+               if (isset($_POST['showOrder'])) $this->showOrder = intval($_POST['showOrder']);
+               if (isset($_POST['visibleEverywhere'])) $this->visibleEverywhere = intval($_POST['visibleEverywhere']);
+               if (isset($_POST['cssClassName'])) $this->cssClassName = StringUtil::trim($_POST['cssClassName']);
+               if (isset($_POST['showHeader'])) $this->showHeader = intval($_POST['showHeader']);
+               if (isset($_POST['className'])) $this->className = StringUtil::trim($_POST['className']);
+               
+               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']);
+               
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validate() {
+               parent::validate();
+               
+               // validate name
+               $this->validateName();
+               
+               // validate box type
+               if (!in_array($this->boxType, Box::$availableBoxTypes)) {
+                       throw new UserInputException('boxType');
+               }
+               
+               // validate box position
+               if (!in_array($this->position, Box::$availablePositions)) {
+                       throw new UserInputException('position');
+               }
+               
+               // validate class name
+               if ($this->boxType == 'system') {
+                       if (empty($this->className)) {
+                               throw new UserInputException('className');
+                       }
+                       
+                       // @todo check class
+                       
+               }
+       }
+       
+       /**
+        * Validates box name.
+        */
+       protected function validateName() {
+               if (empty($this->name)) {
+                       throw new UserInputException('name');
+               }
+               if (Box::getBoxByName($this->name)) {
+                       throw new UserInputException('name', 'notUnique');
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               parent::save();
+               
+               $content = [];
+               if ($this->isMultilingual) {
+                       foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
+                               $content[$language->languageID] = [
+                                       'title' => (!empty($_POST['title'][$language->languageID]) ? $_POST['title'][$language->languageID] : ''),
+                                       'content' => (!empty($_POST['content'][$language->languageID]) ? $_POST['content'][$language->languageID] : '')
+                               ];
+                       }
+               }
+               else {
+                       $content[0] = [
+                               'title' => (!empty($_POST['title'][0]) ? $_POST['title'][0] : ''),
+                               'content' => (!empty($_POST['content'][0]) ? $_POST['content'][0] : '')
+                       ];
+               }
+               
+               $this->objectAction = new BoxAction([], 'create', ['data' => array_merge($this->additionalFields, [
+                       'name' => $this->name,
+                       'packageID' => 1,
+                       'isMultilingual' => $this->isMultilingual,
+                       'boxType' => $this->boxType,
+                       'position' => $this->position,
+                       'showOrder' => $this->showOrder,
+                       'visibleEverywhere' => $this->visibleEverywhere,
+                       'cssClassName' => $this->cssClassName,
+                       'showHeader' => $this->showHeader,                      
+                       'className' => $this->className,
+                       'identifier' => ''
+               ]), 'content' => $content]);
+               $returnValues = $this->objectAction->executeAction();
+               // set generic box identifier
+               $boxEditor = new BoxEditor($returnValues['returnValues']);
+               $boxEditor->update([
+                       'identifier' => 'com.woltlab.wcf.generic'.$boxEditor->boxID
+               ]);
+               
+               // call saved event
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign('success', true);
+               
+               // reset variables
+               $this->boxType = $this->position = $this->cssClassName = $this->className = $this->name = '';
+               $this->showOrder = 0;
+               $this->visibleEverywhere = $this->showHeader = 1;
+               $this->title = $this->content = [];
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign([
+                       'action' => 'add',
+                       'isMultilingual' => $this->isMultilingual,
+                       'name' => $this->name,
+                       'boxType' => $this->boxType,
+                       'position' => $this->position,
+                       'cssClassName' => $this->cssClassName,
+                       'className' => $this->className,
+                       'showOrder' => $this->showOrder,
+                       'visibleEverywhere' => $this->visibleEverywhere,
+                       'showHeader' => $this->showHeader,
+                       'title' => $this->title,
+                       'content' => $this->content,
+                       'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
+                       'availableBoxTypes' => Box::$availableBoxTypes,
+                       'availablePositions' => Box::$availablePositions
+               ]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/BoxEditForm.class.php b/wcfsetup/install/files/lib/acp/form/BoxEditForm.class.php
new file mode 100644 (file)
index 0000000..565fe48
--- /dev/null
@@ -0,0 +1,138 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\box\Box;
+use wcf\data\box\BoxAction;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\WCF;
+
+/**
+ * Shows the box edit form.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class BoxEditForm extends BoxAddForm {
+       /**
+        * @inheritDoc
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.box.list';
+       
+       /**
+        * box id
+        * @var integer
+        */
+       public $boxID = 0;
+       
+       /**
+        * box object
+        * @var Box
+        */
+       public $box = null;
+       
+       /**
+        * @inheritDoc
+        */
+       public function readParameters() {
+               parent::readParameters();
+       
+               if (isset($_REQUEST['id'])) $this->boxID = intval($_REQUEST['id']);
+               $this->box = new Box($this->boxID);
+               if (!$this->box->boxID) {
+                       throw new IllegalLinkException();
+               }
+               if ($this->box->isMultilingual) $this->isMultilingual = 1;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected function validateName() {
+               if (mb_strtolower($this->name) != mb_strtolower($this->box->name)) {
+                       parent::validateName();
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               $content = array();
+               if ($this->isMultilingual) {
+                       foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
+                               $content[$language->languageID] = [
+                                       'title' => (!empty($_POST['title'][$language->languageID]) ? $_POST['title'][$language->languageID] : ''),
+                                       'content' => (!empty($_POST['content'][$language->languageID]) ? $_POST['content'][$language->languageID] : ''),
+                               ];
+                       }
+               }
+               else {
+                       $content[0] = [
+                               'title' => (!empty($_POST['title'][0]) ? $_POST['title'][0] : ''),
+                               'content' => (!empty($_POST['content'][0]) ? $_POST['content'][0] : ''),
+                       ];
+               }
+               
+               $this->objectAction = new BoxAction([$this->box], 'update', ['data' => array_merge($this->additionalFields, [
+                       'name' => $this->name,
+                       'isMultilingual' => $this->isMultilingual,
+                       'boxType' => $this->boxType,
+                       'position' => $this->position,
+                       'showOrder' => $this->showOrder,
+                       'visibleEverywhere' => $this->visibleEverywhere,
+                       'cssClassName' => $this->cssClassName,
+                       'showHeader' => $this->showHeader,
+                       'className' => $this->className
+               ]), 'content' => $content]);
+               $this->objectAction->executeAction();
+               
+               // call saved event
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function readData() {
+               parent::readData();
+       
+               if (empty($_POST)) {
+                       $this->name = $this->box->name;
+                       $this->boxType = $this->box->boxType;
+                       $this->position = $this->box->position;
+                       $this->showOrder = $this->box->showOrder;
+                       $this->cssClassName = $this->box->cssClassName;
+                       $this->className = $this->box->className;
+                       if ($this->box->showHeader) $this->showHeader = 1;
+                       if ($this->box->visibleEverywhere) $this->visibleEverywhere = 1;
+                       
+                       foreach ($this->box->getBoxContent() as $languageID => $content) {
+                               $this->title[$languageID] = $content['title'];
+                               $this->content[$languageID] = $content['content'];
+                       }
+               }
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'edit',
+                       'boxID' => $this->boxID,
+                       'box' => $this->box
+               ));
+       }
+}
index 425d8a0469dac81e3856cc896af75860f04ddaf7..58e666379dd7b59860f2f3d555835598e1f5a92e 100644 (file)
@@ -154,6 +154,9 @@ class PageAddForm extends AbstractForm {
                $this->validateCustomUrl();
        }
        
+       /**
+        * Validates page name.
+        */
        protected function validateName() {
                if (empty($this->name)) {
                        throw new UserInputException('name');
@@ -163,6 +166,9 @@ class PageAddForm extends AbstractForm {
                }
        }
        
+       /**
+        * Validates parent page id.
+        */
        protected function validateParentPageID() {
                if ($this->parentPageID) {
                        $page = new Page($this->parentPageID);
@@ -172,12 +178,18 @@ class PageAddForm extends AbstractForm {
                }
        }
        
+       /**
+        * Validates package id.
+        */
        protected function validatePackageID() {
                if (!isset($this->availableApplications[$this->packageID])) {
                        throw new UserInputException('packageID', 'invalid');
                }
        }
        
+       /**
+        * Validates custom urls.
+        */
        protected function validateCustomUrl() {
                foreach ($this->customURL as $type => $customURL) {
                        if (!empty($customURL) && !RouteHandler::isValidCustomUrl($customURL)) {
@@ -225,7 +237,7 @@ class PageAddForm extends AbstractForm {
                        'identifier' => ''
                ]), 'content' => $content]);
                $returnValues = $this->objectAction->executeAction();
-               // set generic page name
+               // set generic page identifier
                $pageEditor = new PageEditor($returnValues['returnValues']);
                $pageEditor->update([
                        'identifier' => 'com.woltlab.wcf.generic'.$pageEditor->pageID
diff --git a/wcfsetup/install/files/lib/acp/page/BoxListPage.class.php b/wcfsetup/install/files/lib/acp/page/BoxListPage.class.php
new file mode 100644 (file)
index 0000000..548c59a
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Shows a list of boxes.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.page
+ * @category   Community Framework
+ */
+class BoxListPage extends SortablePage {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.box.list';
+       
+       /**
+        * @see \wcf\page\MultipleLinkPage::$objectListClassName
+        */
+       public $objectListClassName = 'wcf\data\box\BoxList';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.cms.canManageBox');
+       
+       /**
+        * @see \wcf\page\SortablePage::$defaultSortField
+        */
+       public $defaultSortField = 'name';
+       
+       /**
+        * @see \wcf\page\SortablePage::$validSortFields
+        */
+       public $validSortFields = array('boxID', 'name', 'boxType', 'position', 'showOrder');
+}
diff --git a/wcfsetup/install/files/lib/data/box/Box.class.php b/wcfsetup/install/files/lib/data/box/Box.class.php
new file mode 100644 (file)
index 0000000..d78173c
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+namespace wcf\data\box;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a box.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.box
+ * @category   Community Framework
+ */
+class Box extends DatabaseObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $databaseTableName = 'box';
+       
+       /**
+        * @inheritDoc
+        */
+       protected static $databaseTableIndexName = 'boxID';
+       
+       /**
+        * available box types
+        * @var string[]
+        */
+       public static $availableBoxTypes = ['text', 'html', 'system', 'menu'];
+       
+       /**
+        * available box positions
+        * @var string[]
+        */
+       public static $availablePositions = ['header', 'headerBoxes', 'top', 'sidebarLeft', 'contentTop', 'sidebarRight', 'contentBottom', 'bottom', 'footerBoxes', 'footer'];
+       
+       /**
+        * Returns true if the active user can delete this box.
+        * 
+        * @return boolean
+        */
+       public function canDelete() {
+               if (WCF::getSession()->getPermission('admin.content.cms.canManageBox') && !$this->originIsSystem) {
+                       return true;
+               }
+                       
+               return false;
+       }
+       
+       /**
+        * Returns the box content.
+        *
+        * @return array
+        */
+       public function getBoxContent() {
+               $content = array();
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_box_content
+                       WHERE   boxID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($this->boxID));
+               while ($row = $statement->fetchArray()) {
+                       $content[($row['languageID'] ?: 0)] = [
+                               'title' => $row['title'],
+                               'content' => $row['content']
+                       ];
+               }
+       
+               return $content;
+       }
+       
+       /**
+        * Returns the box with the given name.
+        *
+        * @param       string          $name
+        * @return      Box
+        */
+       public static function getBoxByName($name) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_box
+                       WHERE   name = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($name));
+               $row = $statement->fetchArray();
+               if ($row !== false) return new Box(null, $row);
+       
+               return null;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/box/BoxAction.class.php b/wcfsetup/install/files/lib/data/box/BoxAction.class.php
new file mode 100644 (file)
index 0000000..6eb628b
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+namespace wcf\data\box;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\WCF;
+
+/**
+ * Executes box related actions.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.box
+ * @category   Community Framework
+ */
+class BoxAction extends AbstractDatabaseObjectAction {
+       /**
+        * @inheritDoc
+        */
+       protected $className = BoxEditor::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsCreate = ['admin.content.cms.canManageBox'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsDelete = ['admin.content.cms.canManageBox'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $permissionsUpdate = ['admin.content.cms.canManageBox'];
+       
+       /**
+        * @inheritDoc
+        */
+       protected $requireACP = ['create', 'delete', 'update'];
+       
+       /**
+        * @inheritDoc
+        */
+       public function create() {
+               $box = parent::create();
+       
+               // save box content
+               if (!empty($this->parameters['content'])) {
+                       $sql = "INSERT INTO     wcf".WCF_N."_box_content
+                                               (boxID, languageID, title, content)
+                               VALUES          (?, ?, ?, ?)";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                               
+                       foreach ($this->parameters['content'] as $languageID => $content) {
+                               $statement->execute([
+                                       $box->boxID,
+                                       ($languageID ?: null),
+                                       $content['title'],
+                                       $content['content']
+                               ]);
+                       }
+               }
+       
+               return $box;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function update() {
+               parent::update();
+       
+               // update box content
+               if (!empty($this->parameters['content'])) {
+                       $sql = "DELETE FROM     wcf".WCF_N."_box_content
+                               WHERE           boxID = ?";
+                       $deleteStatement = WCF::getDB()->prepareStatement($sql);
+                               
+                       $sql = "INSERT INTO     wcf".WCF_N."_box_content
+                                               (boxID, languageID, title, content)
+                               VALUES          (?, ?, ?, ?)";
+                       $insertStatement = WCF::getDB()->prepareStatement($sql);
+                               
+                       foreach ($this->objects as $box) {
+                               $deleteStatement->execute(array($box->boxID));
+       
+                               foreach ($this->parameters['content'] as $languageID => $content) {
+                                       $insertStatement->execute([
+                                               $box->boxID,
+                                               ($languageID ?: null),
+                                               $content['title'],
+                                               $content['content']
+                                       ]);
+                               }
+                       }
+               }
+       
+               return $box;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function validateDelete() {
+               parent::validateDelete();
+               
+               foreach ($this->objects as $object) {
+                       if (!$object->canDelete()) {
+                               throw new PermissionDeniedException();
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/box/BoxEditor.class.php b/wcfsetup/install/files/lib/data/box/BoxEditor.class.php
new file mode 100644 (file)
index 0000000..ffde476
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\box;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit boxes.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.box
+ * @category   Community Framework
+ */
+class BoxEditor extends DatabaseObjectEditor {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = Box::class;
+}
diff --git a/wcfsetup/install/files/lib/data/box/BoxList.class.php b/wcfsetup/install/files/lib/data/box/BoxList.class.php
new file mode 100644 (file)
index 0000000..ca09bcc
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\box;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of boxes.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2015 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage data.box
+ * @category   Community Framework
+ */
+class BoxList extends DatabaseObjectList {
+       /**
+        * @inheritDoc
+        */
+       public $className = Box::class;
+}
index a210f90003d9713d8e67af5cd94766e889b4c082..d7a3ea4e398caa535a272a11e6c59985022c943d 100644 (file)
@@ -214,6 +214,33 @@ CREATE TABLE wcf1_bbcode_media_provider (
        html TEXT NOT NULL
 );
 
+DROP TABLE IF EXISTS wcf1_box;
+CREATE TABLE wcf1_box (
+       boxID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       identifier VARCHAR(255) NOT NULL,
+       name VARCHAR(255) NOT NULL,
+       boxType VARCHAR(255) NOT NULL,
+       position VARCHAR(255) NOT NULL,
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       visibleEverywhere TINYINT(1) NOT NULL DEFAULT 1,
+       isMultilingual TINYINT(1) NOT NULL DEFAULT 0,
+       cssClassName VARCHAR(255) NOT NULL DEFAULT '',
+       showHeader TINYINT(1) NOT NULL DEFAULT 1,
+       originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
+       packageID INT(10) NOT NULL,
+       className VARCHAR(255) NOT NULL DEFAULT ''
+);
+
+DROP TABLE IF EXISTS wcf1_box_content;
+CREATE TABLE wcf1_box_content (
+       boxID INT(10) NOT NULL,
+       languageID INT(10),
+       title VARCHAR(255) NOT NULL,
+       content MEDIUMTEXT,
+       
+       KEY (boxID, languageID)
+);
+
 DROP TABLE IF EXISTS wcf1_captcha_question;
 CREATE TABLE wcf1_captcha_question (
        questionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -1571,6 +1598,11 @@ ALTER TABLE wcf1_bbcode ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (pac
 
 ALTER TABLE wcf1_bbcode_attribute ADD FOREIGN KEY (bbcodeID) REFERENCES wcf1_bbcode (bbcodeID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_box ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+
+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; 
+
 ALTER TABLE wcf1_category ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 
 ALTER TABLE wcf1_cli_history ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;