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
1 change: 1 addition & 0 deletions XSD/menuItem.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<xs:element name="page" type="woltlab_varchar" minOccurs="1" maxOccurs="1" />
<xs:element name="parent" type="woltlab_varchar" minOccurs="0" maxOccurs="1" />
<xs:element name="externalURL" type="woltlab_varchar" minOccurs="0" maxOccurs="1" />
<xs:element name="urlParameters" type="woltlab_varchar" minOccurs="0" maxOccurs="1" />
</xs:choice>
</xs:extension>
</xs:complexContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use wcf\system\database\table\column\IntDatabaseTableColumn;
use wcf\system\database\table\column\MediumtextDatabaseTableColumn;
use wcf\system\database\table\column\VarcharDatabaseTableColumn;
use wcf\system\database\table\column\NotNullVarchar255DatabaseTableColumn;
use wcf\system\database\table\index\DatabaseTableForeignKey;
use wcf\system\database\table\index\DatabaseTableIndex;
use wcf\system\database\table\PartialDatabaseTable;
Expand Down Expand Up @@ -74,4 +75,9 @@
->columns([
MediumtextDatabaseTableColumn::create('exifData'),
]),
PartialDatabaseTable::create('wcf1_menu_item')
->columns([
NotNullVarchar255DatabaseTableColumn::create('urlParameters')
->defaultValue(''),
]),
];
9 changes: 9 additions & 0 deletions wcfsetup/install/files/lib/acp/form/MenuItemAddForm.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,15 @@ protected function createForm()
->fieldId('pageID')
->values(\array_keys($pageHandlers))
),
TextFormField::create('urlParameters')
->label('wcf.acp.menu.item.urlParameters')
->maximumLength(255)
->addDependency(
ValueFormFieldDependency::create('isInternalLinkDependency')
->fieldId('isInternalLink')
->values([1])
)
->addFieldClass('medium'),
TextFormField::create('externalURL')
->label('wcf.acp.menu.item.externalURL')
->maximumLength(255)
Expand Down
15 changes: 13 additions & 2 deletions wcfsetup/install/files/lib/data/menu/item/MenuItem.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use wcf\system\page\handler\ILookupPageHandler;
use wcf\system\page\handler\IMenuPageHandler;
use wcf\system\WCF;
use wcf\util\Url;

/**
* Represents a menu item.
Expand All @@ -32,6 +33,7 @@
* @property-read int $isDisabled is `1` if the menu item is disabled and thus not shown in the menu, otherwise `0`
* @property-read int $originIsSystem is `1` if the menu item has been delivered by a package, otherwise `0` (if the menu item has been created by an admin in the ACP)
* @property-read int $packageID id of the package the which delivers the menu item or `1` if it has been created in the ACP
* @property-read string $urlParameters
*/
class MenuItem extends DatabaseObject implements ITitledObject
{
Expand Down Expand Up @@ -93,17 +95,26 @@ public function getURL()
if ($this->pageObjectID) {
$handler = $this->getMenuPageHandler();
if ($handler && $handler instanceof ILookupPageHandler) {
return $handler->getLink($this->pageObjectID);
return $this->appendUrlParameters($handler->getLink($this->pageObjectID));
}
}

if ($this->pageID) {
return $this->getPage()->getLink();
return $this->appendUrlParameters($this->getPage()->getLink());
} else {
return WCF::getLanguage()->get($this->externalURL);
}
}

private function appendUrlParameters(string $url): string
{
if (!$this->urlParameters) {
return $url;
}

return Url::withQueryString($url, $this->urlParameters);
}

/**
* Returns the page that is linked by this menu item.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use wcf\system\page\PageLocationManager;
use wcf\system\request\RequestHandler;
use wcf\system\request\RouteHandler;
use wcf\util\Url;

/**
* Represents a menu item node tree.
Expand Down Expand Up @@ -75,17 +77,28 @@ public function __construct($menuID, ?MenuItemList $menuItemList = null, $checkV
$activeMenuItems = [];

if (!RequestHandler::getInstance()->isACPRequest()) {
$requestParameters = Url::parseQueryString($_SERVER['QUERY_STRING']);

$possibleLocations = PageLocationManager::getInstance()->getLocations();
$length = \count($possibleLocations);
for ($i = 0; $i < $length; $i++) {
foreach ($menuItemList as $menuItem) {
if ($menuItem->pageID == $possibleLocations[$i]['pageID'] && $menuItem->pageObjectID == $possibleLocations[$i]['pageObjectID']) {
if (!isset($activeMenuItems[$i])) {
$activeMenuItems[$i] = [];
}
for ($i = 0, $length = \count($possibleLocations); $i < $length; $i++) {
foreach ($menuItemList->getObjects() as $menuItem) {
if ($menuItem->pageID !== $possibleLocations[$i]['pageID']) {
continue;
}

$activeMenuItems[$i][] = $menuItem->itemID;
if ($menuItem->pageObjectID !== $possibleLocations[$i]['pageObjectID']) {
continue;
}

if ($menuItem->urlParameters !== '') {
$expectedParameters = Url::parseQueryString($menuItem->urlParameters);
if (\array_diff($expectedParameters, $requestParameters) !== []) {
continue;
}
}

$activeMenuItems[$i] ??= [];
$activeMenuItems[$i][] = $menuItem->itemID;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,14 @@ protected function prepareImport(array $data)
}

$externalURL = (!empty($data['elements']['externalURL'])) ? $data['elements']['externalURL'] : '';
$urlParameters = $data['elements']['urlParameters'] ?? '';

if ($pageID === null && empty($externalURL)) {
throw new SystemException("The menu item '" . $data['attributes']['identifier'] . "' must either have an associated page or an external url set.");
} elseif ($pageID !== null && !empty($externalURL)) {
throw new SystemException("The menu item '" . $data['attributes']['identifier'] . "' can either have an associated page or an external url, but not both.");
} elseif ($pageID === null && !empty($urlParameters)) {
throw new SystemException("The menu item '" . $data['attributes']['identifier'] . "' can not have an additional URL parameters set if it does not have an associated page.");
}

return [
Expand All @@ -171,6 +174,7 @@ protected function prepareImport(array $data)
'parentItemID' => $parentItemID,
'showOrder' => $this->getItemOrder($menuID, $parentItemID),
'title' => $this->getI18nValues($data['elements']['title']),
'urlParameters' => $urlParameters,
];
}

Expand Down Expand Up @@ -390,6 +394,14 @@ protected function addFormFields(IFormDocument $form)
->values(['internal'])
),

TextFormField::create('urlParameters')
->label('wcf.acp.pip.menuItem.urlParameters')
->maximumLength(255)
->addDependency(
ValueFormFieldDependency::create('linkType')
->fieldId('linkType')
->values(['internal'])
),
TextFormField::create('externalURL')
->label('wcf.acp.pip.menuItem.externalURL')
->description('wcf.acp.pip.menuItem.externalURL.description')
Expand Down Expand Up @@ -469,7 +481,7 @@ protected function fetchElementData(\DOMElement $element, $saveData)
$data['title'][LanguageFactory::getInstance()->getLanguageByCode($title->getAttribute('language'))->languageID] = $title->nodeValue;
}

foreach (['externalURL', 'menu', 'page', 'parent'] as $optionalElementName) {
foreach (['externalURL', 'menu', 'page', 'parent', 'urlParameters'] as $optionalElementName) {
$optionalElement = $element->getElementsByTagName($optionalElementName)->item(0);
if ($optionalElement !== null) {
$data[$optionalElementName] = $optionalElement->nodeValue;
Expand Down Expand Up @@ -594,6 +606,10 @@ protected function prepareXmlElement(\DOMDocument $document, IFormDocument $form
[
'page' => '',
'externalURL' => '',
'additionalInternalURL' => [
'defaultValue' => '',
'cdata' => true,
],
'showOrder' => null,
],
$form
Expand Down
70 changes: 70 additions & 0 deletions wcfsetup/install/files/lib/util/Url.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace wcf\util;

use GuzzleHttp\Psr7\Uri;
use Psr\Http\Message\UriInterface;

/**
* Generic wrapper around `parse_url()`.
*
Expand Down Expand Up @@ -216,4 +219,71 @@ public static function getHostnameMatcher(array $hostnames): callable
return false;
};
}

/**
* Appends a query string and an optional fragment to an existing URI.
*
* @since 6.3
*/
public static function withQueryString(string|Uri $uri, string $queryString): UriInterface
{
if (\is_string($uri)) {
$uri = new Uri($uri);
}

if ($queryString === '') {
return $uri;
}

$anchorPosition = \mb_strpos($queryString, '#');
if ($anchorPosition !== false) {
$anchor = \mb_substr($queryString, $anchorPosition + 1);
$queryString = \mb_substr($queryString, 0, $anchorPosition);

$uri = $uri->withFragment($anchor);
}

if ($queryString === '') {
return $uri;
}

$parts = self::parseQueryString($queryString);

return $uri->withQueryValues($uri, $parts);
}

/**
* Parses a query string into a one-dimensional key-value list.
*
* This is a replacement for PHP’s `parse_str()` that has two problems:
* 1. Dots inside the key are replaced by underscores.
* 2. Keys containing square brackets are converted into nested arrays.
*
* @return array<string, string>
* @since 6.3
*/
public static function parseQueryString(string $queryString): array
{
$components = \explode('&', $queryString);
$keyValueMap = [];
for ($i = 0, $length = \count($components); $i < $length; $i++) {
$component = $components[$i];
if (\str_contains($component, '=')) {
[$key, $value] = \explode('=', $component, 2);
} else {
$key = $component;
$value = '';
}

$key = \str_replace(
['%5B', '%5D'],
['[', ']'],
$key
);

$keyValueMap[$key] = $value;
}

return $keyValueMap;
}
}
2 changes: 2 additions & 0 deletions wcfsetup/install/lang/de.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,7 @@ Sie erreichen das Fehlerprotokoll unter: {link controller='ExceptionLogView' isE
<item name="wcf.acp.menu.item.parentItem"><![CDATA[Übergeordneter Menüpunkt]]></item>
<item name="wcf.acp.menu.link.other"><![CDATA[Sonstiges]]></item>
<item name="wcf.acp.menu.item.pageObjectID.error.invalid"><![CDATA[Die eingetragene ID ist ungültig.]]></item>
<item name="wcf.acp.menu.item.urlParameters"><![CDATA[Zusätzliche URL-Parameter]]></item>
<item name="wcf.acp.menu.link.contact"><![CDATA[Kontaktformular]]></item>
<item name="wcf.acp.menu.link.contact.options"><![CDATA[Eingabefelder]]></item>
<item name="wcf.acp.menu.link.contact.recipients"><![CDATA[Empfänger]]></item>
Expand Down Expand Up @@ -2547,6 +2548,7 @@ Die Datenbestände werden sorgfältig gepflegt, aber es ist nicht ausgeschlossen
<item name="wcf.acp.pip.objectType.integerCondition.propertyName.description"><![CDATA[Name der Objekteigenschaft und Spalte der <kbd>{$tableName}</kbd>-Datenbanktabelle, der für diese Bedingung verwendet wird.]]></item>
<item name="wcf.acp.pip.objectType.integerCondition.propertyName.error.noIntegerColumn"><![CDATA[Die angegebene Spalte der Datenbanktabelle <kbd>{$tableName}</kbd> ist keine Spalte vom Typ <kbd>INT</kbd>.]]></item>
<item name="wcf.acp.pip.objectType.integerCondition.propertyName.error.nonExistent"><![CDATA[Die angegebene Spalte existiert nicht in der Datenbanktabelle <kbd>{$tableName}</kbd>.]]></item>
<item name="wcf.acp.pip.menuItem.additionalInternalURL"><![CDATA[Zusätzliche interne URL-Parameter]]></item>
<item name="wcf.acp.pip.menuItem.externalURL"><![CDATA[Externe URL]]></item>
<item name="wcf.acp.pip.menuItem.externalURL.description"><![CDATA[Wenn der Benutzer auf den Menüpunkt klickt, wird er auf die angegebene Website weitergeleitet.]]></item>
<item name="wcf.acp.pip.menuItem.identifier"><![CDATA[Bezeichner des Menüpunktes]]></item>
Expand Down
2 changes: 2 additions & 0 deletions wcfsetup/install/lang/en.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,7 @@ You can access the error log at: {link controller='ExceptionLogView' isEmail=tru
<item name="wcf.acp.menu.item.parentItem"><![CDATA[Parent Menu Item]]></item>
<item name="wcf.acp.menu.link.other"><![CDATA[Other]]></item>
<item name="wcf.acp.menu.item.pageObjectID.error.invalid"><![CDATA[ID is invalid.]]></item>
<item name="wcf.acp.menu.item.urlParameters"><![CDATA[Additional URL Parameters]]></item>
<item name="wcf.acp.menu.link.contact"><![CDATA[Contact Form]]></item>
<item name="wcf.acp.menu.link.contact.options"><![CDATA[Option Fields]]></item>
<item name="wcf.acp.menu.link.contact.recipients"><![CDATA[Recipient]]></item>
Expand Down Expand Up @@ -2347,6 +2348,7 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
<item name="wcf.acp.pip.menuItem.showOrder.description"><![CDATA[The entered value determines in which order the menu items with the same parent are shown.]]></item>
<item name="wcf.acp.pip.menuItem.externalURL"><![CDATA[External URL]]></item>
<item name="wcf.acp.pip.menuItem.externalURL.description"><![CDATA[When clicking on the menu item, the user is redirected to the entered website.]]></item>
<item name="wcf.acp.pip.menuItem.additionalInternalURL"><![CDATA[Additional internal URL parameters]]></item>
<item name="wcf.acp.pip.page.identifier"><![CDATA[Page Identifier]]></item>
<item name="wcf.acp.pip.page.identifier.description"><![CDATA[The identifier consists of least four segments separated by dots. Each segment must not be empty and may only contain the following characters: <kbd>[A-z0-9-_]</kbd>. In general, the first part of the menu identifier is the package identifier and the second part is the unqualified controller class without the controller type suffixes <kbd>Form</kbd> and <kbd>Page</kbd>. Example: <kbd>com.foo.bar.package.Baz</kbd>]]></item>
<item name="wcf.acp.pip.page.identifier.error.notUnique"><![CDATA[This identifier is already used by another page.]]></item>
Expand Down
3 changes: 2 additions & 1 deletion wcfsetup/setup/db/install.sql
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,8 @@ CREATE TABLE wcf1_menu_item (
showOrder INT(10) NOT NULL DEFAULT 0,
isDisabled TINYINT(1) NOT NULL DEFAULT 0,
originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
packageID INT(10) NOT NULL
packageID INT(10) NOT NULL,
urlParameters VARCHAR(255) NOT NULL DEFAULT ''
);

DROP TABLE IF EXISTS wcf1_message_embedded_object;
Expand Down