Added CMS page management (WIP)
authorMarcel Werk <burntime@woltlab.com>
Thu, 19 Nov 2015 16:03:51 +0000 (17:03 +0100)
committerMarcel Werk <burntime@woltlab.com>
Thu, 19 Nov 2015 16:03:51 +0000 (17:03 +0100)
14 files changed:
com.woltlab.wcf/acpMenu.xml
com.woltlab.wcf/userGroupOption.xml
wcfsetup/install/files/acp/templates/pageAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/pageList.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/PageAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/PageEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/PageListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/Page.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/PageAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/PageEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/PageList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/PageNode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/PageNodeTree.class.php [new file with mode: 0644]
wcfsetup/setup/db/install.sql

index f5e2377cfb01d02a0a0be140b16c5dbb199e9993..003caf69269a936ceb067b3f76c001f87470169f 100644 (file)
                <acpmenuitem name="wcf.acp.menu.link.community">
                        <showorder>5</showorder>
                </acpmenuitem>
+               
+               <!-- lantia -->
+               <acpmenuitem name="wcf.acp.menu.link.cms">
+                       <parent>wcf.acp.menu.link.content</parent>
+                       <showorder>1</showorder>
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.cms.page.list">
+                       <controller><![CDATA[wcf\acp\page\PageListPage]]></controller>
+                       <parent>wcf.acp.menu.link.cms</parent>
+                       <permissions>admin.content.cms.canManagePage</permissions>
+               </acpmenuitem> 
        </import>
 </data>
index 78fb9a59523e582ba49c45166b40c378d023c328..ddd2e3e283a2150cbde9c1afb6fc04015d04afe2 100644 (file)
@@ -413,6 +413,14 @@ pdf]]></defaultvalue>
                                <admindefaultvalue>1</admindefaultvalue>
                                <usersonly>1</usersonly>
                        </option>
+                                               
+                       <option name="admin.content.cms.canManagePage">
+                               <categoryname>admin.content</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                               <usersonly>1</usersonly>
+                       </option>
                        
                        <!-- user.message -->
                        <option name="user.message.canUseSmilies">
diff --git a/wcfsetup/install/files/acp/templates/pageAdd.tpl b/wcfsetup/install/files/acp/templates/pageAdd.tpl
new file mode 100644 (file)
index 0000000..001d3c1
--- /dev/null
@@ -0,0 +1,326 @@
+{include file='header' pageTitle='wcf.acp.page.'|concat:$action}
+
+<script data-relocate="true">
+       $(function() {
+               $('#isLandingPage').change(function(event) {
+                       if ($('#isLandingPage')[0].checked) {
+                               $('#isDisabled')[0].checked = false;
+                               $('#isDisabled')[0].disabled = true;
+                       }
+                       else {
+                               $('#isDisabled')[0].disabled = false;
+                       }
+               }).trigger('change');
+               
+               $('#isDisabled').change(function(event) {
+                       if ($('#isDisabled')[0].checked) {
+                               $('#isLandingPage')[0].checked = false;
+                               $('#isLandingPage')[0].disabled = true;
+                       }
+                       else {
+                               $('#isLandingPage')[0].disabled = false;
+                       }
+               }).trigger('change');
+       });
+</script>
+
+<header class="boxHeadline">
+       <h1>{if $action == 'add'}{if $isMultilingual}{lang}wcf.acp.page.addMultilingual{/lang}{else}{lang}wcf.acp.page.add{/lang}{/if}{else}{lang}wcf.acp.page.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='PageList'}{/link}" class="button"><span class="icon icon16 icon-list"></span> <span>{lang}wcf.acp.menu.link.cms.page.list{/lang}</span></a></li>
+                               
+                       {event name='contentNavigationButtons'}
+               </ul>
+       </nav>
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='PageAdd'}{/link}{else}{link controller='PageEdit' id=$pageID}{/link}{/if}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}wcf.global.form.data{/lang}</legend>
+                       
+                       <dl{if $errorField == 'displayName'} class="formError"{/if}>
+                               <dt><label for="displayName">{lang}wcf.global.name{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="displayName" name="displayName" value="{$displayName}" required="required" autofocus="autofocus" class="long" />
+                                       {if $errorField == 'displayName'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.page.displayName.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       {if $action == 'add' || !$page->controller}
+                               <dl{if $errorField == 'parentPageID'} class="formError"{/if}>
+                                       <dt><label for="parentPageID">{lang}wcf.acp.page.parentPageID{/lang}</label></dt>
+                                       <dd>
+                                               <select name="parentPageID" id="parentPageID">
+                                                       <option value="0">{lang}wcf.acp.page.parentPageID.noParentPage{/lang}</option>
+                                                       
+                                                       {foreach from=$pageNodeList item=pageNode}
+                                                               <option value="{@$pageNode->getPage()->pageID}"{if $pageNode->getPage()->pageID == $parentPageID} selected="selected"{/if}>{if $pageNode->getDepth() > 1}{@"&nbsp;&nbsp;&nbsp;&nbsp;"|str_repeat:($pageNode->getDepth() - 1)}{/if}{$pageNode->getPage()->displayName}</option>
+                                                       {/foreach}
+                                               </select>
+                                               {if $errorField == 'parentPageID'}
+                                                       <small class="innerError">
+                                                               {if $errorType == 'empty'}
+                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {else}
+                                                                       {lang}wcf.acp.page.parentPageID.error.{@$errorType}{/lang}
+                                                               {/if}
+                                                       </small>
+                                               {/if}
+                                       </dd>
+                               </dl>
+                               
+                               <dl{if $errorField == 'packageID'} class="formError"{/if}>
+                                       <dt><label for="packageID">{lang}wcf.acp.page.packageID{/lang}</label></dt>
+                                       <dd>
+                                               <select name="packageID" id="packageID">
+                                                       {foreach from=$availableApplications item=availableApplication}
+                                                               <option value="{@$availableApplication->packageID}"{if $availableApplication->packageID == $packageID} selected="selected"{/if}>{$availableApplication->domainName}{$availableApplication->domainPath}</option>
+                                                       {/foreach}
+                                               </select>
+                                               {if $errorField == 'parentPageID'}
+                                                       <small class="innerError">
+                                                               {if $errorType == 'empty'}
+                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {else}
+                                                                       {lang}wcf.acp.page.packageID.error.{@$errorType}{/lang}
+                                                               {/if}
+                                                       </small>
+                                               {/if}
+                                       </dd>
+                               </dl>
+                       {/if}
+                       
+                       {if !$isMultilingual}
+                               <dl{if $errorField == 'customURL'} class="formError"{/if}>
+                                       <dt><label for="customURL">{lang}wcf.acp.page.customURL{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="customURL" name="customURL[0]" value="{if !$customURL[0]|empty}{$customURL[0]}{/if}" class="long" />
+                                               {if $errorField == 'customURL'}
+                                                       <small class="innerError">
+                                                               {if $errorType == 'empty'}
+                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                               {else}
+                                                                       {lang}wcf.acp.page.customURL.error.{@$errorType}{/lang}
+                                                               {/if}
+                                                       </small>
+                                               {/if}
+                                       </dd>
+                               </dl>
+                       {/if}
+                       
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" id="isLandingPage" name="isLandingPage" value="1" {if $isLandingPage}checked="checked" {/if}{if $action == 'edit' && $page->isLandingPage}disabled="disabled" {/if}/> {lang}wcf.acp.page.isLandingPage{/lang}</label>
+                               </dd>
+                       </dl>
+                       
+                       <dl>
+                               <dt></dt>
+                               <dd>
+                                       <label><input type="checkbox" id="isDisabled" name="isDisabled" value="1" {if $isDisabled}checked="checked" {/if}/> {lang}wcf.acp.page.isDisabled{/lang}</label>
+                               </dd>
+                       </dl>
+                       
+                       {event name='dataFields'}
+               </fieldset>
+               
+               {if $action == 'add' || !$page->controller}
+                       {if !$isMultilingual}
+                               <fieldset>
+                                       <legend>content</legend>
+                               
+                                       <dl{if $errorField == 'title'} class="formError"{/if}>
+                                               <dt><label for="title">{lang}wcf.acp.page.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.page.title.error.{@$errorType}{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl{if $errorField == 'content'} class="formError"{/if}>
+                                               <dt><label for="content0">{lang}wcf.acp.page.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.page.content.error.{@$errorType}{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl{if $errorField == 'metaKeywords'} class="formError"{/if}>
+                                               <dt><label for="metaKeywords">{lang}wcf.acp.page.metaKeywords{/lang}</label></dt>
+                                               <dd>
+                                                       <textarea name="metaKeywords[0]" id="metaKeywords">{if !$metaKeywords[0]|empty}{$metaKeywords[0]}{/if}</textarea>
+                                                       {if $errorField == 'metaKeywords'}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'empty'}
+                                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                                       {else}
+                                                                               {lang}wcf.acp.page.metaKeywords.error.{@$errorType}{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                               </dd>
+                                       </dl>
+                                       
+                                       <dl{if $errorField == 'metaDescription'} class="formError"{/if}>
+                                               <dt><label for="metaDescription">{lang}wcf.acp.page.metaDescription{/lang}</label></dt>
+                                               <dd>
+                                                       <textarea name="metaDescription[0]" id="metaDescription">{if !$metaDescription[0]|empty}{$metaDescription[0]}{/if}</textarea>
+                                                       {if $errorField == 'metaDescription'}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'empty'}
+                                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                                       {else}
+                                                                               {lang}wcf.acp.page.metaDescription.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="container containerPadding tabMenuContent">
+                                                       <fieldset>
+                                                               <dl{if $errorField == 'customURL'} class="formError"{/if}>
+                                                                       <dt><label for="customURL{@$availableLanguage->languageID}">{lang}wcf.acp.page.customURL{/lang}</label></dt>
+                                                                       <dd>
+                                                                               <input type="text" id="customURL{@$availableLanguage->languageID}" name="customURL[{@$availableLanguage->languageID}]" value="{if !$customURL[$availableLanguage->languageID]|empty}{$customURL[$availableLanguage->languageID]}{/if}" class="long" />
+                                                                               {if $errorField == 'customURL'}
+                                                                                       <small class="innerError">
+                                                                                               {if $errorType == 'empty'}
+                                                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                                                               {else}
+                                                                                                       {lang}wcf.acp.page.customURL.error.{@$errorType}{/lang}
+                                                                                               {/if}
+                                                                                       </small>
+                                                                               {/if}
+                                                                       </dd>
+                                                               </dl>
+                                                       
+                                                               <dl{if $errorField == 'title'} class="formError"{/if}>
+                                                                       <dt><label for="title{@$availableLanguage->languageID}">{lang}wcf.acp.page.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.page.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.page.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.page.content.error.{@$errorType}{/lang}
+                                                                                               {/if}
+                                                                                       </small>
+                                                                               {/if}
+                                                                       </dd>
+                                                               </dl>
+                                                               
+                                                               <dl{if $errorField == 'metaKeywords'} class="formError"{/if}>
+                                                                       <dt><label for="metaKeywords{@$availableLanguage->languageID}">{lang}wcf.acp.page.metaKeywords{/lang}</label></dt>
+                                                                       <dd>
+                                                                               <textarea name="metaKeywords[{@$availableLanguage->languageID}]" id="metaKeywords{@$availableLanguage->languageID}">{if !$metaKeywords[$availableLanguage->languageID]|empty}{$metaKeywords[$availableLanguage->languageID]}{/if}</textarea>
+                                                                               {if $errorField == 'metaKeywords'}
+                                                                                       <small class="innerError">
+                                                                                               {if $errorType == 'empty'}
+                                                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                                                               {else}
+                                                                                                       {lang}wcf.acp.page.metaKeywords.error.{@$errorType}{/lang}
+                                                                                               {/if}
+                                                                                       </small>
+                                                                               {/if}
+                                                                       </dd>
+                                                               </dl>
+                                                               
+                                                               <dl{if $errorField == 'metaDescription'} class="formError"{/if}>
+                                                                       <dt><label for="metaDescription{@$availableLanguage->languageID}">{lang}wcf.acp.page.metaDescription{/lang}</label></dt>
+                                                                       <dd>
+                                                                               <textarea name="metaDescription[{@$availableLanguage->languageID}]" id="metaDescription{@$availableLanguage->languageID}">{if !$metaDescription[$availableLanguage->languageID]|empty}{$metaDescription[$availableLanguage->languageID]}{/if}</textarea>
+                                                                               {if $errorField == 'metaDescription'}
+                                                                                       <small class="innerError">
+                                                                                               {if $errorType == 'empty'}
+                                                                                                       {lang}wcf.global.form.error.empty{/lang}
+                                                                                               {else}
+                                                                                                       {lang}wcf.acp.page.metaDescription.error.{@$errorType}{/lang}
+                                                                                               {/if}
+                                                                                       </small>
+                                                                               {/if}
+                                                                       </dd>
+                                                               </dl>
+                                                       </fieldset>
+                                               </div>
+                                       {/foreach}
+                               </div>
+                       {/if}
+               {/if}
+               
+               {event name='fieldsets'}
+       </div>
+       
+       <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/pageList.tpl b/wcfsetup/install/files/acp/templates/pageList.tpl
new file mode 100644 (file)
index 0000000..c07d846
--- /dev/null
@@ -0,0 +1,92 @@
+{include file='header' pageTitle='wcf.acp.page.list'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\page\\PageAction', '.jsPageRow');
+               new WCF.Action.Toggle('wcf\\data\\page\\PageAction', '.jsPageRow');
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.page.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+       {pages print=true assign=pagesLinks controller="PageList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+       
+       <nav>
+               <ul>
+                       <li><a href="{link controller='PageAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.page.add{/lang}</span></a></li>
+                       <li><a href="{link controller='PageAdd'}isMultilingual=1{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.page.addMultilingual{/lang}</span></a></li>
+               
+                       {event name='contentNavigationButtonsTop'}
+               </ul>
+       </nav>
+</div>
+
+{if $objects|count}
+       <div class="tabularBox tabularBoxTitle marginTop">
+               <header>
+                       <h2>{lang}wcf.acp.page.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+               </header>
+               
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnPageID{if $sortField == 'pageID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='PageList'}pageNo={@$pageNo}&sortField=pageID&sortOrder={if $sortField == 'pageID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle columnName{if $sortField == 'displayName'} active {@$sortOrder}{/if}"><a href="{link controller='PageList'}pageNo={@$pageNo}&sortField=displayName&sortOrder={if $sortField == 'displayName' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.name{/lang}</a></th>
+                                       <th class="columnText columnCustomURL{if $sortField == 'customURL'} active {@$sortOrder}{/if}"><a href="{link controller='PageList'}pageNo={@$pageNo}&sortField=customURL&sortOrder={if $sortField == 'customURL' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.page.customURL{/lang}</a></th>
+                                       <th class="columnDate columnLastUpdateTime{if $sortField == 'lastUpdateTime'} active {@$sortOrder}{/if}"><a href="{link controller='PageList'}pageNo={@$pageNo}&sortField=lastUpdateTime&sortOrder={if $sortField == 'lastUpdateTime' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.page.lastUpdateTime{/lang}</a></th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$objects item=page}
+                                       <tr class="jsPageRow">
+                                               <td class="columnIcon">
+                                                       {if $page->canDisable()}
+                                                               <span class="icon icon16 icon-check{if $page->isDisabled}-empty{/if} jsToggleButton jsTooltip pointer" title="{lang}wcf.global.button.{if !$page->isDisabled}disable{else}enable{/if}{/lang}" data-object-id="{@$page->pageID}"></span>
+                                                       {else}
+                                                               <span class="icon icon16 icon-check{if $page->isDisabled}-empty{/if} disabled" title="{lang}wcf.global.button.{if !$page->isDisabled}disable{else}enable{/if}{/lang}"></span>
+                                                       {/if}
+                                                       <a href="{link controller='PageEdit' id=$page->pageID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 icon-pencil"></span></a>
+                                                       {if $page->canDelete()}
+                                                               <span class="icon icon16 icon-remove jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$page->pageID}" data-confirm-message="{lang}wcf.acp.page.delete.confirmMessage{/lang}"></span>
+                                                       {else}
+                                                               <span class="icon icon16 icon-remove disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
+                                                       {/if}
+                                                       
+                                                       {event name='rowButtons'}
+                                               </td>
+                                               <td class="columnID columnPageID">{@$page->pageID}</td>
+                                               <td class="columnTitle columnName"><a href="{link controller='PageEdit' id=$page->pageID}{/link}">{$page->displayName}</a></td>
+                                               <td class="columnText columnCustomURL">{$page->customURL}</td>
+                                               <td class="columnDate columnLastUpdateTime">{@$page->lastUpdateTime|time}</td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+       </div>
+       
+       <div class="contentNavigation">
+               {@$pagesLinks}
+               
+               
+               <nav>
+                       <ul>
+                               <li><a href="{link controller='PageAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.page.add{/lang}</span></a></li>
+                               <li><a href="{link controller='PageAdd'}isMultilingual=1{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.page.addMultilingual{/lang}</span></a></li>
+               
+                               {event name='contentNavigationButtonsBottom'}
+                       </ul>
+               </nav>
+       </div>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/lib/acp/form/PageAddForm.class.php b/wcfsetup/install/files/lib/acp/form/PageAddForm.class.php
new file mode 100644 (file)
index 0000000..9e9870a
--- /dev/null
@@ -0,0 +1,262 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\application\ApplicationList;
+use wcf\data\page\PageAction;
+use wcf\data\page\PageEditor;
+use wcf\data\page\PageNodeTree;
+use wcf\data\page\Page;
+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 page 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 PageAddForm extends AbstractForm {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.page.list';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.cms.canManagePage');
+       
+       /**
+        * true if created page is multi-lingual
+        * @var boolean
+        */
+       public $isMultilingual = 0;
+       
+       /**
+        * parent page id
+        * @var integer
+        */
+       public $parentPageID = 0;
+       
+       /**
+        * page's display name
+        * @var string
+        */
+       public $displayName = '';
+       
+       /**
+        * true if page is disabled
+        * @var boolean
+        */
+       public $isDisabled = 0;
+       
+       /**
+        * true if page is landing page
+        * @var boolean
+        */
+       public $isLandingPage = 0;
+       
+       /**
+        * package id of the page
+        * @var integer
+        */
+       public $packageID = 1;
+       
+       /**
+        * list of available applications (standalone packages)
+        * @var array<\wcf\data\application\Application>
+        */
+       public $availableApplications = array();
+       
+       /**
+        * page custom URL
+        * @var array<string>
+        */
+       public $customURL = array();
+       
+       /**
+        * page titles
+        * @var array<string>
+        */
+       public $title = array();
+       
+       /**
+        * page contents
+        * @var array<string>
+        */
+       public $content = array();
+       
+       /**
+        * page meta descriptions
+        * @var array<string>
+        */
+       public $metaDescription = array();
+       
+       /**
+        * page meta keywords
+        * @var array<string>
+        */
+       public $metaKeywords = array();
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+       
+               if (!empty($_REQUEST['isMultilingual'])) $this->isMultilingual = 1;
+               
+               // get available applications
+               $applicationList = new ApplicationList();
+               $applicationList->readObjects();
+               $this->availableApplications = $applicationList->getObjects();
+       }
+       
+       /**
+        * @see \wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               if (isset($_POST['parentPageID'])) $this->parentPageID = intval($_POST['parentPageID']);
+               if (isset($_POST['displayName'])) $this->displayName = StringUtil::trim($_POST['displayName']);
+               if (isset($_POST['isDisabled'])) $this->isDisabled = 1;
+               if (isset($_POST['isLandingPage'])) $this->isLandingPage = 1;
+               if (isset($_POST['packageID'])) $this->packageID = intval($_POST['packageID']);
+               
+               if (isset($_POST['customURL']) && is_array($_POST['customURL'])) $this->customURL = ArrayUtil::trim($_POST['customURL']);
+               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']);
+               if (isset($_POST['metaDescription']) && is_array($_POST['metaDescription'])) $this->metaDescription = ArrayUtil::trim($_POST['metaDescription']);
+               if (isset($_POST['metaKeywords']) && is_array($_POST['metaKeywords'])) $this->metaKeywords = ArrayUtil::trim($_POST['metaKeywords']);
+       }
+       
+       /**
+        * @see \wcf\form\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+               
+               $this->validateDisplayName();
+               
+               $this->validateParentPageID();
+               
+               $this->validatePackageID();
+       }
+       
+       protected function validateDisplayName() {
+               if (empty($this->displayName)) {
+                       throw new UserInputException('displayName');
+               }
+               if (Page::getPageByDisplayName($this->displayName)) {
+                       throw new UserInputException('displayName', 'notUnique');
+               }
+       }
+       
+       protected function validateParentPageID() {
+               if ($this->parentPageID) {
+                       $page = new Page($this->parentPageID);
+                       if (!$page->pageID) {
+                               throw new UserInputException('parentPageID', 'invalid');
+                       }
+               }
+       }
+       
+       protected function validatePackageID() {
+               if (!isset($this->availableApplications[$this->packageID])) {
+                       throw new UserInputException('packageID', 'invalid');
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               parent::save();
+               
+               $content = array();
+               if ($this->isMultilingual) {
+                       foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
+                               $content[$language->languageID] = array(
+                                       'customURL' => (!empty($_POST['customURL'][$language->languageID]) ? $_POST['customURL'][$language->languageID] : ''),
+                                       'title' => (!empty($_POST['title'][$language->languageID]) ? $_POST['title'][$language->languageID] : ''),
+                                       'content' => (!empty($_POST['content'][$language->languageID]) ? $_POST['content'][$language->languageID] : ''),
+                                       'metaDescription' => (!empty($_POST['metaDescription'][$language->languageID]) ? $_POST['metaDescription'][$language->languageID] : ''),
+                                       'metaKeywords' => (!empty($_POST['metaKeywords'][$language->languageID]) ? $_POST['metaKeywords'][$language->languageID] : '')
+                               );
+                       }
+               }
+               else {
+                       $content[0] = array(
+                               'customURL' => (!empty($_POST['customURL'][0]) ? $_POST['customURL'][0] : ''),
+                               'title' => (!empty($_POST['title'][0]) ? $_POST['title'][0] : ''),
+                               'content' => (!empty($_POST['content'][0]) ? $_POST['content'][0] : ''),
+                               'metaDescription' => (!empty($_POST['metaDescription'][0]) ? $_POST['metaDescription'][0] : ''),
+                               'metaKeywords' => (!empty($_POST['metaKeywords'][0]) ? $_POST['metaKeywords'][0] : '')
+                       );
+               }
+               
+               $this->objectAction = new PageAction(array(), 'create', array('data' => array_merge($this->additionalFields, array(
+                       'parentPageID' => ($this->parentPageID ?: null),
+                       'displayName' => $this->displayName,
+                       'isDisabled' => ($this->isDisabled) ? 1 : 0,
+                       'isLandingPage' => ($this->isLandingPage) ? 1 : 0,
+                       'packageID' => ($this->packageID ?: null),
+                       'lastUpdateTime' => TIME_NOW,
+                       'isMultilingual' => $this->isMultilingual,
+                       'name' => ''
+               )), 'content' => $content));
+               $returnValues = $this->objectAction->executeAction();
+               // set generic page name
+               $pageEditor = new PageEditor($returnValues['returnValues']);
+               $pageEditor->update(array(
+                       'name' => 'com.woltlab.wcf.generic'.$pageEditor->pageID
+               ));
+               
+               // call saved event
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign('success', true);
+               
+               // reset variables
+               $this->parentPageID = $this->isDisabled = $this->isLandingPage = 0;
+               $this->packageID = 1;
+               $this->displayName = '';
+               $this->customURL = $this->title = $this->content = $this->metaDescription = $this->metaKeywords = array();
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               $pageNodeList = new PageNodeTree();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'add',
+                       'parentPageID' => $this->parentPageID,
+                       'displayName' => $this->displayName,
+                       'isDisabled' => $this->isDisabled,
+                       'isLandingPage' => $this->isLandingPage,
+                       'isMultilingual' => $this->isMultilingual,
+                       'packageID' => $this->packageID,
+                       'customURL' => $this->customURL,
+                       'title' => $this->title,
+                       'content' => $this->content,
+                       'metaDescription' => $this->metaDescription,
+                       'metaKeywords' => $this->metaKeywords,
+                       'availableApplications' => $this->availableApplications,
+                       'availableLanguages' => LanguageFactory::getInstance()->getLanguages(),
+                       'pageNodeList' => $pageNodeList->getNodeList()
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/PageEditForm.class.php b/wcfsetup/install/files/lib/acp/form/PageEditForm.class.php
new file mode 100644 (file)
index 0000000..ac9d4d8
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\page\PageAction;
+use wcf\data\page\Page;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\LanguageFactory;
+use wcf\system\WCF;
+
+/**
+ * Shows the page 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 PageEditForm extends PageAddForm {
+       /**
+        * page id
+        * @var integer
+        */
+       public $pageID = 0;
+       
+       /**
+        * page object
+        * @var \wcf\data\page\Page
+        */
+       public $page = null;
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+       
+               if (isset($_REQUEST['id'])) $this->pageID = intval($_REQUEST['id']);
+               $this->page = new Page($this->pageID);
+               if (!$this->page->pageID) {
+                       throw new IllegalLinkException();
+               }
+               if ($this->page->isMultilingual) $this->isMultilingual = 1;
+       }
+       
+       /**
+        * @see \wcf\acp\form\PageAddForm::validateDisplayName()
+        */
+       protected function validateDisplayName() {
+               if ($this->displayName != $this->page->displayName) {
+                       parent::validateDisplayName();
+               }
+       }
+       
+       /**
+        * @see \wcf\acp\form\PageAddForm::validateParentPageID()
+        */
+       protected function validateParentPageID() {
+               if (!$this->page->controller && $this->parentPageID) {
+                       $page = new Page($this->parentPageID);
+                       if (!$page->pageID) {
+                               throw new UserInputException('parentPageID', 'invalid');
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\acp\form\PageAddForm::validatePackageID()
+        */
+       protected function validatePackageID() {
+               if (!$this->page->controller) {
+                       parent::validatePackageID();
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               if ($this->page->controller) {
+                       $this->objectAction = new PageAction(array($this->page), 'update', array('data' => array_merge($this->additionalFields, array(
+                               'displayName' => $this->displayName,
+                               'isDisabled' => ($this->isDisabled) ? 1 : 0,
+                               'isLandingPage' => ($this->isLandingPage) ? 1 : 0,
+                               'controllerCustomURL' => (!empty($_POST['customURL'][0]) ? $_POST['customURL'][0] : ''),
+                               'lastUpdateTime' => TIME_NOW
+                       ))));
+                       $this->objectAction->executeAction();
+               }
+               else {
+                       $content = array();
+                       if ($this->isMultilingual) {
+                               foreach (LanguageFactory::getInstance()->getLanguages() as $language) {
+                                       $content[$language->languageID] = array(
+                                               'customURL' => (!empty($_POST['customURL'][$language->languageID]) ? $_POST['customURL'][$language->languageID] : ''),
+                                               'title' => (!empty($_POST['title'][$language->languageID]) ? $_POST['title'][$language->languageID] : ''),
+                                               'content' => (!empty($_POST['content'][$language->languageID]) ? $_POST['content'][$language->languageID] : ''),
+                                               'metaDescription' => (!empty($_POST['metaDescription'][$language->languageID]) ? $_POST['metaDescription'][$language->languageID] : ''),
+                                               'metaKeywords' => (!empty($_POST['metaKeywords'][$language->languageID]) ? $_POST['metaKeywords'][$language->languageID] : '')
+                                       );
+                               }
+                       }
+                       else {
+                               $content[0] = array(
+                                       'customURL' => (!empty($_POST['customURL'][0]) ? $_POST['customURL'][0] : ''),
+                                       'title' => (!empty($_POST['title'][0]) ? $_POST['title'][0] : ''),
+                                       'content' => (!empty($_POST['content'][0]) ? $_POST['content'][0] : ''),
+                                       'metaDescription' => (!empty($_POST['metaDescription'][0]) ? $_POST['metaDescription'][0] : ''),
+                                       'metaKeywords' => (!empty($_POST['metaKeywords'][0]) ? $_POST['metaKeywords'][0] : '')
+                               );
+                       }
+                       
+                       $this->objectAction = new PageAction(array($this->page), 'update', array('data' => array_merge($this->additionalFields, array(
+                               'parentPageID' => ($this->parentPageID ?: null),
+                               'displayName' => $this->displayName,
+                               'isDisabled' => ($this->isDisabled) ? 1 : 0,
+                               'isLandingPage' => ($this->isLandingPage) ? 1 : 0,
+                               'packageID' => ($this->packageID ?: null),
+                               'lastUpdateTime' => TIME_NOW,
+                               'isMultilingual' => $this->isMultilingual
+                       )), 'content' => $content));
+                       $this->objectAction->executeAction();
+               }
+               
+               // call saved event
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign('success', true);
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+       
+               if (empty($_POST)) {
+                       $this->displayName = $this->page->displayName;
+                       $this->parentPageID = $this->page->parentPageID;
+                       $this->packageID = $this->page->packageID;
+                       if ($this->page->isLandingPage) $this->isLandingPage = 1;
+                       if ($this->page->isDiabled) $this->isDisabled = 1;
+                       
+                       foreach ($this->page->getPageContent() as $languageID => $content) {
+                               $this->title[$languageID] = $content['title'];
+                               $this->content[$languageID] = $content['content'];
+                               $this->metaDescription[$languageID] = $content['metaDescription'];
+                               $this->metaKeywords[$languageID] = $content['metaKeywords'];
+                               $this->customURL[$languageID] = $content['customURL'];
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'edit',
+                       'pageID' => $this->pageID,
+                       'page' => $this->page
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/PageListPage.class.php b/wcfsetup/install/files/lib/acp/page/PageListPage.class.php
new file mode 100644 (file)
index 0000000..e193b3e
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Shows a list of pages.
+ * 
+ * @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 PageListPage extends SortablePage {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.page.list';
+       
+       /**
+        * @see \wcf\page\MultipleLinkPage::$objectListClassName
+        */
+       public $objectListClassName = 'wcf\data\page\PageList';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.cms.canManagePage');
+       
+       /**
+        * @see \wcf\page\SortablePage::$defaultSortField
+        */
+       public $defaultSortField = 'displayName';
+       
+       /**
+        * @see \wcf\page\SortablePage::$validSortFields
+        */
+       public $validSortFields = array('pageID', 'displayName', 'customURL', 'lastUpdateTime');
+}
diff --git a/wcfsetup/install/files/lib/data/page/Page.class.php b/wcfsetup/install/files/lib/data/page/Page.class.php
new file mode 100644 (file)
index 0000000..3a05195
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+namespace wcf\data\page;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a page.
+ * 
+ * @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.page
+ * @category   Community Framework
+ */
+class Page extends DatabaseObject {
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'page';
+       
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseTableIndexName
+        */
+       protected static $databaseTableIndexName = 'pageID';
+       
+       /**
+        * Returns true if the active user can delete this page.
+        * 
+        * @return boolean
+        */
+       public function canDelete() {
+               if (WCF::getSession()->getPermission('admin.content.cms.canManagePage') && !$this->originIsSystem && !$this->isLandingPage) {
+                       return true;
+               }
+                       
+               return false;
+       }
+       
+       /**
+        * Returns true if the active user can disable this page.
+        *
+        * @return boolean
+        */
+       public function canDisable() {
+               if (WCF::getSession()->getPermission('admin.content.cms.canManagePage') && !$this->originIsSystem && !$this->isLandingPage) {
+                       return true;
+               }
+                       
+               return false;
+       }
+       
+       /**
+        * Returns the page content.
+        * 
+        * @return array
+        */
+       public function getPageContent() {
+               $content = array();
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_page_content
+                       WHERE   pageID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($this->pageID));
+               while ($row = $statement->fetchArray()) {
+                       $content[($row['languageID'] ?: 0)] = array(
+                               'title' => $row['title'],
+                               'content' => $row['content'],
+                               'metaDescription' => $row['metaDescription'],
+                               'metaKeywords' => $row['metaKeywords'],
+                               'customURL' => $row['customURL']
+                       );
+               }
+               
+               return $content;
+       }
+       
+       /**
+        * Returns the page with the given display name.
+        * 
+        * @param       string          $name
+        * @return      \wcf\data\page\Page
+        */
+       public static function getPageByDisplayName($name) {
+               $sql = "SELECT  *
+                       FROM    wcf".WCF_N."_page
+                       WHERE   displayName = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($name));
+               $row = $statement->fetchArray();
+               if ($row !== false) return new Page(null, $row);
+               
+               return null;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/page/PageAction.class.php b/wcfsetup/install/files/lib/data/page/PageAction.class.php
new file mode 100644 (file)
index 0000000..5c1cfc1
--- /dev/null
@@ -0,0 +1,144 @@
+<?php
+namespace wcf\data\page;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IToggleAction;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\WCF;
+
+/**
+ * Executes page 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.page
+ * @category   Community Framework
+ */
+class PageAction extends AbstractDatabaseObjectAction implements IToggleAction {
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$className
+        */
+       protected $className = 'wcf\data\page\PageEditor';
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsCreate
+        */
+       protected $permissionsCreate = array('admin.content.cms.canManagePage');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+       */
+       protected $permissionsDelete = array('admin.content.cms.canManagePage');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+       */
+       protected $permissionsUpdate = array('admin.content.cms.canManagePage');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$requireACP
+        */
+       protected $requireACP = array('create', 'delete', 'toggle', 'update');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::create()
+        */
+       public function create() {
+               $page = parent::create();
+               
+               // save page content
+               if (!empty($this->parameters['content'])) {
+                       $sql = "INSERT INTO     wcf".WCF_N."_page_content
+                                               (pageID, languageID, title, content, metaDescription, metaKeywords, customURL)
+                               VALUES          (?, ?, ?, ?, ?, ?, ?)";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       
+                       foreach ($this->parameters['content'] as $languageID => $content) {
+                               $statement->execute(array(
+                                       $page->pageID,
+                                       ($languageID ?: null),
+                                       $content['title'],
+                                       $content['content'],
+                                       $content['metaDescription'],
+                                       $content['metaKeywords'],
+                                       $content['customURL']
+                               ));
+                       }
+               }
+               
+               return $page;
+       }
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::update()
+        */
+       public function update() {
+               parent::update();
+       
+               // update page content
+               if (!empty($this->parameters['content'])) {
+                       $sql = "DELETE FROM     wcf".WCF_N."_page_content
+                               WHERE           pageID = ?";
+                       $deleteStatement = WCF::getDB()->prepareStatement($sql);
+                       
+                       $sql = "INSERT INTO     wcf".WCF_N."_page_content
+                                               (pageID, languageID, title, content, metaDescription, metaKeywords, customURL)
+                               VALUES          (?, ?, ?, ?, ?, ?, ?)";
+                       $insertStatement = WCF::getDB()->prepareStatement($sql);
+                       
+                       foreach ($this->objects as $page) {
+                               $deleteStatement->execute(array($page->pageID));
+                               
+                               foreach ($this->parameters['content'] as $languageID => $content) {
+                                       $insertStatement->execute(array(
+                                               $page->pageID,
+                                               ($languageID ?: null),
+                                               $content['title'],
+                                               $content['content'],
+                                               $content['metaDescription'],
+                                               $content['metaKeywords'],
+                                               $content['customURL']
+                                       ));
+                               }
+                       }
+               }
+               
+               return $page;
+       }
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::validateDelete()
+        */
+       public function validateDelete() {
+               parent::validateDelete();
+               
+               foreach ($this->objects as $object) {
+                       if (!$object->canDelete()) {
+                               throw new PermissionDeniedException();
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\data\IToggleAction::validateToggle()
+       */
+       public function validateToggle() {
+               parent::validateUpdate();
+               
+               foreach ($this->objects as $object) {
+                       if (!$object->canDisable()) {
+                               throw new PermissionDeniedException();
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\data\IToggleAction::toggle()
+        */
+       public function toggle() {
+               foreach ($this->objects as $object) {
+                       $object->update(array('isDisabled' => ($object->isDisabled) ? 0 : 1));
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/page/PageEditor.class.php b/wcfsetup/install/files/lib/data/page/PageEditor.class.php
new file mode 100644 (file)
index 0000000..c649c9a
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\page;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit pages.
+ * 
+ * @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.page
+ * @category   Community Framework
+ */
+class PageEditor extends DatabaseObjectEditor {
+       /**
+        * @see \wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\page\Page';
+}
diff --git a/wcfsetup/install/files/lib/data/page/PageList.class.php b/wcfsetup/install/files/lib/data/page/PageList.class.php
new file mode 100644 (file)
index 0000000..01d9d87
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\page;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of pages.
+ * 
+ * @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.page
+ * @category   Community Framework
+ */
+class PageList extends DatabaseObjectList {
+       /**
+        * @see \wcf\data\DatabaseObjectList::$className
+        */
+       public $className = 'wcf\data\page\Page';
+}
diff --git a/wcfsetup/install/files/lib/data/page/PageNode.class.php b/wcfsetup/install/files/lib/data/page/PageNode.class.php
new file mode 100644 (file)
index 0000000..57408e8
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+namespace wcf\data\page;
+
+/**
+ * Represents a page node element.
+ * 
+ * @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.page
+ * @category   Community Framework
+ */
+class PageNode implements \Countable, \RecursiveIterator {
+       /**
+        * parent node
+        * @var \wcf\data\page\PageNode
+        */
+       protected $parentNode = null;
+       
+       /**
+        * children of this node
+        * @var array<\wcf\data\page\PageNode>
+        */
+       protected $children = array();
+       
+       /**
+        * page object
+        * @var \wcf\data\page\Page
+        */
+       protected $page = null;
+       
+       /**
+        * node depth
+        * @var integer
+        */
+       protected $depth = 0;
+       
+       /**
+        * iterator position
+        * @var integer
+        */
+       private $position = 0;
+       
+       /**
+        * Creates a new PageNode object.
+        * 
+        * @param       \wcf\data\page\PageNode         $parentNode
+        * @param       \wcf\data\page\Page             $page
+        * @param       integer                         $depth
+        */
+       public function __construct($parentNode = null, Page $page = null, $depth = 0) {
+               $this->parentNode = $parentNode;
+               $this->page = $page;
+               $this->depth = $depth;
+       }
+       
+       /**
+        * Sets the children of this node.
+        * 
+        * @param       array<\wcf\data\page\PageNode>          $children
+        */
+       public function setChildren(array $children) {
+               $this->children = $children;
+       }
+       
+       /**
+        * Returns the parent node
+        * 
+        * @return      \wcf\data\page\PageNode
+        */
+       public function getParentNode() {
+               return $this->parentNode;
+       }
+       
+       /**
+        * Returns the page object of this node
+        * 
+        * @return      \wcf\data\page\Page
+        */
+       public function getPage() {
+               return $this->page;
+       }
+       
+       /**
+        * Returns the number of children.
+        * 
+        * @return      integer
+        */
+       public function count() {
+               return count($this->children);
+       }
+       
+       /**
+        * @see \Iterator::rewind()
+        */
+       public function rewind() {
+               $this->position = 0;
+       }
+       
+       /**
+        * @see \Iterator::valid()
+        */
+       public function valid() {
+               return isset($this->children[$this->position]);
+       }
+       
+       /**
+        * @see \Iterator::next()
+        */
+       public function next() {
+               $this->position++;
+       }
+       
+       /**
+        * @see \Iterator::current()
+        */
+       public function current() {
+               return $this->children[$this->position];
+       }
+       
+       /**
+        * @see \Iterator::key()
+        */
+       public function key() {
+               return $this->position;
+       }
+       
+       /**
+        * @see \RecursiveIterator::getChildren()
+        */
+       public function getChildren() {
+               return $this->children[$this->position];
+       }
+       
+       /**
+        * @see \RecursiveIterator::hasChildren()
+        */
+       public function hasChildren() {
+               return count($this->children) > 0;
+       }
+       
+       /**
+        * Returns node depth.
+        * 
+        * @return      integer
+        */
+       public function getDepth() {
+               return $this->depth;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/page/PageNodeTree.class.php b/wcfsetup/install/files/lib/data/page/PageNodeTree.class.php
new file mode 100644 (file)
index 0000000..b817852
--- /dev/null
@@ -0,0 +1,115 @@
+<?php
+namespace wcf\data\page;
+
+/**
+ * Represents a page node tree.
+ * 
+ * @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.page
+ * @category   Community Framework
+ */
+class PageNodeTree {
+       /**
+        * parent page id
+        * @var integer
+        */
+       public $parentID = null;
+       
+       /**
+        * start depth
+        * @var integer
+        */
+       public $startDepth = 0;
+       
+       /**
+        * list of pages
+        * @var array<\wcf\data\page\Page>
+        */
+       public $pages = array();
+       
+       /**
+        * page structure
+        * @var array<array>
+        */
+       public $pageStructure = array();
+       
+       /**
+        * root node
+        * @var \wcf\data\page\PageNode
+        */
+       public $node = null;
+       
+       /**
+        * Creates a new PageNodeTree object.
+        *
+        * @param       integer                 $parentID
+        * @param       integer                 $startDepth
+        */
+       public function __construct($parentID = null, $startDepth = 0) {
+               $this->parentID = $parentID;
+               $this->startDepth = $startDepth;
+               
+               // load pages
+               $pageList = new PageList();
+               $pageList->sqlOrderBy = "page.displayName";
+               $pageList->readObjects();
+               
+               foreach ($pageList as $page) {
+                       $this->pages[$page->pageID] = $page;
+                               
+                       if (!isset($this->pageStructure[$page->parentPageID])) {
+                               $this->pageStructure[$page->parentPageID] = array();
+                       }
+                       $this->pageStructure[$page->parentPageID][] = $page->pageID;
+               }
+               
+               // generate node tree
+               $this->node = new PageNode(null, null, $startDepth);
+               $this->node->setChildren($this->generateNodeTree($parentID, $this->node));
+       }
+       
+       /**
+        * Generates the node tree recursively
+        * 
+        * @param       integer                         $parentID
+        * @param       \wcf\data\page\PageNode         $parentNode
+        * @param       array<integer>                  $filter
+        * @return      array<\wcf\data\page\PageNode>
+        */
+       protected function generateNodeTree($parentID, PageNode $parentNode = null) {
+               $nodes = array();
+               
+               $pageIDs = (isset($this->pageStructure[$parentID]) ? $this->pageStructure[$parentID] : array());
+               foreach ($pageIDs as $pageID) {
+                       $page = $this->pages[$pageID];
+                       $node = new PageNode($parentNode, $page, ($parentNode !== null ? ($parentNode->getDepth() + 1) : 0));
+                       $nodes[] = $node;
+                               
+                       // get children
+                       $node->setChildren($this->generateNodeTree($pageID, $node));
+               }
+               
+               return $nodes;
+       }
+       
+       /**
+        * Returns the page node tree.
+        * 
+        * @return      array<\wcf\data\page\PageNode>
+        */
+       public function getNodeTree() {
+               return $this->node->getChildren();
+       }
+       
+       /**
+        * Returns the iteratable node list
+        *
+        * @return      \RecursiveIteratorIterator
+        */
+       public function getNodeList() {
+               return new \RecursiveIteratorIterator($this->node, \RecursiveIteratorIterator::SELF_FIRST);
+       }
+}
index 63c666cae0789f265f346e8b786db7f316bac8fd..d2c208be3452b6e629ef3e8b3b078ebf5128e4ca 100644 (file)
@@ -796,6 +796,35 @@ CREATE TABLE wcf1_package_update_version (
        UNIQUE KEY packageUpdateID (packageUpdateID, packageVersion)
 );
 
+DROP TABLE IF EXISTS wcf1_page;
+CREATE TABLE wcf1_page (
+       pageID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       parentPageID INT(10),
+       name VARCHAR(255) NOT NULL,
+       displayName VARCHAR(255) NOT NULL,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       isLandingPage TINYINT(1) NOT NULL DEFAULT 0,
+       isMultilingual TINYINT(1) NOT NULL DEFAULT 0,
+       originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
+       packageID INT(10) NOT NULL,
+       controller VARCHAR(255) NOT NULL DEFAULT '',
+       controllerCustomURL VARCHAR(255) NOT NULL DEFAULT '',
+       lastUpdateTime INT(10) NOT NULL DEFAULT 0
+);
+
+DROP TABLE IF EXISTS wcf1_page_content;
+CREATE TABLE wcf1_page_content (
+       pageID INT(10) NOT NULL,
+       languageID INT(10),
+       title VARCHAR(255) NOT NULL,
+       content MEDIUMTEXT,
+       metaDescription TEXT,
+       metaKeywords TEXT,
+       customURL VARCHAR(255) NOT NULL,
+
+       KEY (pageID, languageID)
+);
+
 DROP TABLE IF EXISTS wcf1_page_menu_item;
 CREATE TABLE wcf1_page_menu_item (
        menuItemID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -1599,6 +1628,12 @@ ALTER TABLE wcf1_paid_subscription_transaction_log ADD FOREIGN KEY (userID) REFE
 ALTER TABLE wcf1_paid_subscription_transaction_log ADD FOREIGN KEY (subscriptionID) REFERENCES wcf1_paid_subscription (subscriptionID) ON DELETE SET NULL;
 ALTER TABLE wcf1_paid_subscription_transaction_log ADD FOREIGN KEY (paymentMethodObjectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_page ADD FOREIGN KEY (parentPageID) REFERENCES wcf1_page (pageID) ON DELETE SET NULL;
+ALTER TABLE wcf1_page ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
+
+ALTER TABLE wcf1_page_content ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE CASCADE;
+ALTER TABLE wcf1_page_content ADD FOREIGN KEY (languageID) REFERENCES wcf1_language (languageID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_page_menu_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 
 ALTER TABLE wcf1_search ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;