Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
917 changes: 137 additions & 780 deletions com.woltlab.wcf/acpMenu.xml

Large diffs are not rendered by default.

8 changes: 1 addition & 7 deletions com.woltlab.wcf/userGroupOption.xml
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,6 @@
<admindefaultvalue>1</admindefaultvalue>
<usersonly>1</usersonly>
</option>
<option name="admin.configuration.package.canUninstallPackage">
<categoryname>admin.configuration.package</categoryname>
<optiontype>boolean</optiontype>
<defaultvalue>0</defaultvalue>
<admindefaultvalue>1</admindefaultvalue>
<usersonly>1</usersonly>
</option>
<option name="admin.configuration.package.canEditServer">
<categoryname>admin.configuration.package</categoryname>
<optiontype>boolean</optiontype>
Expand Down Expand Up @@ -1081,5 +1074,6 @@ webp</defaultvalue>
</import>
<delete>
<option name="user.profile.avatar.maxSize"/>
<option name="admin.configuration.package.canUninstallPackage"/>
</delete>
</data>
2 changes: 1 addition & 1 deletion wcfsetup/install/files/acp/templates/packageList.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
'wcf.acp.package.uninstallation.title': '{jslang}wcf.acp.package.uninstallation.title{/jslang}',
});

{if $__wcf->session->getPermission('admin.configuration.package.canUninstallPackage')}
{if $__wcf->session->getPermission('admin.configuration.package.canInstallPackage')}
new WCF.ACP.Package.Uninstallation($('.jsPackageRow .jsUninstallButton'));
{/if}

Expand Down
21 changes: 7 additions & 14 deletions wcfsetup/install/files/acp/templates/pageMenu.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,15 @@
{if $_subMenuItems|empty}
<li{if $_menuItem->menuItem|in_array:$_activeMenuItems} class="active"{/if}><a href="{$_menuItem->getLink()}" class="acpPageSubMenuLink">{$_menuItem}</a></li>
{else}
{if $_menuItem->menuItem === 'wcf.acp.menu.link.option.category'}
{* handle special option categories *}
<li class="acpPageSubMenuLinkWrapper">
<a href="{$_menuItem->getLink()}" class="acpPageSubMenuLink{if $_menuItem->menuItem|in_array:$_activeMenuItems && $_activeMenuItems[0] === $_menuItem->menuItem} active{/if}">{$_menuItem}</a>

{foreach from=$_subMenuItems item=_subMenuItem}
<li{if $_subMenuItem->menuItem|in_array:$_activeMenuItems} class="active"{/if}><a href="{$_subMenuItem->getLink()}" class="acpPageSubMenuLink">{$_subMenuItem}</a></li>
<a href="{$_subMenuItem->getLink()}" class="acpPageSubMenuIcon jsTooltip{if $_subMenuItem->menuItem|in_array:$_activeMenuItems} active{/if}" title="{$_subMenuItem}">
{unsafe:$_subMenuItem->getIcon()->toHtml()}
</a>
{/foreach}
{else}
<li class="acpPageSubMenuLinkWrapper">
<a href="{$_menuItem->getLink()}" class="acpPageSubMenuLink{if $_menuItem->menuItem|in_array:$_activeMenuItems && $_activeMenuItems[0] === $_menuItem->menuItem} active{/if}">{$_menuItem}</a>

{foreach from=$_subMenuItems item=_subMenuItem}
<a href="{$_subMenuItem->getLink()}" class="acpPageSubMenuIcon jsTooltip{if $_subMenuItem->menuItem|in_array:$_activeMenuItems} active{/if}" title="{$_subMenuItem}">
{unsafe:$_subMenuItem->getIcon()->toHtml()}
</a>
{/foreach}
</li>
{/if}
</li>
{/if}
{/foreach}
</ol>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ final class PackageListPage extends AbstractGridViewPage
*/
public $neededPermissions = [
'admin.configuration.package.canUpdatePackage',
'admin.configuration.package.canUninstallPackage',
'admin.configuration.package.canInstallPackage',
];

#[\Override]
Expand Down
2 changes: 1 addition & 1 deletion wcfsetup/install/files/lib/acp/page/PackagePage.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class PackagePage extends AbstractPage
*/
public $neededPermissions = [
'admin.configuration.package.canUpdatePackage',
'admin.configuration.package.canUninstallPackage',
'admin.configuration.package.canInstallPackage',
];

/**
Expand Down
578 changes: 275 additions & 303 deletions wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,6 @@ public function getLink(): string
'application' => $this->application,
];

// links of top option category menu items need the id of the option
// category
if ($this->parentMenuItem == 'wcf.acp.menu.link.option.category') {
$linkParameters['id'] = $this->optionCategoryID;
}

return LinkHandler::getInstance()->getLink(
$this->controller,
$linkParameters,
Expand Down Expand Up @@ -119,14 +113,6 @@ protected function parseController()
*/
public function __toString(): string
{
// This is a placeholder category that represents the dynamic menu items
// generated by categories in the `options.xml`. It has no title and
// trying to access its name causes a false-positive for missing phrases
// with every ACP request.
if ($this->menuItem === 'wcf.acp.menu.link.option.category') {
return 'wcf.acp.menu.link.option.category';
}

return WCF::getLanguage()->get($this->menuItem);
}

Expand Down
2 changes: 1 addition & 1 deletion wcfsetup/install/files/lib/data/package/Package.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public function getAllRequiredPackages()
*/
public function canUninstall()
{
if (!WCF::getSession()->getPermission('admin.configuration.package.canUninstallPackage')) {
if (!WCF::getSession()->getPermission('admin.configuration.package.canInstallPackage')) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class PackageAction extends AbstractDatabaseObjectAction
/**
* @inheritDoc
*/
protected $permissionsDelete = ['admin.configuration.package.canUninstallPackage'];
protected $permissionsDelete = ['admin.configuration.package.canInstallPackage'];

/**
* @inheritDoc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,6 @@ public static function getOwnerPermissions()
'admin.configuration.canEditOption',
'admin.configuration.canManageApplication',
'admin.configuration.package.canInstallPackage',
'admin.configuration.package.canUninstallPackage',
'admin.configuration.package.canUpdatePackage',
'admin.general.canUseAcp',
'admin.general.canViewPageDuringOfflineMode',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@

namespace wcf\system\cache\builder;

use wcf\acp\form\OptionForm;
use wcf\data\acp\menu\item\ACPMenuItem;
use wcf\data\acp\menu\item\ACPMenuItemList;
use wcf\data\option\category\OptionCategory;
use wcf\data\option\category\OptionCategoryList;
use wcf\data\option\OptionList;

/**
* Caches the ACP menu items.
Expand All @@ -18,118 +13,20 @@
*/
class ACPMenuCacheBuilder extends AbstractCacheBuilder
{
/**
* list of option categories which directly contain options
* @var string[]
*/
protected $categoriesWithOptions = [];

/**
* list of option categories grouped by the name of their parent category
* @var array<string, OptionCategory[]>
*/
protected $categoryStructure = [];

/**
* @inheritDoc
*/
public function rebuild(array $parameters)
{
$data = [];

// get "real" menu items
$menuItemList = new ACPMenuItemList();
$menuItemList->sqlOrderBy = "acp_menu_item.showOrder";
$menuItemList->readObjects();
foreach ($menuItemList as $menuItem) {
$data[$menuItem->parentMenuItem][] = $menuItem;
}

// get menu items for top option categories
$data['wcf.acp.menu.link.option.category'] = [];
foreach ($this->getTopOptionCategories() as $optionCategory) {
$data['wcf.acp.menu.link.option.category'][] = new ACPMenuItem(null, [
'menuItem' => 'wcf.acp.option.category.' . $optionCategory->categoryName,
'parentMenuItem' => 'wcf.acp.menu.link.option.category',
'menuItemController' => OptionForm::class,
'permissions' => $optionCategory->permissions,
'optionCategoryID' => $optionCategory->categoryID,
'options' => $optionCategory->options,
]);
}

return $data;
}

/**
* Returns the list with top option categories which contain options.
*
* @return OptionCategory[]
*/
protected function getTopOptionCategories()
{
$optionCategoryList = new OptionCategoryList();
$optionCategoryList->readObjects();
$optionCategories = $optionCategoryList->getObjects();

// build category structure
$this->categoryStructure = [];
foreach ($optionCategories as $optionCategory) {
if (!isset($this->categoryStructure[$optionCategory->parentCategoryName])) {
$this->categoryStructure[$optionCategory->parentCategoryName] = [];
}

$this->categoryStructure[$optionCategory->parentCategoryName][] = $optionCategory;
}

$optionList = new OptionList();
$optionList->readObjects();

// collect names of categories which contain options
foreach ($optionList as $option) {
if (!isset($this->categoriesWithOptions[$option->categoryName])) {
$this->categoriesWithOptions[$option->categoryName] = $option->categoryName;
}
}

// collect top categories which contain options
$topCategories = [];
foreach ($this->categoryStructure[""] as $topCategory) {
if ($this->containsOptions($topCategory)) {
$topCategories[$topCategory->categoryID] = $topCategory;
}
}

return $topCategories;
}

/**
* Returns true if the given category or one of its child categories contains
* options.
*
* @param OptionCategory $topCategory
* @return bool
*/
protected function containsOptions(OptionCategory $topCategory)
{
// check if category directly contains options
if (isset($this->categoriesWithOptions[$topCategory->categoryName])) {
return true;
}

if (!isset($this->categoryStructure[$topCategory->categoryName])) {
// if category directly contains no options and has no child
// categories, it contains no options at all
return false;
}

// check child categories
foreach ($this->categoryStructure[$topCategory->categoryName] as $category) {
if ($this->containsOptions($category)) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

namespace wcf\system\cache\eager;

use wcf\data\option\category\OptionCategory;
use wcf\data\option\category\OptionCategoryList;
use wcf\data\option\OptionList;

/**
* Returns the list with top option categories which contain options.
*
* @author Marcel Werk
* @copyright 2001-2025 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.2
*
* @extends AbstractEagerCache<array<int, OptionCategory>>
*/
final class TopOptionCategoryCache extends AbstractEagerCache
{
/**
* list of option categories which directly contain options
* @var string[]
*/
private array $categoriesWithOptions = [];

/**
* list of option categories grouped by the name of their parent category
* @var array<string, OptionCategory[]>
*/
private array $categoryStructure = [];

/**
* @return array<int, OptionCategory>
*/
#[\Override]
protected function getCacheData(): array
{
$optionCategoryList = new OptionCategoryList();
$optionCategoryList->readObjects();
$optionCategories = $optionCategoryList->getObjects();

// build category structure
$this->categoryStructure = [];
foreach ($optionCategories as $optionCategory) {
if (!isset($this->categoryStructure[$optionCategory->parentCategoryName])) {
$this->categoryStructure[$optionCategory->parentCategoryName] = [];
}

$this->categoryStructure[$optionCategory->parentCategoryName][] = $optionCategory;
}

$optionList = new OptionList();
$optionList->readObjects();

// collect names of categories which contain options
foreach ($optionList as $option) {
if (!isset($this->categoriesWithOptions[$option->categoryName])) {
$this->categoriesWithOptions[$option->categoryName] = $option->categoryName;
}
}

// collect top categories which contain options
$topCategories = [];
foreach ($this->categoryStructure[""] as $topCategory) {
if ($this->containsOptions($topCategory)) {
$topCategories[$topCategory->categoryID] = $topCategory;
}
}

return $topCategories;
}

/**
* Returns true if the given category or one of its child categories contains
* options.
*/
private function containsOptions(OptionCategory $topCategory): bool
{
// check if category directly contains options
if (isset($this->categoriesWithOptions[$topCategory->categoryName])) {
return true;
}

if (!isset($this->categoryStructure[$topCategory->categoryName])) {
// if category directly contains no options and has no child
// categories, it contains no options at all
return false;
}

// check child categories
foreach ($this->categoryStructure[$topCategory->categoryName] as $category) {
if ($this->containsOptions($category)) {
return true;
}
}

return false;
}
}
Loading