Added menu management (WIP)
authorMarcel Werk <burntime@woltlab.com>
Sat, 28 Nov 2015 20:07:16 +0000 (21:07 +0100)
committerMarcel Werk <burntime@woltlab.com>
Sat, 28 Nov 2015 20:07:16 +0000 (21:07 +0100)
24 files changed:
com.woltlab.wcf/acpMenu.xml
com.woltlab.wcf/userGroupOption.xml
wcfsetup/install/files/acp/templates/menuAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/menuItemAdd.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/menuItemList.tpl [new file with mode: 0644]
wcfsetup/install/files/acp/templates/menuList.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/MenuAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/MenuEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/MenuItemAddForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/MenuItemEditForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/MenuItemListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/MenuListPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/Menu.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/MenuAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/MenuEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/MenuList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/item/MenuItem.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/item/MenuItemAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/item/MenuItemEditor.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/item/MenuItemList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/item/MenuItemNode.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/menu/item/MenuItemNodeTree.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/PageAction.class.php
wcfsetup/setup/db/install.sql

index 003caf69269a936ceb067b3f76c001f87470169f..9aa827115a82efaabaabd431e7c41e9e084bbc6b 100644 (file)
                        <controller><![CDATA[wcf\acp\page\PageListPage]]></controller>
                        <parent>wcf.acp.menu.link.cms</parent>
                        <permissions>admin.content.cms.canManagePage</permissions>
-               </acpmenuitem> 
+               </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.cms.menu.list">
+                       <controller><![CDATA[wcf\acp\page\MenuListPage]]></controller>
+                       <parent>wcf.acp.menu.link.cms</parent>
+                       <permissions>admin.content.cms.canManageMenu</permissions>
+               </acpmenuitem>
        </import>
 </data>
index ddd2e3e283a2150cbde9c1afb6fc04015d04afe2..463227ae4cf413013c5f037189507d4767593b0d 100644 (file)
@@ -422,6 +422,14 @@ pdf]]></defaultvalue>
                                <usersonly>1</usersonly>
                        </option>
                        
+                       <option name="admin.content.cms.canManageMenu">
+                               <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/menuAdd.tpl b/wcfsetup/install/files/acp/templates/menuAdd.tpl
new file mode 100644 (file)
index 0000000..9178caf
--- /dev/null
@@ -0,0 +1,58 @@
+{include file='header' pageTitle='wcf.acp.menu.'|concat:$action}
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.menu.{$action}{/lang}</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='MenuList'}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.list{/lang}</span></a></li>
+                               
+                       {event name='contentNavigationButtons'}
+               </ul>
+       </nav>
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='MenuAdd'}{/link}{else}{link controller='MenuEdit' id=$menuID}{/link}{/if}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}wcf.global.form.data{/lang}</legend>
+                       
+                       <dl{if $errorField == 'title'} class="formError"{/if}>
+                               <dt><label for="title">{lang}wcf.global.title{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" id="title" name="title" value="{$i18nPlainValues['title']}" autofocus="autofocus" class="long" />
+                                       {if $errorField == 'title'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'title' || $errorType == 'multilingual'}
+                                                               {lang}wcf.global.form.error.{@$errorType}{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.menu.title.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                                       <small>{lang}wcf.acp.menu.title.description{/lang}</small>
+                                       {include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=false}
+                               </dd>
+                       </dl>
+                       
+                       {event name='dataFields'}
+               </fieldset>
+               
+               {event name='fieldsets'}
+       </div>
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/menuItemAdd.tpl b/wcfsetup/install/files/acp/templates/menuItemAdd.tpl
new file mode 100644 (file)
index 0000000..ba39e75
--- /dev/null
@@ -0,0 +1,182 @@
+{include file='header' pageTitle='wcf.acp.menu.item.'|concat:$action}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               var $isInternalLink = $('input[name=isInternalLink]').filter('[value=1]');
+               var $pageIDContainer = $('#pageIDContainer');
+               var $externalURLContainer = $('#externalURLContainer');
+               
+               function handleIsInternalLink() {
+                       if ($isInternalLink.is(':checked')) {
+                               $pageIDContainer.show();
+                               $externalURLContainer.hide();
+                       }
+                       else {
+                               $pageIDContainer.hide();
+                               $externalURLContainer.show();
+                       }
+               }
+               
+               $('input[name=isInternalLink]').change(handleIsInternalLink);
+               handleIsInternalLink();
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.menu.item.{$action}{/lang}</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='MenuItemList' id=$menuID}{/link}" class="button"><span class="icon icon16 fa-list"></span> <span>{lang}wcf.acp.menu.item.list{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtons'}
+               </ul>
+       </nav>
+</div>
+
+<form method="post" action="{if $action == 'add'}{link controller='MenuItemAdd'}{/link}{else}{link controller='MenuItemEdit' id=$itemID}{/link}{/if}">
+       <div class="container containerPadding marginTop">
+               <fieldset>
+                       <legend>{lang}wcf.acp.pageMenu.data{/lang}</legend>
+                       
+                       
+                       <dl{if $errorField == 'parentItemID'} class="formError"{/if}>
+                               <dt><label for="parentItemID">{lang}wcf.acp.menu.item.parentItem{/lang}</label></dt>
+                               <dd>
+                                       <select name="parentItemID" id="parentItemID">
+                                               <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                               
+                                               {foreach from=$menuItemNodeList item=menuItemNode}
+                                                       <option value="{@$menuItemNode->getMenuItem()->itemID}"{if $menuItemNode->getMenuItem()->itemID == $parentItemID} selected="selected"{/if}>{if $menuItemNode->getDepth() > 1}{@"&nbsp;&nbsp;&nbsp;&nbsp;"|str_repeat:($menuItemNode->getDepth() - 1)}{/if}{lang}{$menuItemNode->getMenuItem()->title}{/lang}</option>
+                                               {/foreach}
+                                       </select>
+                                       {if $errorField == 'parentItemID'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.menu.item.parentItemID.error.{$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl{if $errorField == 'title'} class="formError"{/if}>
+                               <dt><label for="title">{lang}wcf.global.name{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" name="title" id="title" value="{$title}" class="long" required="required" />
+                                       {if $errorField == 'title'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'multilingual'}
+                                                               {lang}wcf.global.form.error.multilingual{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.menu.item.title.error.{$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                                       
+                                       {include file='multipleLanguageInputJavascript' elementIdentifier='title' forceSelection=false}
+                               </dd>
+                       </dl>
+                       
+                       {event name='dataFields'}
+               </fieldset>
+               
+               <fieldset>
+                       <legend>{lang}wcf.acp.menu.item.link{/lang}</legend>
+                       
+                       <dl>
+                               <dt></dt>
+                               <dd class="floated">
+                                       <label><input type="radio" name="isInternalLink" value="1"{if $isInternalLink} checked="checked"{/if} /> {lang}wcf.acp.menu.item.link.internal{/lang}</label>
+                                       <label><input type="radio" name="isInternalLink" value="0"{if !$isInternalLink} checked="checked"{/if} /> {lang}wcf.acp.menu.item.link.external{/lang}</label>
+                               </dd>
+                       </dl>
+                       
+                       <dl id="pageIDContainer"{if $errorField == 'pageID'} class="formError"{/if}>
+                               <dt><label for="pageID">{lang}wcf.acp.page.parentPageID{/lang}</label></dt>
+                               <dd>
+                                       <select name="pageID" id="pageID">
+                                               <option value="0">{lang}wcf.global.noSelection{/lang}</option>
+                                               
+                                               {foreach from=$pageNodeList item=pageNode}
+                                                       <option value="{@$pageNode->getPage()->pageID}"{if $pageNode->getPage()->pageID == $pageID} 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 == 'pageID'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.menu.item.pageID.error.{@$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                               </dd>
+                       </dl>
+                       
+                       <dl id="externalURLContainer"{if $errorField == 'externalURL'} class="formError"{/if}>
+                               <dt><label for="externalURL">{lang}wcf.acp.menu.item.externalURL{/lang}</label></dt>
+                               <dd>
+                                       <input type="text" name="externalURL" id="externalURL" value="{$externalURL}" class="long" />
+                                       {if $errorField == 'externalURL'}
+                                               <small class="innerError">
+                                                       {if $errorType == 'empty'}
+                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                       {else}
+                                                               {lang}wcf.acp.menu.item.externalURL.error.{$errorType}{/lang}
+                                                       {/if}
+                                               </small>
+                                       {/if}
+                                       
+                                       {include file='multipleLanguageInputJavascript' elementIdentifier='externalURL' forceSelection=false}
+                               </dd>
+                       </dl>
+                       
+                       {event name='linkFields'}
+               </fieldset>
+               
+               <fieldset>
+                       <legend>{lang}wcf.acp.menu.item.advanced{/lang}</legend>
+                       
+                       <dl>
+                               <dt><label for="showOrder">{lang}wcf.acp.menu.item.showOrder{/lang}</label></dt>
+                               <dd>
+                                       <input type="number" name="showOrder" id="showOrder" value="{@$showOrder}" class="tiny" min="0" />
+                               </dd>
+                       </dl>
+                       
+                       {if $action == 'add' || !$menuItem->isLandingPage}
+                               <dl>
+                                       <dt></dt>
+                                       <dd>
+                                               <label><input type="checkbox" name="isDisabled" id="isDisabled" value="1"{if $isDisabled} checked="checked"{/if} /> <span>{lang}wcf.acp.menu.item.isDisabled{/lang}</span></label>
+                                       </dd>
+                               </dl>
+                       {/if}
+                       
+                       {event name='advancedFields'}
+               </fieldset>
+               
+               {event name='fields'}
+       </div>
+       
+       <div class="formSubmit">
+               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" />
+               {if $action == 'add'}<input type="hidden" name="menuID" value="{@$menuID}" />{/if}
+               {@SECURITY_TOKEN_INPUT_TAG}
+       </div>
+</form>
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/menuItemList.tpl b/wcfsetup/install/files/acp/templates/menuItemList.tpl
new file mode 100644 (file)
index 0000000..4ad5ea6
--- /dev/null
@@ -0,0 +1,69 @@
+{include file='header' pageTitle='wcf.acp.menu.item.list'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\menu\\item\\MenuItemAction', '.sortableNode', '> .sortableNodeLabel .jsDeleteButton');
+               new WCF.Action.Toggle('wcf\\data\\menu\\item\\MenuItemAction', '.sortableNode', '> .sortableNodeLabel .jsToggleButton');
+               new WCF.Sortable.List('menuItemList', 'wcf\\data\\menu\\item\\MenuItemAction', undefined, { protectRoot: true }, false, { menuID: '{@$menuID}' });
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.menu.item.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+       <nav>
+               <ul>
+                       <li><a href="{link controller='MenuItemAdd'}menuID={@$menuID}{/link}" class="button"><span class="icon icon16 fa-plus"></span> <span>{lang}wcf.acp.menu.item.add{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtonsTop'}
+               </ul>
+       </nav>
+</div>
+
+{hascontent}
+       <div id="menuItemList" class="container containerPadding sortableListContainer marginTop">
+               <ol class="sortableList" data-object-id="0">
+                       {content}
+                               {foreach from=$menuItemNodeList item=menuItemNode}
+                                       <li class="sortableNode" data-object-id="{@$menuItemNode->getMenuItem()->itemID}">
+                                               <span class="sortableNodeLabel">
+                                                       <a href="{link controller='MenuItemEdit' id=$menuItemNode->getMenuItem()->itemID}{/link}">{lang}{$menuItemNode->getMenuItem()->title}{/lang}</a>
+                                                       <span class="statusDisplay sortableButtonContainer">
+                                                               {if $menuItemNode->getMenuItem()->canDisable()}
+                                                                       <span class="icon icon16 icon-check{if $menuItemNode->getMenuItem()->isDisabled}-empty{/if} jsToggleButton jsTooltip pointer" title="{lang}wcf.global.button.{if $menuItemNode->getMenuItem()->isDisabled}enable{else}disable{/if}{/lang}" data-object-id="{@$menuItemNode->getMenuItem()->itemID}"></span>
+                                                               {else}
+                                                                       <span class="icon icon16 icon-check{if $menuItemNode->getMenuItem()->isDisabled}-empty{/if} disabled" title="{lang}wcf.global.button.{if $menuItemNode->getMenuItem()->isDisabled}enable{else}disable{/if}{/lang}"></span>
+                                                               {/if}
+                                                               <a href="{link controller='MenuItemEdit' id=$menuItemNode->getMenuItem()->itemID}{/link}" class="jsTooltip" title="{lang}wcf.global.button.edit{/lang}"><span class="icon icon16 fa-pencil"></span></a>
+                                                               {if $menuItemNode->getMenuItem()->canDelete()}
+                                                                       <span class="icon icon16 fa-times jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$menuItemNode->getMenuItem()->itemID}" data-confirm-message="{lang menuItem=$menuItemNode->getMenuItem()}wcf.acp.menu.item.delete.confirmMessage{/lang}"></span>
+                                                               {else}
+                                                                       <span class="icon icon16 fa-times disabled" title="{lang}wcf.global.button.delete{/lang}"></span>
+                                                               {/if}
+                                                               
+                                                               {event name='itemButtons'}
+                                                       </span>
+                                               </span>
+                                       
+                                               <ol class="sortableList" data-object-id="{@$menuItemNode->getMenuItem()->itemID}">{if !$menuItemNode->hasChildren()}</ol></li>{/if}
+                                               
+                                               {if !$menuItemNode->hasChildren() && $menuItemNode->isLastSibling()}
+                                                       {@"</ol></li>"|str_repeat:$menuItemNode->getOpenParentNodes()}
+                                               {/if}
+                               {/foreach}
+                       {/content}
+               </ol>
+       </div>
+       
+       <div class="formSubmit">
+               <button class="button buttonPrimary" data-type="submit">{lang}wcf.global.button.saveSorting{/lang}</button>
+       </div>
+{hascontentelse}
+       <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/hascontent}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/acp/templates/menuList.tpl b/wcfsetup/install/files/acp/templates/menuList.tpl
new file mode 100644 (file)
index 0000000..956f9b1
--- /dev/null
@@ -0,0 +1,83 @@
+{include file='header' pageTitle='wcf.acp.menu.list'}
+
+<script data-relocate="true">
+       //<![CDATA[
+       $(function() {
+               new WCF.Action.Delete('wcf\\data\\menu\\MenuAction', '.jsMenuRow');
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.menu.list{/lang}</h1>
+</header>
+
+<div class="contentNavigation">
+       {pages print=true assign=pagesLinks controller="MenuList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}
+       
+       <nav>
+               <ul>
+                       <li><a href="{link controller='MenuAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.menu.add{/lang}</span></a></li>
+                       
+                       {event name='contentNavigationButtonsTop'}
+               </ul>
+       </nav>
+</div>
+
+{if $objects|count}
+       <div class="tabularBox tabularBoxTitle marginTop">
+               <header>
+                       <h2>{lang}wcf.acp.menu.list{/lang} <span class="badge badgeInverse">{#$items}</span></h2>
+               </header>
+               
+               <table class="table">
+                       <thead>
+                               <tr>
+                                       <th class="columnPageID{if $sortField == 'menuID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='MenuList'}pageNo={@$pageNo}&sortField=menuID&sortOrder={if $sortField == 'menuID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
+                                       <th class="columnTitle{if $sortField == 'title'} active {@$sortOrder}{/if}"><a href="{link controller='MenuList'}pageNo={@$pageNo}&sortField=title&sortOrder={if $sortField == 'title' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.name{/lang}</a></th>
+                                       <th class="columnDigits columnItems{if $sortField == 'customURL'} active {@$sortOrder}{/if}"><a href="{link controller='MenuList'}pageNo={@$pageNo}&sortField=items&sortOrder={if $sortField == 'items' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.menu.items{/lang}</a></th>
+                                       
+                                       {event name='columnHeads'}
+                               </tr>
+                       </thead>
+                       
+                       <tbody>
+                               {foreach from=$objects item=menu}
+                                       <tr class="jsMenuRow">
+                                               <td class="columnIcon">
+                                                       <a href="{link controller='MenuEdit' id=$menu->menuID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip"><span class="icon icon16 icon-pencil"></span></a>
+                                                       <a href="{link controller='MenuItemList' id=$menu->menuID}{/link}" title="{lang}wcf.acp.menu.item.list{/lang}" class="jsTooltip"><span class="icon icon16 icon-list"></span></a>
+                                                       {if $menu->canDelete()}
+                                                               <span class="icon icon16 icon-remove jsDeleteButton jsTooltip pointer" title="{lang}wcf.global.button.delete{/lang}" data-object-id="{@$menu->menuID}" data-confirm-message="{lang}wcf.acp.menu.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">{@$menu->menuID}</td>
+                                               <td class="columnTitle"><a href="{link controller='MenuItemList' id=$menu->menuID}{/link}">{lang}{$menu->title}{/lang}</a></td>
+                                               <td class="columnDigits columnItems">{#$menu->items}</td>
+                                               
+                                               {event name='columns'}
+                                       </tr>
+                               {/foreach}
+                       </tbody>
+               </table>
+       </div>
+       
+       <div class="contentNavigation">
+               {@$pagesLinks}
+               
+               
+               <nav>
+                       <ul>
+                               <li><a href="{link controller='MenuAdd'}{/link}" class="button"><span class="icon icon16 icon-plus"></span> <span>{lang}wcf.acp.menu.add{/lang}</span></a></li>
+                       
+                               {event name='contentNavigationButtonsBottom'}
+                       </ul>
+               </nav>
+       </div>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/lib/acp/form/MenuAddForm.class.php b/wcfsetup/install/files/lib/acp/form/MenuAddForm.class.php
new file mode 100644 (file)
index 0000000..42f0364
--- /dev/null
@@ -0,0 +1,127 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\menu\MenuAction;
+use wcf\data\menu\MenuEditor;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the menu 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 MenuAddForm extends AbstractForm {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.menu.list';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.cms.canManageMenu');
+       
+       /**
+        * menu title
+        * @var string
+        */
+       public $title = '';
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+       
+               I18nHandler::getInstance()->register('title');
+       }
+       
+       /**
+        * @see \wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               I18nHandler::getInstance()->readValues();
+               
+               if (I18nHandler::getInstance()->isPlainValue('title')) $this->title = I18nHandler::getInstance()->getValue('title');
+       }
+       
+       /**
+        * @see \wcf\form\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+               
+               // validate menu title
+               if (!I18nHandler::getInstance()->validateValue('title')) {
+                       if (I18nHandler::getInstance()->isPlainValue('title')) {
+                               throw new UserInputException('title');
+                       }
+                       else {
+                               throw new UserInputException('title', 'multilingual');
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               parent::save();
+               
+               // save label
+               $this->objectAction = new MenuAction(array(), 'create', array('data' => array_merge($this->additionalFields, array(
+                       'title' => $this->title,
+                       'packageID' => 1,
+                       'identifier' => ''
+               ))));
+               $returnValues = $this->objectAction->executeAction();
+               // set generic identifier
+               $menuEditor = new MenuEditor($returnValues['returnValues']);
+               $menuEditor->update(array(
+                       'identifier' => 'com.woltlab.wcf.generic'.$menuEditor->menuID
+               ));
+               // save i18n
+               if (!I18nHandler::getInstance()->isPlainValue('title')) {
+                       I18nHandler::getInstance()->save('title', 'wcf.menu.menu'.$menuEditor->menuID, 'wcf.menu', 1);
+                               
+                       // update title
+                       $menuEditor->update(array(
+                               'title' => 'wcf.menu.menu'.$menuEditor->menuID
+                       ));
+               }
+               $this->saved();
+               
+               // reset values
+               $this->title = '';
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+               
+               I18nHandler::getInstance()->reset();
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'add',
+                       'title' => 'title'
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/MenuEditForm.class.php b/wcfsetup/install/files/lib/acp/form/MenuEditForm.class.php
new file mode 100644 (file)
index 0000000..5e07c2e
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\menu\MenuAction;
+use wcf\data\menu\Menu;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the menu 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 MenuEditForm extends MenuAddForm {
+       /**
+        * menu id
+        * @var integer
+        */
+       public $menuID = 0;
+       
+       /**
+        * menu object
+        * @var \wcf\data\menu\Menu
+        */
+       public $menu = null;
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+       
+               if (isset($_REQUEST['id'])) $this->menuID = intval($_REQUEST['id']);
+               $this->menu = new Menu($this->menuID);
+               if (!$this->menu->menuID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               AbstractForm::save();
+       
+               $this->title = 'wcf.menu.menu'.$this->menu->menuID;
+               if (I18nHandler::getInstance()->isPlainValue('title')) {
+                       I18nHandler::getInstance()->remove($this->title);
+                       $this->title = I18nHandler::getInstance()->getValue('title');
+               }
+               else {
+                       I18nHandler::getInstance()->save('title', $this->title, 'wcf.menu', 1);
+               }
+       
+               // update menu
+               $this->objectAction = new MenuAction(array($this->menuID), 'update', array('data' => array_merge($this->additionalFields, array(
+                       'title' => $this->title
+               ))));
+               $this->objectAction->executeAction();
+               $this->saved();
+       
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               if (empty($_POST)) {
+                       I18nHandler::getInstance()->setOptions('title', 1, $this->menu->title, 'wcf.menu.menu\d+');
+                       
+                       $this->title = $this->menu->title;
+               }
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables(!empty($_POST));
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'edit',
+                       'menuID' => $this->menuID,
+                       'menu' => $this->menu
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/MenuItemAddForm.class.php b/wcfsetup/install/files/lib/acp/form/MenuItemAddForm.class.php
new file mode 100644 (file)
index 0000000..a3b343d
--- /dev/null
@@ -0,0 +1,259 @@
+<?php
+namespace wcf\acp\form;
+use wcf\form\AbstractForm;
+use wcf\system\exception\UserInputException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+use wcf\data\menu\Menu;
+use wcf\system\exception\IllegalLinkException;
+use wcf\data\menu\item\MenuItemNodeTree;
+use wcf\data\page\Page;
+use wcf\data\menu\item\MenuItem;
+use wcf\data\menu\item\MenuItemAction;
+use wcf\data\menu\item\MenuItemEditor;
+use wcf\data\page\PageNodeTree;
+
+/**
+ * Shows the menu item 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 MenuItemAddForm extends AbstractForm {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.menu.list';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.cms.canManageMenu');
+       
+       /**
+        * menu id
+        * @var integer
+        */
+       public $menuID = 0;
+       
+       /**
+        * menu object
+        * @var \wcf\data\menu\Menu
+        */
+       public $menu = null;
+       
+       /**
+        * activation state
+        * @var boolean
+        */
+       public $isDisabled = false;
+       
+       /**
+        * internal link
+        * @var boolean
+        */
+       public $isInternalLink = true;
+       
+       /**
+        * page id
+        * @var integer
+        */
+       public $pageID = null;
+       
+       /**
+        * menu item title
+        * @var string
+        */
+       public $title = '';
+       
+       /**
+        * external url
+        * @var string
+        */
+       public $externalURL = '';
+       
+       /**
+        * id of the parent menu item
+        * @var integer
+        */
+       public $parentItemID = null;
+       
+       /**
+        * show order
+        * @var integer
+        */
+       public $showOrder = 0;
+       
+       /**
+        * menu item node tree
+        * @var wcf\data\menu\item\MenuItemNodeTree
+        */
+       public $menuItems = null;
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_REQUEST['menuID'])) $this->menuID = intval($_REQUEST['menuID']);
+               $this->menu = new Menu($this->menuID);
+               if (!$this->menu->menuID) {
+                       throw new IllegalLinkException();
+               }
+               
+               I18nHandler::getInstance()->register('title');
+               I18nHandler::getInstance()->register('externalURL');
+       }
+       
+       /**
+        * @see \wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+               
+               I18nHandler::getInstance()->readValues();
+               if (I18nHandler::getInstance()->isPlainValue('title')) $this->title = I18nHandler::getInstance()->getValue('title');
+               if (I18nHandler::getInstance()->isPlainValue('externalURL')) $this->externalURL = I18nHandler::getInstance()->getValue('externalURL');
+               
+               if (isset($_POST['isDisabled'])) $this->isDisabled = true;
+               $this->isInternalLink = false;
+               if (isset($_POST['isInternalLink'])) $this->isInternalLink = (bool) $_POST['isInternalLink'];
+               if (!empty($_POST['pageID'])) $this->pageID = intval($_POST['pageID']);
+               if (!empty($_POST['parentItemID'])) $this->parentItemID = intval($_POST['parentItemID']);
+               if (isset($_POST['showOrder'])) $this->showOrder = intval($_POST['showOrder']);
+       }
+       
+       /**
+        * @see \wcf\form\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+               
+               // validate menu item controller
+               if ($this->isInternalLink) {
+                       $this->externalURL = '';
+                       
+                       if (!$this->pageID) {
+                               throw new UserInputException('pageID');
+                       }
+                       $page = new Page($this->pageID);
+                       if (!$page->pageID) {
+                               throw new UserInputException('pageID', 'invalid');
+                       }
+               }
+               else {
+                       $this->pageID = null;
+                       
+                       // validate external url
+                       if (!I18nHandler::getInstance()->validateValue('externalURL')) {
+                               throw new UserInputException('externalURL');
+                       }
+               }
+               
+               // validate page menu item name
+               if (!I18nHandler::getInstance()->validateValue('title')) {
+                       throw new UserInputException('title');
+               }
+               
+               // validate parent menu item
+               if ($this->parentItemID) {
+                       $parentMenuItem = new MenuItem($this->parentItemID);
+                       if (!$parentMenuItem->itemID || $parentMenuItem->menuID != $this->menuID) {
+                               throw new UserInputException('parentItemID', 'invalid');
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               parent::save();
+               
+               $this->objectAction = new MenuItemAction(array(), 'create', array('data' => array_merge($this->additionalFields, array(
+                       'isDisabled' => ($this->isDisabled) ? 1 : 0,
+                       'title' => $this->title,
+                       'pageID' => $this->pageID,
+                       'externalURL' => $this->externalURL,
+                       'menuID' => $this->menuID,
+                       'parentItemID' => $this->parentItemID,
+                       'showOrder' => $this->showOrder,
+                       'identifier' => StringUtil::getRandomID()
+               ))));
+               $this->objectAction->executeAction();
+               
+               $returnValues = $this->objectAction->getReturnValues();
+               $menuItem = $returnValues['returnValues'];
+               
+               // set generic identifier
+               $data = array(
+                       'identifier' => 'com.woltlab.wcf.generic'.$menuItem->itemID
+               );
+               if (!I18nHandler::getInstance()->isPlainValue('title')) {
+                       I18nHandler::getInstance()->save('title', 'wcf.menu.item.title'.$menuItem->itemID, 'wcf.menu');
+                       $data['title'] = 'wcf.menu.item.title'.$menuItem->itemID;
+               }
+               if (!I18nHandler::getInstance()->isPlainValue('externalURL')) {
+                       I18nHandler::getInstance()->save('externalURL', 'wcf.menu.item.externalURL'.$menuItem->itemID, 'wcf.menu');
+                       $data['externalURL'] = 'wcf.menu.item.externalURL'.$menuItem->itemID;
+               }
+               
+               // update values
+               $menuItemEditor = new MenuItemEditor($menuItem);
+               $menuItemEditor->update($data);
+               
+               // call saved event
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign('success', true);
+               
+               // reset variables
+               $this->isDisabled = $this->isInternalLink = false;
+               $this->pageID = $this->parentItemID = null;
+               $this->externalURL = $this->title = '';
+               $this->showOrder = 0;
+               
+               I18nHandler::getInstance()->reset();
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+       
+               $this->menuItems = new MenuItemNodeTree($this->menuID);
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               I18nHandler::getInstance()->assignVariables();
+               $pageNodeList = new PageNodeTree();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'add',
+                       'menuID' => $this->menuID,
+                       'menu' => $this->menu,
+                       'isDisabled' => $this->isDisabled,
+                       'isInternalLink' => $this->isInternalLink,
+                       'pageID' => $this->pageID,
+                       'title' => $this->title,
+                       'externalURL' => $this->externalURL,
+                       'parentItemID' => $this->parentItemID,
+                       'showOrder' => $this->showOrder,
+                       'menuItemNodeList' => $this->menuItems->getNodeList(),
+                       'pageNodeList' => $pageNodeList->getNodeList()
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/form/MenuItemEditForm.class.php b/wcfsetup/install/files/lib/acp/form/MenuItemEditForm.class.php
new file mode 100644 (file)
index 0000000..abd683a
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\menu\item\MenuItem;
+use wcf\data\menu\item\MenuItemAction;
+use wcf\data\menu\Menu;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\language\I18nHandler;
+use wcf\system\WCF;
+
+/**
+ * Shows the menu item 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 MenuItemEditForm extends MenuItemAddForm {
+       /**
+        * menu item id
+        * @var integer
+        */
+       public $itemID = 0;
+       
+       /**
+        * menu object
+        * @var \wcf\data\menu\Menu
+        */
+       public $menuItem = null;
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               AbstractForm::readParameters();
+               
+               if (isset($_REQUEST['id'])) $this->itemID = intval($_REQUEST['id']);
+               $this->menuItem = new MenuItem($this->itemID);
+               if (!$this->menuItem->itemID) {
+                       throw new IllegalLinkException();
+               }
+               
+               $this->menu = new Menu($this->menuItem->menuID);
+               $this->menuID = $this->menu->menuID;
+               
+               I18nHandler::getInstance()->register('title');
+               I18nHandler::getInstance()->register('externalURL');
+       }
+       
+       /**
+        * @see \wcf\form\IForm::save()
+        */
+       public function save() {
+               AbstractForm::save();
+               
+               $this->title = 'wcf.menu.item.title'.$this->menuItem->itemID;
+               if (I18nHandler::getInstance()->isPlainValue('title')) {
+                       I18nHandler::getInstance()->remove($this->title);
+                       $this->title = I18nHandler::getInstance()->getValue('title');
+               }
+               else {
+                       I18nHandler::getInstance()->save('title', $this->title, 'wcf.menu', 1);
+               }
+               $this->externalURL = 'wcf.menu.item.externalURL'.$this->menuItem->itemID;
+               if (I18nHandler::getInstance()->isPlainValue('externalURL')) {
+                       I18nHandler::getInstance()->remove($this->externalURL);
+                       $this->externalURL = I18nHandler::getInstance()->getValue('externalURL');
+               }
+               else {
+                       I18nHandler::getInstance()->save('externalURL', $this->externalURL, 'wcf.menu', 1);
+               }
+               
+               // update menu
+               $this->objectAction = new MenuItemAction(array($this->itemID), 'update', array('data' => array_merge($this->additionalFields, array(
+                       'isDisabled' => ($this->isDisabled) ? 1 : 0,
+                       'title' => $this->title,
+                       'pageID' => $this->pageID,
+                       'externalURL' => $this->externalURL,
+                       'parentItemID' => $this->parentItemID,
+                       'showOrder' => $this->showOrder
+               ))));
+               $this->objectAction->executeAction();
+               $this->saved();
+               
+               // show success
+               WCF::getTPL()->assign(array(
+                       'success' => true
+               ));
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+       
+               if (empty($_POST)) {
+                       I18nHandler::getInstance()->setOptions('title', 1, $this->menuItem->title, 'wcf.menu.item.title\d+');
+                       I18nHandler::getInstance()->setOptions('externalURL', 1, $this->menuItem->externalURL, 'wcf.menu.item.externalURL\d+');
+                       
+                       $this->parentItemID = $this->menuItem->parentItemID;
+                       $this->title = $this->menuItem->title;
+                       $this->pageID = $this->menuItem->pageID;
+                       $this->externalURL = $this->menuItem->externalURL;
+                       $this->showOrder = $this->menuItem->showOrder;
+                       $this->isDisabled = $this->menuItem->isDisabled;
+                       if (!$this->pageID) {
+                               $this->isInternalLink = false;
+                       }
+               }
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'action' => 'edit',
+                       'itemID' => $this->itemID,
+                       'menuItem' => $this->menuItem
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/MenuItemListPage.class.php b/wcfsetup/install/files/lib/acp/page/MenuItemListPage.class.php
new file mode 100644 (file)
index 0000000..bfb97a9
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+namespace wcf\acp\page;
+use wcf\data\menu\item\MenuItemNodeTree;
+use wcf\data\menu\Menu;
+use wcf\page\AbstractPage;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+
+/**
+ * Shows a list of menu items.
+ * 
+ * @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 MenuItemListPage extends AbstractPage {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.menu.list';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.display.canManagePageMenu');
+       
+       /**
+        * menu item list
+        * @var \wcf\data\menu\item\MenuItemList
+        */
+       public $objectList = null;
+       
+       /**
+        * menu item node tree
+        * @var wcf\data\menu\item\MenuItemNodeTree
+        */
+       public $menuItems = null;
+               
+       /**
+        * menu id
+        * @var integer
+        */
+       public $menuID = 0;
+       
+       /**
+        * menu object
+        * @var \wcf\data\menu\Menu
+        */
+       public $menu = null;
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+       
+               if (isset($_REQUEST['id'])) $this->menuID = intval($_REQUEST['id']);
+               $this->menu = new Menu($this->menuID);
+               if (!$this->menu->menuID) {
+                       throw new IllegalLinkException();
+               }
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               $this->menuItems = new MenuItemNodeTree($this->menuID);
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'menuID' => $this->menuID,
+                       'menu' => $this->menu,
+                       'menuItemNodeList' => $this->menuItems->getNodeList()
+               ));
+       }
+}
diff --git a/wcfsetup/install/files/lib/acp/page/MenuListPage.class.php b/wcfsetup/install/files/lib/acp/page/MenuListPage.class.php
new file mode 100644 (file)
index 0000000..47c57d6
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace wcf\acp\page;
+use wcf\page\SortablePage;
+
+/**
+ * Shows a list of menus.
+ * 
+ * @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 MenuListPage extends SortablePage {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.cms.menu.list';
+       
+       /**
+        * @see \wcf\page\MultipleLinkPage::$objectListClassName
+        */
+       public $objectListClassName = 'wcf\data\menu\MenuList';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.content.cms.canManageMenu');
+       
+       /**
+        * @see \wcf\page\SortablePage::$defaultSortField
+        */
+       public $defaultSortField = 'title';
+       
+       /**
+        * @see \wcf\page\SortablePage::$validSortFields
+        */
+       public $validSortFields = array('menuID', 'title', 'items');
+}
diff --git a/wcfsetup/install/files/lib/data/menu/Menu.class.php b/wcfsetup/install/files/lib/data/menu/Menu.class.php
new file mode 100644 (file)
index 0000000..16287ce
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+namespace wcf\data\menu;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a menu.
+ * 
+ * @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.menu
+ * @category   Community Framework
+ */
+class Menu extends DatabaseObject {
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'menu';
+       
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseTableIndexName
+        */
+       protected static $databaseTableIndexName = 'menuID';
+       
+       /**
+        * Returns true if the active user can delete this menu.
+        * 
+        * @return boolean
+        */
+       public function canDelete() {
+               if (WCF::getSession()->getPermission('admin.content.cms.canManageMenu') && !$this->originIsSystem) {
+                       return true;
+               }
+                       
+               return false;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/menu/MenuAction.class.php b/wcfsetup/install/files/lib/data/menu/MenuAction.class.php
new file mode 100644 (file)
index 0000000..f4d45e4
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+namespace wcf\data\menu;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\PermissionDeniedException;
+
+/**
+ * Executes menu 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.menu
+ * @category   Community Framework
+ */
+class MenuAction extends AbstractDatabaseObjectAction {
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$className
+        */
+       protected $className = 'wcf\data\menu\MenuEditor';
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsCreate
+        */
+       protected $permissionsCreate = array('admin.content.cms.canManageMenu');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+       */
+       protected $permissionsDelete = array('admin.content.cms.canManageMenu');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+       */
+       protected $permissionsUpdate = array('admin.content.cms.canManageMenu');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$requireACP
+        */
+       protected $requireACP = array('create', 'delete', 'update');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::validateDelete()
+        */
+       public function validateDelete() {
+               parent::validateDelete();
+               
+               foreach ($this->objects as $object) {
+                       if (!$object->canDelete()) {
+                               throw new PermissionDeniedException();
+                       }
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/menu/MenuEditor.class.php b/wcfsetup/install/files/lib/data/menu/MenuEditor.class.php
new file mode 100644 (file)
index 0000000..ec2d8f1
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\menu;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit menus.
+ * 
+ * @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.menu
+ * @category   Community Framework
+ */
+class MenuEditor extends DatabaseObjectEditor {
+       /**
+        * @see \wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\menu\Menu';
+}
diff --git a/wcfsetup/install/files/lib/data/menu/MenuList.class.php b/wcfsetup/install/files/lib/data/menu/MenuList.class.php
new file mode 100644 (file)
index 0000000..e258bd6
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\menu;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of menus.
+ * 
+ * @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.menu
+ * @category   Community Framework
+ */
+class MenuList extends DatabaseObjectList {
+       /**
+        * @see \wcf\data\DatabaseObjectList::$className
+        */
+       public $className = 'wcf\data\menu\Menu';
+}
diff --git a/wcfsetup/install/files/lib/data/menu/item/MenuItem.class.php b/wcfsetup/install/files/lib/data/menu/item/MenuItem.class.php
new file mode 100644 (file)
index 0000000..ed6e8e1
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+namespace wcf\data\menu\item;
+use wcf\data\DatabaseObject;
+use wcf\system\WCF;
+
+/**
+ * Represents a menu item.
+ * 
+ * @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.menu.item
+ * @category   Community Framework
+ */
+class MenuItem extends DatabaseObject {
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseTableName
+        */
+       protected static $databaseTableName = 'menu_item';
+       
+       /**
+        * @see \wcf\data\DatabaseObject::$databaseTableIndexName
+        */
+       protected static $databaseTableIndexName = 'itemID';
+       
+       /**
+        * Returns true if the active user can delete this menu item.
+        *
+        * @return boolean
+        */
+       public function canDelete() {
+               if (WCF::getSession()->getPermission('admin.content.cms.canManageMenu') && !$this->originIsSystem) {
+                       return true;
+               }
+                       
+               return false;
+       }
+       
+       /**
+        * Returns true if the active user can disable this menu item.
+        *
+        * @return boolean
+        */
+       public function canDisable() {
+               if (WCF::getSession()->getPermission('admin.content.cms.canManageMenu')) {
+                       return true;
+               }
+                       
+               return false;
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/menu/item/MenuItemAction.class.php b/wcfsetup/install/files/lib/data/menu/item/MenuItemAction.class.php
new file mode 100644 (file)
index 0000000..f48b2bd
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+namespace wcf\data\menu\item;
+use wcf\data\AbstractDatabaseObjectAction;
+use wcf\data\IToggleAction;
+use wcf\system\exception\PermissionDeniedException;
+
+/**
+ * Executes menu item 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.menu.item
+ * @category   Community Framework
+ */
+class MenuItemAction extends AbstractDatabaseObjectAction implements IToggleAction {
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$className
+        */
+       protected $className = 'wcf\data\menu\item\MenuItemEditor';
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsCreate
+        */
+       protected $permissionsCreate = array('admin.content.cms.canManageMenu');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsDelete
+       */
+       protected $permissionsDelete = array('admin.content.cms.canManageMenu');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
+       */
+       protected $permissionsUpdate = array('admin.content.cms.canManageMenu');
+       
+       /**
+        * @see \wcf\data\AbstractDatabaseObjectAction::$requireACP
+        */
+       protected $requireACP = array('create', 'delete', 'toggle', 'update');
+       
+       /**
+        * @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/menu/item/MenuItemEditor.class.php b/wcfsetup/install/files/lib/data/menu/item/MenuItemEditor.class.php
new file mode 100644 (file)
index 0000000..5e37002
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\menu\item;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit menu items.
+ * 
+ * @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.menu.item
+ * @category   Community Framework
+ */
+class MenuItemEditor extends DatabaseObjectEditor {
+       /**
+        * @see \wcf\data\DatabaseObjectDecorator::$baseClass
+        */
+       protected static $baseClass = 'wcf\data\menu\item\MenuItem';
+}
diff --git a/wcfsetup/install/files/lib/data/menu/item/MenuItemList.class.php b/wcfsetup/install/files/lib/data/menu/item/MenuItemList.class.php
new file mode 100644 (file)
index 0000000..3d960de
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+namespace wcf\data\menu\item;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of menu items.
+ * 
+ * @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.menu.item
+ * @category   Community Framework
+ */
+class MenuItemList extends DatabaseObjectList {
+       /**
+        * @see \wcf\data\DatabaseObjectList::$className
+        */
+       public $className = 'wcf\data\menu\item\MenuItem';
+}
diff --git a/wcfsetup/install/files/lib/data/menu/item/MenuItemNode.class.php b/wcfsetup/install/files/lib/data/menu/item/MenuItemNode.class.php
new file mode 100644 (file)
index 0000000..6907b79
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+namespace wcf\data\menu\item;
+
+/**
+ * Represents a menu item 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.menu.item
+ * @category   Community Framework
+ */
+class MenuItemNode implements \Countable, \RecursiveIterator {
+       /**
+        * parent node
+        * @var \wcf\data\menu\item\MenuItemNode
+        */
+       protected $parentNode = null;
+       
+       /**
+        * children of this node
+        * @var array<\wcf\data\menu\item\MenuItemNode>
+        */
+       protected $children = array();
+       
+       /**
+        * menu item object
+        * @var \wcf\data\menu\item\MenuItem
+        */
+       protected $menuItem = null;
+       
+       /**
+        * node depth
+        * @var integer
+        */
+       protected $depth = 0;
+       
+       /**
+        * iterator position
+        * @var integer
+        */
+       private $position = 0;
+       
+       /**
+        * Creates a new MenuItemNode object.
+        * 
+        * @param       \wcf\data\menu\item\MenuItemNode        $parentNode
+        * @param       \wcf\data\menu\item\MenuItem            $menuItem
+        * @param       integer                                 $depth
+        */
+       public function __construct($parentNode = null, MenuItem $menuItem = null, $depth = 0) {
+               $this->parentNode = $parentNode;
+               $this->menuItem = $menuItem;
+               $this->depth = $depth;
+       }
+       
+       /**
+        * Sets the children of this node.
+        * 
+        * @param       array<\wcf\data\menu\item\MenuItemNode>         $children
+        */
+       public function setChildren(array $children) {
+               $this->children = $children;
+       }
+       
+       /**
+        * Returns the parent node
+        * 
+        * @return      \wcf\data\menu\item\MenuItemNode
+        */
+       public function getParentNode() {
+               return $this->parentNode;
+       }
+       
+       /**
+        * Returns the menu item object of this node.
+        * 
+        * @return      \wcf\data\menu\item\MenuItem
+        */
+       public function getMenuItem() {
+               return $this->menuItem;
+       }
+       
+       /**
+        * Returns the number of children.
+        * 
+        * @return      integer
+        */
+       public function count() {
+               return count($this->children);
+       }
+       
+       /**
+        * Returns true if this element is the last sibling.
+        *
+        * @return      boolean
+        */
+       public function isLastSibling() {
+               foreach ($this->parentNode as $key => $child) {
+                       if ($child === $this) {
+                               if ($key == count($this->parentNode) - 1) return true;
+                               return false;
+                       }
+               }
+       }
+       
+       /**
+        * Returns the number of open parent nodes.
+        *
+        * @return      integer
+        */
+       public function getOpenParentNodes() {
+               $element = $this;
+               $i = 0;
+       
+               while ($element->parentNode->parentNode != null && $element->isLastSibling()) {
+                       $i++;
+                       $element = $element->parentNode;
+               }
+       
+               return $i;
+       }
+       
+       /**
+        * @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/menu/item/MenuItemNodeTree.class.php b/wcfsetup/install/files/lib/data/menu/item/MenuItemNodeTree.class.php
new file mode 100644 (file)
index 0000000..3689193
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+namespace wcf\data\menu\item;
+
+/**
+ * Represents a menu item 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.menu.item
+ * @category   Community Framework
+ */
+class MenuItemNodeTree {
+       /**
+        * menu id
+        * @var integer
+        */
+       public $menuID = null;
+       
+       /**
+        * list of menu items
+        * @var array<\wcf\data\menu\item\MenuItem>
+        */
+       public $menuItems = array();
+       
+       /**
+        * menu item structure
+        * @var array<array>
+        */
+       public $menuItemStructure = array();
+       
+       /**
+        * root node
+        * @var \wcf\data\menu\item\MenuItemNode
+        */
+       public $node = null;
+       
+       /**
+        * Creates a new MenuItemNodeTree object.
+        *
+        * @param       integer                 $menuID
+        */
+       public function __construct($menuID) {
+               $this->menuID = $menuID;
+               
+               // load menu items
+               $menuItemList = new MenuItemList();
+               $menuItemList->getConditionBuilder()->add('menu_item.menuID = ?', array($this->menuID));
+               $menuItemList->sqlOrderBy = "menu_item.showOrder";
+               $menuItemList->readObjects();
+               
+               foreach ($menuItemList as $menuItem) {
+                       $this->menuItems[$menuItem->itemID] = $menuItem;
+                               
+                       if (!isset($this->menuItemStructure[$menuItem->parentItemID])) {
+                               $this->menuItemStructure[$menuItem->parentItemID] = array();
+                       }
+                       $this->menuItemStructure[$menuItem->parentItemID][] = $menuItem->itemID;
+               }
+               
+               // generate node tree
+               $this->node = new MenuItemNode();
+               $this->node->setChildren($this->generateNodeTree(null, $this->node));
+       }
+       
+       /**
+        * Generates the node tree recursively
+        * 
+        * @param       integer                                 $parentID
+        * @param       \wcf\data\menu\item\MenuItemNode        $parentNode
+        * @param       array<integer>                          $filter
+        * @return      array<\wcf\data\menu\item\MenuItemNode>
+        */
+       protected function generateNodeTree($parentID = null, MenuItemNode $parentNode = null) {
+               $nodes = array();
+               
+               $itemIDs = (isset($this->menuItemStructure[$parentID]) ? $this->menuItemStructure[$parentID] : array());
+               foreach ($itemIDs as $itemID) {
+                       $menuItem = $this->menuItems[$itemID];
+                       $node = new MenuItemNode($parentNode, $menuItem, ($parentNode !== null ? ($parentNode->getDepth() + 1) : 0));
+                       $nodes[] = $node;
+                               
+                       // get children
+                       $node->setChildren($this->generateNodeTree($itemID, $node));
+               }
+               
+               return $nodes;
+       }
+       
+       /**
+        * Returns the menu item node tree.
+        * 
+        * @return      array<\wcf\data\menu\item\MenuItemNode>
+        */
+       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 5c1cfc1822e586351d00e0f934661a3029038ba1..6d3242553897a63f3a87b55a154dc3f66134269e 100644 (file)
@@ -122,7 +122,7 @@ class PageAction extends AbstractDatabaseObjectAction implements IToggleAction {
        
        /**
         * @see \wcf\data\IToggleAction::validateToggle()
-       */
+        */
        public function validateToggle() {
                parent::validateUpdate();
                
index d2c208be3452b6e629ef3e8b3b078ebf5128e4ca..63d8fa3ea3bf3d5dd25534c5e7f594b7e8e9021e 100644 (file)
@@ -506,6 +506,29 @@ CREATE TABLE wcf1_like_object (
        UNIQUE KEY (objectTypeID, objectID)
 );
 
+DROP TABLE IF EXISTS wcf1_menu;
+CREATE TABLE wcf1_menu (
+       menuID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       identifier VARCHAR(255) NOT NULL,
+       title VARCHAR(255) NOT NULL,
+       originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
+       packageID INT(10) NOT NULL
+);
+
+DROP TABLE IF EXISTS wcf1_menu_item;
+CREATE TABLE wcf1_menu_item (
+       itemID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+       menuID INT(10) NOT NULL,
+       parentItemID INT(10),
+       identifier VARCHAR(255) NOT NULL,
+       title VARCHAR(255) NOT NULL,
+       pageID INT(10),
+       externalURL VARCHAR(255) NOT NULL DEFAULT '',
+       showOrder INT(10) NOT NULL DEFAULT 0,
+       isDisabled TINYINT(1) NOT NULL DEFAULT 0,
+       originIsSystem TINYINT(1) NOT NULL DEFAULT 0
+);
+
 DROP TABLE IF EXISTS wcf1_message_embedded_object;
 CREATE TABLE wcf1_message_embedded_object (
        messageObjectTypeID INT(10) NOT NULL,
@@ -1576,6 +1599,12 @@ ALTER TABLE wcf1_language_item ADD FOREIGN KEY (languageID) REFERENCES wcf1_lang
 ALTER TABLE wcf1_language_item ADD FOREIGN KEY (languageCategoryID) REFERENCES wcf1_language_category (languageCategoryID) ON DELETE CASCADE;
 ALTER TABLE wcf1_language_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_menu ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE; 
+
+ALTER TABLE wcf1_menu_item ADD FOREIGN KEY (menuID) REFERENCES wcf1_menu (menuID) ON DELETE CASCADE;
+ALTER TABLE wcf1_menu_item ADD FOREIGN KEY (parentItemID) REFERENCES wcf1_menu_item (itemID) ON DELETE SET NULL;
+ALTER TABLE wcf1_menu_item ADD FOREIGN KEY (pageID) REFERENCES wcf1_page (pageID) ON DELETE CASCADE; 
+
 ALTER TABLE wcf1_modification_log ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_modification_log ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE SET NULL;