Added integration of purchased Plugin-Store products
authorAlexander Ebert <ebert@woltlab.com>
Sat, 26 Apr 2014 21:14:31 +0000 (23:14 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Sat, 26 Apr 2014 21:14:31 +0000 (23:14 +0200)
wcfsetup/install/files/acp/js/WCF.ACP.js
wcfsetup/install/files/acp/templates/packageList.tpl
wcfsetup/install/files/acp/templates/pluginStoreAuthorization.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/page/PackageListPage.class.php
wcfsetup/install/files/lib/acp/page/PluginStorePurchasedItemsPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/package/PackageAction.class.php
wcfsetup/install/files/lib/util/JSON.class.php
wcfsetup/install/lang/de.xml

index b104a9b4ed246d514fe6930dee94c3641345bf9f..786ad5df1f77861b367c87d5441a9f10ad1dc885 100644 (file)
@@ -1447,6 +1447,88 @@ WCF.ACP.Package.Update.Search = Class.extend({
        }
 });
 
+/**
+ * Namespace for classes related to the WoltLab Plugin-Store.
+ */
+WCF.ACP.PluginStore = { };
+
+/**
+ * Namespace for classes handling items purchased in the WoltLab Plugin-Store.
+ */
+WCF.ACP.PluginStore.PurchasedItems = { };
+
+/**
+ * Searches for purchased items available for install but not yet installed.
+ */
+WCF.ACP.PluginStore.PurchasedItems.Search = Class.extend({
+       _dialog: null,
+       _proxy: null,
+       _wcfMajorReleases: [ ],
+       
+       init: function(wcfMajorReleases) {
+               this._dialog = null;
+               this._proxy = new WCF.Action.Proxy({
+                       success: $.proxy(this._success, this)
+               });
+               this._wcfMajorReleases = wcfMajorReleases;
+               if (!this._wcfMajorReleases.length) {
+                       console.debug("[WCF.ACP.PluginStore.PurchasedItems.Search] No suitable WCF major releases found, aborting.");
+                       return;
+               }
+               
+               var $button = $('<li><a class="button"><span class="icon icon16 fa-shopping-cart" /> <span>' + WCF.Language.get('wcf.acp.pluginstore.purchasedItems.button.search') + '</span></a></li>');
+               $button.prependTo($('.contentNavigation:eq(0) > nav > ul')).click($.proxy(this._click, this));
+       },
+       
+       _click: function() {
+               this._proxy.setOption('data', {
+                       actionName: 'searchForPurchasedItems',
+                       className: 'wcf\\data\\package\\PackageAction',
+                       parameters: {
+                               wcfMajorReleases: this._wcfMajorReleases
+                       }
+               });
+               this._proxy.sendRequest();
+       },
+       
+       _success: function(data, textStatus, jqXHR) {
+               if (data.returnValues.template) {
+                       if (this._dialog === null) {
+                               this._dialog = $('<div />').hide().appendTo(document.body);
+                               this._dialog.html(data.returnValues.template).wcfDialog({
+                                       title: WCF.Language.get('wcf.acp.pluginstore.authorization')
+                               });
+                       }
+                       else {
+                               this._dialog.html(data.returnValues.template);
+                               this._dialog.wcfDialog('open');
+                       }
+                       
+                       this._dialog.find('button').click($.proxy(this._submit, this));
+               }
+               else if (data.returnValues.noResults) {
+                       this._dialog.wcfDialog('option', 'title', 'Gekaufte Produkte (Plugin-Store)');
+                       this._dialog.html(data.returnValues.noResults);
+                       this._dialog.wcfDialog('open');
+               }
+       },
+       
+       _submit: function() {
+               this._dialog.wcfDialog('close');
+               
+               this._proxy.setOption('data', {
+                       actionName: 'searchForPurchasedItems',
+                       className: 'wcf\\data\\package\\PackageAction',
+                       parameters: {
+                               password: $('#pluginStorePassword').val(),
+                               username: $('#pluginStoreUsername').val(),
+                               wcfMajorReleases: this._wcfMajorReleases
+                       }
+               });
+               this._proxy.sendRequest();
+       }
+});
+
 /**
  * Handles option selection.
  */
index e60cc045918693043b44a73113919f55ac4a7d0e..5d1b26df23ce75cf04ffe78968005747d09250b8 100644 (file)
@@ -6,7 +6,11 @@
                WCF.Language.addObject({
                        'wcf.acp.package.searchForUpdates': '{lang}wcf.acp.package.searchForUpdates{/lang}',
                        'wcf.acp.package.searchForUpdates.noResults': '{lang}wcf.acp.package.searchForUpdates.noResults{/lang}',
-                       'wcf.acp.package.uninstallation.title': '{lang}wcf.acp.package.uninstallation.title{/lang}'
+                       'wcf.acp.package.uninstallation.title': '{lang}wcf.acp.package.uninstallation.title{/lang}',
+                       'wcf.acp.pluginstore.authorization': '{lang}wcf.acp.pluginstore.authorization{/lang}',
+                       'wcf.acp.pluginstore.purchasedItems': '{lang}wcf.acp.pluginstore.purchasedItems{/lang}',
+                       'wcf.acp.pluginstore.purchasedItems.button.search': '{lang}wcf.acp.pluginstore.purchasedItems.button.search{/lang}',
+                       'wcf.acp.pluginstore.purchasedItems.noResults': '{lang}wcf.acp.pluginstore.purchasedItems.noResults{/lang}'
                });
                
                {if $__wcf->session->getPermission('admin.system.package.canUninstallPackage')}
@@ -22,6 +26,8 @@
                {if $__wcf->session->getPermission('admin.system.package.canUpdatePackage')}
                        new WCF.ACP.Package.Update.Search();
                {/if}
+               
+               new WCF.ACP.PluginStore.PurchasedItems.Search([ {implode from=$wcfMajorReleases item=wcfMajorRelease}'{$wcfMajorRelease}'{/implode} ]);
        });
        //]]>
 </script>
diff --git a/wcfsetup/install/files/acp/templates/pluginStoreAuthorization.tpl b/wcfsetup/install/files/acp/templates/pluginStoreAuthorization.tpl
new file mode 100644 (file)
index 0000000..7c95174
--- /dev/null
@@ -0,0 +1,22 @@
+{if $rejected}
+       <p class="error">{lang}wcf.acp.pluginstore.authorization.credentails.rejected{/lang}</p>
+{/if}
+
+<fieldset{if $rejected} class="marginTop"{/if}>
+       <legend>{lang}wcf.acp.pluginstore.authorization.credentials{/lang}</legend>
+       <small>{lang}wcf.acp.pluginstore.authorization.credentails.description{/lang}</small>
+       
+       <dl>
+               <dt><label for="pluginStoreUsername">{lang}wcf.acp.pluginstore.authorization.username{/lang}</label></dt>
+               <dd><input type="text" id="pluginStoreUsername" value="" class="long" /></dd>
+       </dl>
+       
+       <dl>
+               <dt><label for="pluginStorePassword">{lang}wcf.acp.pluginstore.authorization.password{/lang}</label></dt>
+               <dd><input type="password" id="pluginStorePassword" value="" class="long" /></dd>
+       </dl>
+</fieldset>
+
+<div class="formSubmit">
+       <button>{lang}wcf.global.button.submit{/lang}</button>
+</div>
\ No newline at end of file
index 14c9232aeb77dbcf3bbbeef1f86a708e06686df1..a0bc1fbdb02a8b9a0c48a69929b6a38239228356 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\acp\page;
 use wcf\page\SortablePage;
 use wcf\system\WCF;
+use wcf\data\package\update\server\PackageUpdateServer;
 
 /**
  * Shows a list of all installed packages.
@@ -55,6 +56,12 @@ class PackageListPage extends SortablePage {
         */
        public $objectListClassName = 'wcf\data\package\PackageList';
        
+       /**
+        * list of WCF major releases covered by existing store update servers
+        * @var array<string>
+        */
+       public $wcfMajorReleases = array();
+       
        /**
         * @see \wcf\page\IPage::readParameters()
         */
@@ -64,6 +71,20 @@ class PackageListPage extends SortablePage {
                if (isset($_GET['packageID'])) $this->packageID = intval($_GET['packageID']);
        }
        
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               $updateServers = PackageUpdateServer::getActiveUpdateServers();
+               foreach ($updateServers as $updateServer) {
+                       if (preg_match('~^https?://store.woltlab.com/(maelstrom|typhoon)/$~', $updateServer->serverURL, $matches)) {
+                               $this->wcfMajorReleases[] = $matches[1];
+                       }
+               }
+       }
+       
        /**
         * @see \wcf\page\IPage::assignVariables()
         */
@@ -71,7 +92,8 @@ class PackageListPage extends SortablePage {
                parent::assignVariables();
                
                WCF::getTPL()->assign(array(
-                       'packageID' => $this->packageID
+                       'packageID' => $this->packageID,
+                       'wcfMajorReleases' => $this->wcfMajorReleases
                ));
        }
        
diff --git a/wcfsetup/install/files/lib/acp/page/PluginStorePurchasedItemsPage.class.php b/wcfsetup/install/files/lib/acp/page/PluginStorePurchasedItemsPage.class.php
new file mode 100644 (file)
index 0000000..d5e1e04
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+namespace wcf\acp\page;
+use wcf\page\AbstractPage;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\WCF;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+
+/**
+ * Shows a list of purchased plugin store items.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 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 PluginStorePurchasedItemsPage extends AbstractPage {
+       /**
+        * @see \wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.package';
+       
+       /**
+        * @see \wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.system.package.canUpdatePackage', 'admin.system.package.canUninstallPackage');
+       
+       /**
+        * list of purchased products grouped by WCF major release.
+        * @var array<array>
+        */
+       public $products = array();
+       
+       /**
+        * @see \wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               $this->products = WCF::getSession()->getVar('__pluginStoreProducts');
+               if (empty($this->products)) {
+                       throw new IllegalLinkException();
+               }
+               
+               die("<pre>".print_r($this->products, true));
+       }
+       
+       /**
+        * @see \wcf\page\IPage::readData()
+        */
+       public function readData() {
+               parent::readData();
+               
+               $availableProducts = array();
+               foreach ($this->products as $products) {
+                       $availableProducts = array_merge($availableProducts, array_keys($products));
+               }
+               
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("package IN (?)")
+       }
+       
+       /**
+        * @see \wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+               
+               WCF::getTPL()->assign(array(
+                       'products' => $this->products
+               ));
+       }
+}
index e03a5be583253529038cf71f07cfa4e6e478f286..3e7f30c586d81d9a3f4834b5068c60e862a24c3f 100644 (file)
@@ -1,6 +1,13 @@
 <?php
 namespace wcf\data\package;
 use wcf\data\AbstractDatabaseObjectAction;
+use wcf\system\exception\HTTPUnauthorizedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\HTTPRequest;
+use wcf\util\JSON;
+use wcf\system\exception\SystemException;
+use wcf\system\request\LinkHandler;
 
 /**
  * Executes package-related actions.
@@ -32,4 +39,87 @@ class PackageAction extends AbstractDatabaseObjectAction {
         * @see \wcf\data\AbstractDatabaseObjectAction::$permissionsUpdate
         */
        protected $permissionsUpdate = array('admin.system.package.canUpdatePackage');
+       
+       public function validateSearchForPurchasedItems() {
+               // TODO: validate permissions
+               
+               $this->readString('password', true);
+               $this->readString('username', true);
+               
+               if (empty($this->parameters['wcfMajorReleases']) || !is_array($this->parameters['wcfMajorReleases'])) {
+                       throw new UserInputException('wcfMajorReleases');
+               }
+               
+               if (empty($this->parameters['username'])) {
+                       // check if user has already provided credentials
+                       $sql = "SELECT  loginUsername, loginPassword
+                               FROM    wcf".WCF_N."_package_update_server
+                               WHERE   serverURL = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql, 1);
+                       $statement->execute(array('http://store.woltlab.com/typhoon/'));
+                       $row = $statement->fetchArray();
+                       if (!empty($row['loginUsername']) && !empty($row['loginPassword'])) {
+                               $this->parameters['password'] = $row['loginPassword'];
+                               $this->parameters['username'] = $row['loginUsername'];
+                       }
+               }
+       }
+       
+       public function searchForPurchasedItems() {
+               if (empty($this->parameters['username']) || empty($this->parameters['password'])) {
+                       return array(
+                               'template' => $this->renderAuthorizationDialog(false)
+                       );
+               }
+               
+               $request = new HTTPRequest('https://www.woltlab.com/api/1.0/customer/purchases/list.json', array(
+                       'method' => 'POST'
+               ), array(
+                       'username' => $this->parameters['username'],
+                       'password' => $this->parameters['password'],
+                       'wcfMajorReleases' => $this->parameters['wcfMajorReleases']
+               ));
+               
+               $request->execute();
+               $reply = $request->getReply();
+               $response = JSON::decode($reply['body']);
+               
+               $code = (isset($response['status'])) ? $response['status'] : 500;
+               switch ($code) {
+                       case 200:
+                               if (empty($response['products'])) {
+                                       return array(
+                                               'noResults' => WCF::getLanguage()->get('wcf.acp.pluginstore.purchasedItems.noResults')
+                                       );
+                               }
+                               else {
+                                       WCF::getSession()->register('__pluginStoreProducts', $response['products']);
+                                       
+                                       return array(
+                                               'redirectURL' => LinkHandler::getInstance()->getLink('PluginStorePurchasedItems')
+                                       );
+                               }
+                       break;
+                       
+                       // authentication error
+                       case 401:
+                               return array(
+                                       'template' => $this->renderAuthorizationDialog(true)
+                               );
+                       break;
+                       
+                       // any other kind of errors
+                       default:
+                               throw new SystemException(WCF::getLanguage()->getDynamicVariable('wcf.acp.pluginstore.api.error', array('status' => $code)));
+                       break;
+               }
+       }
+       
+       protected function renderAuthorizationDialog($rejected) {
+               WCF::getTPL()->assign(array(
+                       'rejected' => $rejected
+               ));
+               
+               return WCF::getTPL()->fetch('pluginStoreAuthorization');
+       }
 }
index a880aa20e0fb0051f746f972a61eb3b84a0c1c4f..444d8ca0e8b55d4f3baff5a1faaead4ea69495f8 100644 (file)
@@ -35,7 +35,7 @@ final class JSON {
                $data = json_decode($json, $asArray);
                
                if ($data === null && self::getLastError() !== JSON_ERROR_NONE) {
-                       throw new SystemException('Could not decode JSON "'.$json.'"');
+                       throw new SystemException('Could not decode JSON (error '.self::getLastError().'): '.$json);
                }
                
                return $data;
index f1f5028e209d9481aae79821ff3e64d56c44c5f8..a206f9217519edf130e72234f5b0d16983d3afd3 100644 (file)
@@ -1031,6 +1031,20 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.pageMenu.menuItemParameters"><![CDATA[Parameter]]></item>
        </category>
        
+       <category name="wcf.acp.pluginstore">
+               <item name="wcf.acp.pluginstore.api.error"><![CDATA[Fehler {@$status}): Der Server konnte die Anfrage nicht erfolgreich bearbeiten.]]></item>
+               <item name="wcf.acp.pluginstore.authorization"><![CDATA[Autorisierung erforderlich]]></item>
+               <item name="wcf.acp.pluginstore.authorization.credentials"><![CDATA[Zugangsdaten]]></item>
+               <item name="wcf.acp.pluginstore.authorization.credentials.description"><![CDATA[Geben Sie bitte Benutzername und Passwort für Ihr WoltLab.com-Benutzerkonto an.]]></item>
+               <item name="wcf.acp.pluginstore.authorization.credentials.rejected"><![CDATA[Ihre Zugangsdaten wurden vom Server abgewiesen, bitte überprüfen Sie Benutzername und Passwort!]]></item>
+               <item name="wcf.acp.pluginstore.authorization.username"><![CDATA[Benutzername]]></item>
+               <item name="wcf.acp.pluginstore.authorization.password"><![CDATA[Password]]></item>
+               <item name="wcf.acp.pluginstore.authorization.saveCredentials"><![CDATA[Zugangsdaten für aktuelle Sitzung speichern]]></item>
+               <item name="wcf.acp.pluginstore.purchasedItems.button.search"><![CDATA[Gekaufte Produkte (Plugin-Store)]]></item>
+               <item name="wcf.acp.pluginstore.purchasedItems"><![CDATA[Gekaufte Produkte (Plugin-Store)]]></item>
+               <item name="wcf.acp.pluginstore.purchasedItems.noResults"><![CDATA[Die Suche ergab keine Treffer, entweder haben Sie noch keine Produkte erworben oder diese sind nicht kompatibel.]]></item>
+       </category>
+       
        <category name="wcf.acp.rebuildData">
                <item name="wcf.acp.rebuildData"><![CDATA[Anzeigen aktualisieren]]></item>
                <item name="wcf.acp.rebuildData.description"><![CDATA[Für eine vollständige Aktualisierung aller Daten (z.B. nach einem Datenimport) führen Sie die Aktionen bitte in der hier angegebenen Reihenfolge durch.]]></item>