text-decoration: underline;
}
}
+
+/* Search for Packages */
+.packageSearchName {
+ @include wcfFontHeadline;
+ @include wcfFontBold;
+}
+
+.packageSearchVersion {
+ color: $wcfContentDimmedText;
+ font-weight: 400;
+ padding-left: 5px;
+}
+
+.packageSearchPackage {
+ color: $wcfContentDimmedText;
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+ font-weight: 400;
+
+ @include wcfFontSmall;
+}
+
+#packageSearchResultContainer {
+ &:not([data-status="idle"]) .packageSearchStatusIdle {
+ display: none;
+ }
+
+ &:not([data-status="loading"]) .packageSearchStatusLoading {
+ display: none;
+ }
+
+ &:not([data-status="noResults"]) .packageSearchStatusNoResults {
+ display: none;
+ }
+
+ &:not([data-status="refreshDatabase"]) .packageSearchStatusRefreshDatabase {
+ display: none;
+ }
+
+ &:not([data-status="showResults"]) {
+ #packageSearchResultCounter,
+ #packageSearchResultList {
+ display: none;
+ }
+ }
+}
+
+.packageSearchStatus {
+ align-items: center;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ min-height: 120px;
+}
+
+.packageSearchStatusLabel {
+ margin-top: 10px;
+
+ @include wcfFontHeadline;
+}
<tr>
<th colspan="2" class="columnTitle">{lang}wcf.acp.package.name{/lang}</th>
<th class="columnText">{lang}wcf.acp.package.author{/lang}</a></th>
- <th class="columnText">{lang}wcf.acp.package.version{/lang}</th>
<th class="columnText">{lang}wcf.acp.package.license{/lang}</th>
<th class="columnDate">{lang}wcf.acp.package.packageDate{/lang}</a></th>
{event name='buttons'}
</td>
- <td class="columnTitle" title="{$package->packageDescription}">{$package->packageName}</td>
- <td class="columnText">{if $package->authorURL}<a href="{$package->authorURL}" class="externalURL">{$package->author}</a>{else}{$package->author}{/if}</td>
- <td class="columnText">
- {$package->getAccessibleVersion()->packageVersion}
- {*if $package->getAccessibleVersion()->packageUpdateVersionID != $package->getLatestVersion()->packageUpdateVersionID}
- <span class="icon icon16 icon-info-sign jsTooltip" title="{lang packageVersion=$package->getLatestVersion()->packageVersion}wcf.acp.package.newerVersionAvailable{/lang}"></span>
- {/if*}
+ <td class="columnTitle" title="{$package->packageDescription}">
+ <div class="packageSearchName">{$package->packageName} <span class="packageSearchVersion">{$package->getAccessibleVersion()->packageVersion}</span></div>
+ <span class="packageSearchPackage">{$package->package}</span>
</td>
+ <td class="columnText">{if $package->authorURL}<a href="{$package->authorURL}" class="externalURL">{$package->author}</a>{else}{$package->author}{/if}</td>
<td class="columnText">{if $package->getAccessibleVersion()->licenseURL}<a href="{$package->getAccessibleVersion()->licenseURL}" class="externalURL">{$package->getAccessibleVersion()->license}</a>{else}{$package->getAccessibleVersion()->license}{/if}</td>
<td class="columnDate">{@$package->getAccessibleVersion()->packageDate|time}</td>
{include file='header'}
<script data-relocate="true">
- $(function() {
- WCF.Language.addObject({
+ require(['Language', 'WoltLabSuite/Core/Acp/Ui/Package/Search'], function(Language, AcpUiPackageSearch) {
+ Language.addObject({
'wcf.acp.package.install.title': '{lang}wcf.acp.package.install.title{/lang}',
'wcf.acp.package.update.unauthorized': '{lang}wcf.acp.package.update.unauthorized{/lang}'
});
- new WCF.ACP.Package.Search();
+ new AcpUiPackageSearch();
});
</script>
{include file='formError'}
<div class="section">
- <section class="section">
+ <section class="section" id="packageSearch">
<h2 class="sectionTitle">{lang}wcf.acp.package.search{/lang}</h2>
<dl>
- <dt><label for="packageName">{lang}wcf.acp.package.search.packageName{/lang}</label></dt>
- <dd><input type="text" id="packageName" value="" class="long" data-search-name="packageName"></dd>
- </dl>
- <dl>
- <dt><label for="packageDescription">{lang}wcf.acp.package.search.packageDescription{/lang}</label></dt>
- <dd><input type="text" id="packageDescription" value="" class="long" data-search-name="packageDescription"></dd>
- </dl>
- <dl>
- <dt><label for="package">{lang}wcf.acp.package.search.package{/lang}</label></dt>
+ <dt><label for="packageSearchInput">{lang}wcf.acp.package.search.input{/lang}</label></dt>
<dd>
- <input type="text" id="package" value="" class="medium" data-search-name="package">
- <small>{lang}wcf.acp.package.search.package.description{/lang}</small>
+ <input type="text" id="packageSearchInput" value="" class="long" autocomplete="off">
+ <small>{lang}wcf.acp.package.search.input.description{/lang}</small>
</dd>
</dl>
-
- <div class="formSubmit">
- <button class="jsButtonPackageSearch">{lang}wcf.global.button.submit{/lang}</button>
- </div>
</section>
- <section class="section tabularBox" id="packageSearchResultContainer" style="display: none;">
- <h2 class="sectionTitle">{lang}wcf.acp.package.search.resultList{/lang} <span class="badge">0</span></h2>
+ <section class="section tabularBox" id="packageSearchResultContainer" data-status="idle">
+ <h2 class="sectionTitle">{lang}wcf.acp.package.search.resultList{/lang} <span class="badge" id="packageSearchResultCounter">0</span></h2>
+
+ <div class="packageSearchStatus packageSearchStatusIdle">
+ <span class="packageSearchStatusLabel">{lang}wcf.acp.package.search.status.idle{/lang}</span>
+ </div>
+
+ <div class="packageSearchStatus packageSearchStatusRefreshDatabase">
+ <span class="icon icon64 fa-spinner"></span>
+ <span class="packageSearchStatusLabel">{lang}wcf.acp.package.search.status.refreshDatabase{/lang}</span>
+ </div>
+
+ <div class="packageSearchStatus packageSearchStatusLoading">
+ <span class="icon icon64 fa-spinner"></span>
+ <span class="packageSearchStatusLabel">{lang}wcf.acp.package.search.status.loading{/lang}</span>
+ </div>
+
+ <div class="packageSearchStatus packageSearchStatusNoResults">
+ <span class="packageSearchStatusLabel">{lang}wcf.acp.package.search.status.noResults{/lang}</span>
+ </div>
<div id="packageSearchResultList"></div>
</section>
--- /dev/null
+/**
+ *
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @module WoltLabSuite/Core/Acp/Ui/Package/Search
+ */
+define(['Ajax'], function(Ajax) {
+ 'use strict';
+
+ function AcpUiPackageSearch() { this.init(); }
+ AcpUiPackageSearch.prototype = {
+ init: function () {
+ this._input = elById('packageSearchInput');
+ this._isBusy = false;
+ this._isFirstRequest = true;
+ this._lastValue = '';
+ this._options = {
+ delay: 300,
+ minLength: 3
+ };
+ this._request = null;
+ this._resultList = elById('packageSearchResultList');
+ this._resultListContainer = elById('packageSearchResultContainer');
+ this._resultCounter = elById('packageSearchResultCounter');
+ this._timerDelay = null;
+
+ this._input.addEventListener('keyup', this._keyup.bind(this));
+ },
+
+ _keyup: function () {
+ var value = this._input.value.trim();
+ if (this._lastValue === value) {
+ return;
+ }
+
+ this._lastValue = value;
+
+ if (value.length < this._options.minLength) {
+ this._setStatus('idle');
+ return;
+ }
+
+ if (this._isFirstRequest) {
+ if (!this._isBusy) {
+ this._isBusy = true;
+
+ this._setStatus('refreshDatabase');
+
+ Ajax.api(this, {
+ actionName: 'refreshDatabase'
+ });
+ }
+
+ return;
+ }
+
+ if (this._timerDelay !== null) {
+ window.clearTimeout(this._timerDelay);
+ }
+
+ this._timerDelay = window.setTimeout((function() {
+ this._setStatus('loading');
+ this._search(value);
+ }).bind(this), this._options.delay);
+ },
+
+ _search: function(value) {
+ if (this._request) {
+ this._request.abortPrevious();
+ }
+
+ this._request = Ajax.api(this, {
+ parameters: {
+ searchString: value
+ }
+ });
+ },
+
+ _setStatus: function (status) {
+ elData(this._resultListContainer, 'status', status);
+ },
+
+ _ajaxSuccess: function(data) {
+ switch (data.actionName) {
+ case 'refreshDatabase':
+ this._isFirstRequest = false;
+
+ this._lastValue = '';
+ this._keyup();
+ break;
+
+ case 'search':
+ if (data.returnValues.count > 0) {
+ this._resultList.innerHTML = data.returnValues.template;
+ this._resultCounter.textContent = data.returnValues.count;
+
+ this._setStatus('showResults');
+ }
+ else {
+ this._setStatus('noResults');
+ }
+ break;
+ }
+ },
+
+ _ajaxSetup: function() {
+ return {
+ data: {
+ actionName: 'search',
+ className: 'wcf\\data\\package\\update\\PackageUpdateAction'
+ },
+ silent: true
+ };
+ }
+ };
+
+ return AcpUiPackageSearch;
+});
public function validateSearch() {
WCF::getSession()->checkPermissions(['admin.configuration.package.canInstallPackage']);
- $this->readString('package', true);
- $this->readString('packageDescription', true);
- $this->readString('packageName', true);
- $this->readBoolean('searchDescription', true);
-
- if (empty($this->parameters['package']) && empty($this->parameters['packageDescription']) && empty($this->parameters['packageName'])) {
- throw new PermissionDeniedException();
- }
+ $this->readString('searchString');
}
/**
* @return array
*/
public function search() {
- PackageUpdateDispatcher::getInstance()->refreshPackageDatabase();
$availableUpdateServers = PackageUpdateServer::getActiveUpdateServers();
// there are no available package update servers
$conditions = new PreparedStatementConditionBuilder();
$conditions->add("package_update.packageUpdateServerID IN (?)", [array_keys($availableUpdateServers)]);
- if (!empty($this->parameters['package'])) {
- $conditions->add("package_update.package LIKE ?", ['%' . $this->parameters['package'] . '%']);
- }
- if (!empty($this->parameters['packageDescription'])) {
- $conditions->add("package_update.packageDescription LIKE ?", ['%' . $this->parameters['packageDescription'] . '%']);
- }
- if (!empty($this->parameters['packageName'])) {
- $conditions->add("package_update.packageName LIKE ?", ['%' . $this->parameters['packageName'] . '%']);
- }
+ $searchString = '%' . $this->parameters['searchString'] . '%';
+ $conditions->add("(package_update.package LIKE ? OR package_update.packageDescription LIKE ? OR package_update.packageName LIKE ?)", [$searchString, $searchString, $searchString]);
$conditions->add("package.packageID IS NULL");
// find matching packages
while ($row = $statement->fetchArray()) {
$packageVersions[$row['packageUpdateVersionID']] = [
'packageUpdateID' => $row['packageUpdateID'],
- 'packageVersion' => $row['packageVersion']
+ 'packageVersion' => $row['packageVersion'],
];
}
}
}
- $search = SearchEditor::create([
- 'userID' => WCF::getUser()->userID,
- 'searchData' => serialize($packageUpdates),
- 'searchTime' => TIME_NOW,
- 'searchType' => 'acpPackageSearch'
- ]);
-
- // forward call to build the actual result list
- $updateAction = new PackageUpdateAction([], 'getResultList', [
- 'pageNo' => 1,
- 'search' => $search
- ]);
-
- $returnValues = $updateAction->executeAction();
- return $returnValues['returnValues'];
+ return $this->getResultList($availableUpdateServers, $packageUpdates);
}
/**
if (!isset($packageVersions[$package][$packageUpdateID])) {
$packageVersions[$package][$packageUpdateID] = [
'accessible' => [],
- 'existing' => []
+ 'existing' => [],
];
}
$existing = array_pop($existing);
$packageUpdates[$versions[$accessible]] = [
'accessible' => $accessible,
- 'existing' => $existing
+ 'existing' => $existing,
];
}
return $packageUpdates;
}
- /**
- * Validates parameters to return a result list for a previous search.
- */
- public function validateGetResultList() {
- WCF::getSession()->checkPermissions(['admin.configuration.package.canInstallPackage']);
-
- $this->readInteger('pageNo');
- $this->readInteger('searchID');
-
- $this->search = new Search($this->parameters['searchID']);
- if (!$this->search->searchID || $this->search->userID != WCF::getUser()->userID) {
- throw new UserInputException('searchID');
- }
- }
-
/**
* Returns a result list for a previous search.
*
+ * @param PackageUpdateServer[] $updateServers
+ * @param array $updateData
* @return array
*/
- public function getResultList() {
- if ($this->search === null && isset($this->parameters['search']) && $this->parameters['search'] instanceof Search) {
- $this->search = $this->parameters['search'];
- }
- $updateData = unserialize($this->search->searchData);
-
+ protected function getResultList(array $updateServers, array $updateData) {
// get package updates
$conditions = new PreparedStatementConditionBuilder();
$conditions->add("packageUpdateID IN (?)", [array_keys($updateData)]);
$sql = "SELECT *
FROM wcf".WCF_N."_package_update
".$conditions;
- $statement = WCF::getDB()->prepareStatement($sql, 20, ($this->parameters['pageNo'] - 1) * 20);
+ $statement = WCF::getDB()->prepareStatement($sql);
$statement->execute($conditions->getParameters());
$packageUpdates = $packageVersionIDs = [];
while ($packageUpdate = $statement->fetchObject(PackageUpdate::class)) {
$updateVersions = $statement->fetchObjects(PackageUpdateVersion::class, 'packageUpdateVersionID');
// assign versions
+ /**
+ * @var int $packageUpdateID
+ * @var ViewablePackageUpdate $packageUpdate
+ */
foreach ($packageUpdates as $packageUpdateID => $packageUpdate) {
$versionIDs = $updateData[$packageUpdate->packageUpdateID];
$packageUpdate->setAccessibleVersion($updateVersions[$versionIDs['accessible']]);
$packageUpdate->setLatestVersion($updateVersions[$versionIDs['existing']]);
+ $packageUpdate->setUpdateServer($updateServers[$packageUpdate->packageUpdateServerID]);
}
+ uasort($packageUpdates, function(ViewablePackageUpdate $a, ViewablePackageUpdate $b) {
+ $aIsTrusted = $a->getUpdateServer()->isTrustedServer() || $a->getUpdateServer()->isWoltLabStoreServer();
+ $bIsTrusted = $b->getUpdateServer()->isTrustedServer() || $b->getUpdateServer()->isWoltLabStoreServer();
+
+ if ($aIsTrusted === $bIsTrusted) {
+ return strnatcasecmp($a->getName(), $b->getName());
+ }
+
+ return $aIsTrusted ? -1 : 1;
+ });
+
WCF::getTPL()->assign([
- 'packageUpdates' => $packageUpdates
+ 'packageUpdates' => $packageUpdates,
]);
- $count = count($updateData);
return [
- 'count' => $count,
- 'pageCount' => ceil($count / 20),
- 'searchID' => $this->search->searchID,
- 'template' => WCF::getTPL()->fetch('packageSearchResultList')
+ 'count' => count($updateData),
+ 'template' => WCF::getTPL()->fetch('packageSearchResultList'),
];
}
}
return [
- 'url' => $url
+ 'url' => $url,
];
}
return $this->createQueue('install');
}
+ public function validateRefreshDatabase() {
+ WCF::getSession()->checkPermissions(['admin.configuration.package.canInstallPackage']);
+
+ $this->readBoolean('ignoreCache', true);
+
+ if (ENABLE_BENCHMARK) {
+ throw new NamedUserException(WCF::getLanguage()->getDynamicVariable('wcf.acp.package.searchForUpdates.benchmark'));
+ }
+ }
+
+ public function refreshDatabase() {
+ PackageUpdateDispatcher::getInstance()->refreshPackageDatabase();
+ }
+
/**
* Creates a new package installation queue.
*
}
catch (PackageUpdateUnauthorizedException $e) {
return [
- 'template' => $e->getRenderedTemplate()
+ 'template' => $e->getRenderedTemplate(),
];
}
if (!empty($excludedPackages)) {
return [
'excludedPackages' => true,
- 'template' => WCF::getTPL()->fetch('packageUpdateExcludedPackages', 'wcf', ['excludedPackages' => $excludedPackages])
+ 'template' => WCF::getTPL()->fetch('packageUpdateExcludedPackages', 'wcf', ['excludedPackages' => $excludedPackages]),
];
}
}
'packageName' => $package['packageName'],
'packageID' => $package['packageID'] ?: null,
'archive' => $package['archive'],
- 'action' => $package['action']
+ 'action' => $package['action'],
]);
$parentQueueID = $queue->queueID;
}
return [
- 'queueID' => $queueID
+ 'queueID' => $queueID,
];
}
}
<?php
namespace wcf\data\package\update;
+use wcf\data\package\update\server\PackageUpdateServer;
use wcf\data\package\update\version\PackageUpdateVersion;
use wcf\data\DatabaseObjectDecorator;
* latest accessible package update version object
* @var PackageUpdateVersion
*/
- protected $accessibleVersion = null;
+ protected $accessibleVersion;
/**
* latest package update version object
* @var PackageUpdateVersion
*/
- protected $latestVersion = null;
+ protected $latestVersion;
+
+ /**
+ * @var PackageUpdateServer
+ */
+ protected $updateServer;
/**
* Sets latest accessible package update version object.
public function getLatestVersion() {
return $this->latestVersion;
}
+
+ /**
+ * @param PackageUpdateServer $updateServer
+ * @since 5.2
+ */
+ public function setUpdateServer(PackageUpdateServer $updateServer) {
+ $this->updateServer = $updateServer;
+ }
+
+ /**
+ * @return PackageUpdateServer
+ * @since 5.2
+ */
+ public function getUpdateServer() {
+ return $this->updateServer;
+ }
}
{/foreach}
</ul>
{if LANGUAGE_USE_INFORMAL_VARIANT}Solltest du{else}Sollten Sie{/if} die <strong>Lizenz für die jeweilige App bereits erworben</strong> haben, so reicht es aus, alle kommenden Updates zu installieren. <strong>Die Testversion wird in diesem Fall automatisch in die Vollversion umgewandelt.</strong>]]></item>
+ <item name="wcf.acp.package.search.input"><![CDATA[Suchbegriff eingeben]]></item>
+ <item name="wcf.acp.package.search.input.description"><![CDATA[Es wird nach Teilübereinstimmungen des Paketnamens, dessen Beschreibung sowie dem internen Bezeichner gesucht.]]></item>
+ <item name="wcf.acp.package.search.status.idle"><![CDATA[Geben Sie mehr als drei Buchstaben ein, um die Suche zu starten.]]></item>
+ <item name="wcf.acp.package.search.status.loading"><![CDATA[Suche läuft…]]></item>
+ <item name="wcf.acp.package.search.status.noResults"><![CDATA[Die Suche ergab keine Treffer.]]></item>
+ <item name="wcf.acp.package.search.status.refreshDatabase"><![CDATA[Die Paketlisten werden aktualisiert…]]></item>
</category>
<category name="wcf.acp.page">
<item name="wcf.acp.page.add"><![CDATA[Seite hinzufügen]]></item>
{/foreach}
</ul>
If you have <strong>already bought the licenses for the listed apps</strong>, then it is enough to install all upcoming updates. <strong>The evaluation copy will automatically be upgraded to the full version.</strong>]]></item>
+ <item name="wcf.acp.package.search.input"><![CDATA[Search term]]></item>
+ <item name="wcf.acp.package.search.input.description"><![CDATA[Partial matches are performed against the package name, its description and its internal identifier.]]></item>
+ <item name="wcf.acp.package.search.status.idle"><![CDATA[Enter three or more characters to start searching.]]></item>
+ <item name="wcf.acp.package.search.status.loading"><![CDATA[Searching…]]></item>
+ <item name="wcf.acp.package.search.status.noResults"><![CDATA[There were no matches.]]></item>
+ <item name="wcf.acp.package.search.status.refreshDatabase"><![CDATA[Retrieving the package lists…]]></item>
</category>
<category name="wcf.acp.paidSubscription">
<item name="wcf.acp.paidSubscription.list"><![CDATA[Paid Subscriptions]]></item>