Skip to content

Commit 5d52407

Browse files
CyperghostBurntimeXdtdesign
authored
Additional url parameters for internal menu items (#6409)
* Additional parameter for internal menu items * Rename property for additional parameters * Reduce size of the input field * Fix issue when appending the url parameters * Improve the handling of query parameters and fragments * Consider URL parameters for the active state of menu items --------- Co-authored-by: Marcel Werk <burntime@woltlab.com> Co-authored-by: Alexander Ebert <ebert@woltlab.com>
1 parent 464a7ab commit 5d52407

File tree

10 files changed

+143
-12
lines changed

10 files changed

+143
-12
lines changed

XSD/menuItem.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<xs:element name="page" type="woltlab_varchar" minOccurs="1" maxOccurs="1" />
4444
<xs:element name="parent" type="woltlab_varchar" minOccurs="0" maxOccurs="1" />
4545
<xs:element name="externalURL" type="woltlab_varchar" minOccurs="0" maxOccurs="1" />
46+
<xs:element name="urlParameters" type="woltlab_varchar" minOccurs="0" maxOccurs="1" />
4647
</xs:choice>
4748
</xs:extension>
4849
</xs:complexContent>

wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.2_step1.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use wcf\system\database\table\column\IntDatabaseTableColumn;
1212
use wcf\system\database\table\column\MediumtextDatabaseTableColumn;
1313
use wcf\system\database\table\column\VarcharDatabaseTableColumn;
14+
use wcf\system\database\table\column\NotNullVarchar255DatabaseTableColumn;
1415
use wcf\system\database\table\index\DatabaseTableForeignKey;
1516
use wcf\system\database\table\index\DatabaseTableIndex;
1617
use wcf\system\database\table\PartialDatabaseTable;
@@ -74,4 +75,9 @@
7475
->columns([
7576
MediumtextDatabaseTableColumn::create('exifData'),
7677
]),
78+
PartialDatabaseTable::create('wcf1_menu_item')
79+
->columns([
80+
NotNullVarchar255DatabaseTableColumn::create('urlParameters')
81+
->defaultValue(''),
82+
]),
7783
];

wcfsetup/install/files/lib/acp/form/MenuItemAddForm.class.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@ protected function createForm()
210210
->fieldId('pageID')
211211
->values(\array_keys($pageHandlers))
212212
),
213+
TextFormField::create('urlParameters')
214+
->label('wcf.acp.menu.item.urlParameters')
215+
->maximumLength(255)
216+
->addDependency(
217+
ValueFormFieldDependency::create('isInternalLinkDependency')
218+
->fieldId('isInternalLink')
219+
->values([1])
220+
)
221+
->addFieldClass('medium'),
213222
TextFormField::create('externalURL')
214223
->label('wcf.acp.menu.item.externalURL')
215224
->maximumLength(255)

wcfsetup/install/files/lib/data/menu/item/MenuItem.class.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use wcf\system\page\handler\ILookupPageHandler;
1212
use wcf\system\page\handler\IMenuPageHandler;
1313
use wcf\system\WCF;
14+
use wcf\util\Url;
1415

1516
/**
1617
* Represents a menu item.
@@ -32,6 +33,7 @@
3233
* @property-read int $isDisabled is `1` if the menu item is disabled and thus not shown in the menu, otherwise `0`
3334
* @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)
3435
* @property-read int $packageID id of the package the which delivers the menu item or `1` if it has been created in the ACP
36+
* @property-read string $urlParameters
3537
*/
3638
class MenuItem extends DatabaseObject implements ITitledObject
3739
{
@@ -93,17 +95,26 @@ public function getURL()
9395
if ($this->pageObjectID) {
9496
$handler = $this->getMenuPageHandler();
9597
if ($handler && $handler instanceof ILookupPageHandler) {
96-
return $handler->getLink($this->pageObjectID);
98+
return $this->appendUrlParameters($handler->getLink($this->pageObjectID));
9799
}
98100
}
99101

100102
if ($this->pageID) {
101-
return $this->getPage()->getLink();
103+
return $this->appendUrlParameters($this->getPage()->getLink());
102104
} else {
103105
return WCF::getLanguage()->get($this->externalURL);
104106
}
105107
}
106108

109+
private function appendUrlParameters(string $url): string
110+
{
111+
if (!$this->urlParameters) {
112+
return $url;
113+
}
114+
115+
return Url::withQueryString($url, $this->urlParameters);
116+
}
117+
107118
/**
108119
* Returns the page that is linked by this menu item.
109120
*

wcfsetup/install/files/lib/data/menu/item/MenuItemNodeTree.class.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use wcf\system\page\PageLocationManager;
66
use wcf\system\request\RequestHandler;
7+
use wcf\system\request\RouteHandler;
8+
use wcf\util\Url;
79

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

7779
if (!RequestHandler::getInstance()->isACPRequest()) {
80+
$requestParameters = Url::parseQueryString($_SERVER['QUERY_STRING']);
81+
7882
$possibleLocations = PageLocationManager::getInstance()->getLocations();
79-
$length = \count($possibleLocations);
80-
for ($i = 0; $i < $length; $i++) {
81-
foreach ($menuItemList as $menuItem) {
82-
if ($menuItem->pageID == $possibleLocations[$i]['pageID'] && $menuItem->pageObjectID == $possibleLocations[$i]['pageObjectID']) {
83-
if (!isset($activeMenuItems[$i])) {
84-
$activeMenuItems[$i] = [];
85-
}
83+
for ($i = 0, $length = \count($possibleLocations); $i < $length; $i++) {
84+
foreach ($menuItemList->getObjects() as $menuItem) {
85+
if ($menuItem->pageID !== $possibleLocations[$i]['pageID']) {
86+
continue;
87+
}
8688

87-
$activeMenuItems[$i][] = $menuItem->itemID;
89+
if ($menuItem->pageObjectID !== $possibleLocations[$i]['pageObjectID']) {
90+
continue;
8891
}
92+
93+
if ($menuItem->urlParameters !== '') {
94+
$expectedParameters = Url::parseQueryString($menuItem->urlParameters);
95+
if (\array_diff($expectedParameters, $requestParameters) !== []) {
96+
continue;
97+
}
98+
}
99+
100+
$activeMenuItems[$i] ??= [];
101+
$activeMenuItems[$i][] = $menuItem->itemID;
89102
}
90103
}
91104
}

wcfsetup/install/files/lib/system/package/plugin/MenuItemPackageInstallationPlugin.class.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,14 @@ protected function prepareImport(array $data)
155155
}
156156

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

159160
if ($pageID === null && empty($externalURL)) {
160161
throw new SystemException("The menu item '" . $data['attributes']['identifier'] . "' must either have an associated page or an external url set.");
161162
} elseif ($pageID !== null && !empty($externalURL)) {
162163
throw new SystemException("The menu item '" . $data['attributes']['identifier'] . "' can either have an associated page or an external url, but not both.");
164+
} elseif ($pageID === null && !empty($urlParameters)) {
165+
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.");
163166
}
164167

165168
return [
@@ -171,6 +174,7 @@ protected function prepareImport(array $data)
171174
'parentItemID' => $parentItemID,
172175
'showOrder' => $this->getItemOrder($menuID, $parentItemID),
173176
'title' => $this->getI18nValues($data['elements']['title']),
177+
'urlParameters' => $urlParameters,
174178
];
175179
}
176180

@@ -390,6 +394,14 @@ protected function addFormFields(IFormDocument $form)
390394
->values(['internal'])
391395
),
392396

397+
TextFormField::create('urlParameters')
398+
->label('wcf.acp.pip.menuItem.urlParameters')
399+
->maximumLength(255)
400+
->addDependency(
401+
ValueFormFieldDependency::create('linkType')
402+
->fieldId('linkType')
403+
->values(['internal'])
404+
),
393405
TextFormField::create('externalURL')
394406
->label('wcf.acp.pip.menuItem.externalURL')
395407
->description('wcf.acp.pip.menuItem.externalURL.description')
@@ -469,7 +481,7 @@ protected function fetchElementData(\DOMElement $element, $saveData)
469481
$data['title'][LanguageFactory::getInstance()->getLanguageByCode($title->getAttribute('language'))->languageID] = $title->nodeValue;
470482
}
471483

472-
foreach (['externalURL', 'menu', 'page', 'parent'] as $optionalElementName) {
484+
foreach (['externalURL', 'menu', 'page', 'parent', 'urlParameters'] as $optionalElementName) {
473485
$optionalElement = $element->getElementsByTagName($optionalElementName)->item(0);
474486
if ($optionalElement !== null) {
475487
$data[$optionalElementName] = $optionalElement->nodeValue;
@@ -594,6 +606,10 @@ protected function prepareXmlElement(\DOMDocument $document, IFormDocument $form
594606
[
595607
'page' => '',
596608
'externalURL' => '',
609+
'additionalInternalURL' => [
610+
'defaultValue' => '',
611+
'cdata' => true,
612+
],
597613
'showOrder' => null,
598614
],
599615
$form

wcfsetup/install/files/lib/util/Url.class.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace wcf\util;
44

5+
use GuzzleHttp\Psr7\Uri;
6+
use Psr\Http\Message\UriInterface;
7+
58
/**
69
* Generic wrapper around `parse_url()`.
710
*
@@ -216,4 +219,71 @@ public static function getHostnameMatcher(array $hostnames): callable
216219
return false;
217220
};
218221
}
222+
223+
/**
224+
* Appends a query string and an optional fragment to an existing URI.
225+
*
226+
* @since 6.3
227+
*/
228+
public static function withQueryString(string|Uri $uri, string $queryString): UriInterface
229+
{
230+
if (\is_string($uri)) {
231+
$uri = new Uri($uri);
232+
}
233+
234+
if ($queryString === '') {
235+
return $uri;
236+
}
237+
238+
$anchorPosition = \mb_strpos($queryString, '#');
239+
if ($anchorPosition !== false) {
240+
$anchor = \mb_substr($queryString, $anchorPosition + 1);
241+
$queryString = \mb_substr($queryString, 0, $anchorPosition);
242+
243+
$uri = $uri->withFragment($anchor);
244+
}
245+
246+
if ($queryString === '') {
247+
return $uri;
248+
}
249+
250+
$parts = self::parseQueryString($queryString);
251+
252+
return $uri->withQueryValues($uri, $parts);
253+
}
254+
255+
/**
256+
* Parses a query string into a one-dimensional key-value list.
257+
*
258+
* This is a replacement for PHP’s `parse_str()` that has two problems:
259+
* 1. Dots inside the key are replaced by underscores.
260+
* 2. Keys containing square brackets are converted into nested arrays.
261+
*
262+
* @return array<string, string>
263+
* @since 6.3
264+
*/
265+
public static function parseQueryString(string $queryString): array
266+
{
267+
$components = \explode('&', $queryString);
268+
$keyValueMap = [];
269+
for ($i = 0, $length = \count($components); $i < $length; $i++) {
270+
$component = $components[$i];
271+
if (\str_contains($component, '=')) {
272+
[$key, $value] = \explode('=', $component, 2);
273+
} else {
274+
$key = $component;
275+
$value = '';
276+
}
277+
278+
$key = \str_replace(
279+
['%5B', '%5D'],
280+
['[', ']'],
281+
$key
282+
);
283+
284+
$keyValueMap[$key] = $value;
285+
}
286+
287+
return $keyValueMap;
288+
}
219289
}

wcfsetup/install/lang/de.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,7 @@ Sie erreichen das Fehlerprotokoll unter: {link controller='ExceptionLogView' isE
12301230
<item name="wcf.acp.menu.item.parentItem"><![CDATA[Übergeordneter Menüpunkt]]></item>
12311231
<item name="wcf.acp.menu.link.other"><![CDATA[Sonstiges]]></item>
12321232
<item name="wcf.acp.menu.item.pageObjectID.error.invalid"><![CDATA[Die eingetragene ID ist ungültig.]]></item>
1233+
<item name="wcf.acp.menu.item.urlParameters"><![CDATA[Zusätzliche URL-Parameter]]></item>
12331234
<item name="wcf.acp.menu.link.contact"><![CDATA[Kontaktformular]]></item>
12341235
<item name="wcf.acp.menu.link.contact.options"><![CDATA[Eingabefelder]]></item>
12351236
<item name="wcf.acp.menu.link.contact.recipients"><![CDATA[Empfänger]]></item>
@@ -2547,6 +2548,7 @@ Die Datenbestände werden sorgfältig gepflegt, aber es ist nicht ausgeschlossen
25472548
<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>
25482549
<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>
25492550
<item name="wcf.acp.pip.objectType.integerCondition.propertyName.error.nonExistent"><![CDATA[Die angegebene Spalte existiert nicht in der Datenbanktabelle <kbd>{$tableName}</kbd>.]]></item>
2551+
<item name="wcf.acp.pip.menuItem.additionalInternalURL"><![CDATA[Zusätzliche interne URL-Parameter]]></item>
25502552
<item name="wcf.acp.pip.menuItem.externalURL"><![CDATA[Externe URL]]></item>
25512553
<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>
25522554
<item name="wcf.acp.pip.menuItem.identifier"><![CDATA[Bezeichner des Menüpunktes]]></item>

wcfsetup/install/lang/en.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,7 @@ You can access the error log at: {link controller='ExceptionLogView' isEmail=tru
12061206
<item name="wcf.acp.menu.item.parentItem"><![CDATA[Parent Menu Item]]></item>
12071207
<item name="wcf.acp.menu.link.other"><![CDATA[Other]]></item>
12081208
<item name="wcf.acp.menu.item.pageObjectID.error.invalid"><![CDATA[ID is invalid.]]></item>
1209+
<item name="wcf.acp.menu.item.urlParameters"><![CDATA[Additional URL Parameters]]></item>
12091210
<item name="wcf.acp.menu.link.contact"><![CDATA[Contact Form]]></item>
12101211
<item name="wcf.acp.menu.link.contact.options"><![CDATA[Option Fields]]></item>
12111212
<item name="wcf.acp.menu.link.contact.recipients"><![CDATA[Recipient]]></item>
@@ -2347,6 +2348,7 @@ If you have <strong>already bought the licenses for the listed apps</strong>, th
23472348
<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>
23482349
<item name="wcf.acp.pip.menuItem.externalURL"><![CDATA[External URL]]></item>
23492350
<item name="wcf.acp.pip.menuItem.externalURL.description"><![CDATA[When clicking on the menu item, the user is redirected to the entered website.]]></item>
2351+
<item name="wcf.acp.pip.menuItem.additionalInternalURL"><![CDATA[Additional internal URL parameters]]></item>
23502352
<item name="wcf.acp.pip.page.identifier"><![CDATA[Page Identifier]]></item>
23512353
<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>
23522354
<item name="wcf.acp.pip.page.identifier.error.notUnique"><![CDATA[This identifier is already used by another page.]]></item>

wcfsetup/setup/db/install.sql

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -835,7 +835,8 @@ CREATE TABLE wcf1_menu_item (
835835
showOrder INT(10) NOT NULL DEFAULT 0,
836836
isDisabled TINYINT(1) NOT NULL DEFAULT 0,
837837
originIsSystem TINYINT(1) NOT NULL DEFAULT 0,
838-
packageID INT(10) NOT NULL
838+
packageID INT(10) NOT NULL,
839+
urlParameters VARCHAR(255) NOT NULL DEFAULT ''
839840
);
840841

841842
DROP TABLE IF EXISTS wcf1_message_embedded_object;

0 commit comments

Comments
 (0)