Added processor for language servers
authorAlexander Ebert <ebert@woltlab.com>
Fri, 15 Jul 2011 14:58:14 +0000 (16:58 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Fri, 15 Jul 2011 14:58:14 +0000 (16:58 +0200)
wcfsetup/install/files/lib/system/language/LanguageServerProcessor.class.php [new file with mode: 0644]

diff --git a/wcfsetup/install/files/lib/system/language/LanguageServerProcessor.class.php b/wcfsetup/install/files/lib/system/language/LanguageServerProcessor.class.php
new file mode 100644 (file)
index 0000000..89f1b95
--- /dev/null
@@ -0,0 +1,348 @@
+<?php
+namespace wcf\system\language;
+use wcf\data\language\Language;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
+use wcf\system\io\RemoteFile;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+use wcf\util\FileUtil;
+use wcf\util\XML;
+
+/**
+ * Processes language item import from language servers.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.language
+ * @category   Community Framework
+ */
+class LanguageServerProcessor extends SingletonFactory {
+       /**
+        * language object
+        * @var wcf\data\language\Language
+        */     
+       protected $language = null;
+       
+       /**
+        * Imports language variables for a language from given language servers.
+        * 
+        * @param       wcf\data\language\Language      $language
+        * @param       array   $languageServers
+        */
+       public function import(Language $language, array $languageServers) {
+               if (!count($languageServers)) return;
+               $this->language = $language;
+               
+               // get package list
+               $packageList = $this->getPackageList();
+               
+               foreach ($languageServers as $languageServer) {
+                       $this->importLanguageFile($languageServer->serverURL, $packageList);
+               }
+       }
+       
+       /**
+        * Returns list of installed packages and their associated version.
+        * 
+        * @return      array
+        */
+       protected function getPackageList() {
+               $sql = "SELECT  package, packageVersion
+                       FROM    wcf".WCF_N."_package";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute();
+               
+               $packages = array();
+               while ($row = $statement->fetchArray()) {
+                       $packages[$row['package']] = $row['packageVersion'];
+               }
+               
+               return $packages;
+       }
+       
+       /**
+        * Downloads and imports a language file from a language server.
+        * 
+        * @param       string  $location
+        * @param       array   $packageList
+        */     
+       protected function importLanguageFile($location, array $packageList) {
+               // get proxy
+               $options = array();
+               if (PROXY_SERVER_HTTP) {
+                       $options['http']['proxy'] = PROXY_SERVER_HTTP;
+                       $options['http']['request_fulluri'] = true;
+               }
+               
+               // parse url
+               $parsedURL = parse_url($location);
+               $port = ($parsedURL['scheme'] == 'https' ? 443 : 80);
+               $host = $parsedURL['host'];
+               $path = (isset($parsedURL['path']) ? $parsedURL['path'] : '/');
+               
+               $remoteFile = new RemoteFile(($parsedURL['scheme'] == 'https' ? 'ssl://' : '').$host, $port, 30, $options); // the file to read.
+               if (!isset($remoteFile)) {
+                       throw new SystemException("cannot connect to http host '".$host."'", 14000);
+               }
+               
+               // build and send the http request
+               $request = "POST ".$path." HTTP/1.0\r\n";
+               $request .= "User-Agent: HTTP.PHP (LanguageServerProcessor.class.php; WoltLab Community Framework/".WCF_VERSION."; ".WCF::getLanguage()->languageCode.")\r\n";
+               $request .= "Accept: */*\r\n";
+               $request .= "Accept-Language: ".WCF::getLanguage()->languageCode."\r\n";
+               $request .= "Host: ".$host."\r\n";
+               
+               // build post string
+               $postString = 'languageCode='.$this->language->languageCode;
+               foreach ($packageList as $package => $packageVersion) {
+                       $postString .= '&packages['.urlencode($package).']='.urlencode($packageVersion);
+               }
+               
+               // send content type and length
+               $request .= "Content-Type: application/x-www-form-urlencoded\r\n";
+               $request .= "Content-Length: ".strlen($postString)."\r\n";
+               // if it is a POST request, there MUST be a blank line before the POST data, but there MUST NOT be 
+               // another blank line before, and of course there must be another blank line at the end of the request!
+               $request .= "\r\n";
+               if (!empty($postString)) $request .= $postString."\r\n";
+               // send close
+               $request .= "Connection: Close\r\n\r\n";
+
+               // send request
+               $remoteFile->puts($request);
+               
+               // define response vars
+               $header = $content = '';
+               
+               // fetch the response.
+               while (!$remoteFile->eof()) {
+                       $line = $remoteFile->gets();
+                       if (rtrim($line) != '') {
+                               $header .= $line;
+                       } else {
+                               break;
+                       }
+               }
+               while (!$remoteFile->eof()) {
+                       $content .= $remoteFile->gets();
+               }
+               
+               // clean up and return the server's response.
+               $remoteFile->close();
+               
+               // get http status code / line
+               $httpStatusCode = 0;
+               $httpStatusLine = '';
+               if (preg_match('%http/\d\.\d (\d{3})[^\n]*%i', $header, $match)) {
+                       $httpStatusLine = trim($match[0]);
+                       $httpStatusCode = $match[1];
+               }
+               
+               // catch http 301 Moved Permanently
+               // catch http 302 Found
+               // catch http 303 See Other
+               if ($httpStatusCode == 301 || $httpStatusCode == 302 || $httpStatusCode == 303) {
+                       // find location
+                       if (preg_match('/location:([^\n]*)/i', $header, $match)) {
+                               $location = trim($match[1]);
+                               if ($location != $url) {
+                                       $this->importLanguageFile($location, $packageList);
+                                       return;
+                               }
+                       }
+               }
+               
+               $this->parseResponse($content);
+       }
+       
+       /**
+        * Parses XML response from language server.
+        * 
+        * @param       string  $xmlResponse
+        */
+       protected function parseResponse($xmlResponse) {
+               // parse xml
+               $xml = new XML();
+               $xml->loadXML('languageServerResponse.xml', $xmlResponse);
+               $xpath = $xml->xpath();
+               
+               // parse variables
+               $variables = array();
+               $packages = $xpath->query('/ns:language/ns:package');
+               foreach ($packages as $package) {
+                       $packageName = $package->getAttribute('name');
+                       $variables[$packageName] = array();
+                       
+                       $categories = $xpath->query('child::ns:category', $package);
+                       foreach ($categories as $category) {
+                               $categoryName = $category->getAttribute('name');
+                               $variables[$packageName][$categoryName] = array();
+                               
+                               $items = $xpath->query('child::ns:item', $category);
+                               foreach ($items as $item) {
+                                       $variables[$packageName][$categoryName][$item->getAttribute('name')] = $item->nodeValue;
+                               }
+                       }
+               }
+               
+               // try to resolve packages
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("package IN (?)", array(array_keys($variables)));
+               
+               $sql = "SELECT  packageID, package
+                       FROM    wcf".WCF_N."_package
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditions->getParameters());
+               $packages = array();
+               while ($row = $statement->fetchArray()) {
+                       $packages[$row['package']] = $row['packageID'];
+               }
+               
+               // ignore variables if no package is known
+               if (!count($packages)) return;
+               
+               $this->importVariables($variables, $packages);
+       }
+       
+       /**
+        * Imports language variables and categories.
+        * 
+        * @param       array   $variables
+        * @param       array   $packages
+        */
+       protected function importVariables(array $variables, array $packages) {
+               $categories = $this->importCategories($variables);
+               
+               $createItems = $updateItems = array();
+               foreach ($packages as $package => $packageID) {
+                       foreach ($variables[$package] as $category => $items) {
+                               // get existing items
+                               $conditions = new PreparedStatementConditionBuilder();
+                               $conditions->add("languageID = ?", array($this->language->languageID));
+                               $conditions->add("packageID = ?", array($packageID));
+                               $conditions->add("languageCategoryID = ?", array($categories[$category]));
+                               $conditions->add("languageItem IN (?)", array(array_keys($items)));
+                                       
+                               $sql = "SELECT  languageItemID, languageItem
+                                       FROM    wcf".WCF_N."_language_item
+                                       ".$conditions;
+                               $statement = WCF::getDB()->prepareStatement($sql);
+                               $statement->execute($conditions->getParameters());
+                               
+                               $existingItemIDs = array();
+                               while ($row = $statement->fetchArray()) {
+                                       $existingItemIDs[$row['languageItem']] = $row['languageItemID'];
+                               }
+                               
+                               foreach ($items as $itemName => $itemValue) {
+                                       if (isset($existingItemIDs[$itemName])) {
+                                               $updateItems[$existingItemIDs[$itemName]] = $itemValue;
+                                       }
+                                       else {
+                                               $createItems[] = array(
+                                                       'languageID' => $this->language->languageID,
+                                                       'languageItem' => $itemName,
+                                                       'languageItemValue'=> $itemValue,
+                                                       'languageItemOriginIsSystem' => 1,
+                                                       'languageCategoryID' => $categories[$category],
+                                                       'packageID' => $packageID
+                                               );
+                                       }
+                               }
+                       }
+               }
+               
+               // create items
+               if (count($createItems)) {
+                       $sql = "INSERT INTO     wcf".WCF_N."_language_item
+                                               (languageID, languageItem, languageItemValue, languageItemOriginIsSystem, languageCategoryID, packageID)
+                               VALUES          (?, ?, ?, ?, ?, ?)";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       
+                       foreach ($createItems as $item) {
+                               $statement->execute(array(
+                                       $item['languageID'],
+                                       $item['languageItem'],
+                                       $item['languageItemValue'],
+                                       $item['languageItemOriginIsSystem'],
+                                       $item['languageCategoryID'],
+                                       $item['packageID']
+                               ));
+                       }
+               }
+               
+               // update items
+               if (count($updateItems)) {
+                       $sql = "UPDATE  wcf".WCF_N."_language_item
+                               SET     languageItemValue = ?
+                               WHERE   languageItemID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       
+                       foreach ($updateItems as $languageItemID => $languageItemValue) {
+                               $statement->execute(array($languageItemID, $languageItemValue));
+                       }
+               }
+       }
+       
+       /**
+        * Imports new langage categories.
+        * 
+        * @param       array   $variables
+        * @return      array
+        */     
+       protected function importCategories(array $variables) {
+               // get categories
+               $categoryNames = array();
+               foreach ($variables as $package => $dummy) {
+                       $categoryNames = array_merge($categoryNames, array_keys($variables[$package]));
+               }
+               
+               // fetch existing categories
+               $conditions = new PreparedStatementConditionBuilder();
+               $conditions->add("languageCategory IN (?)", array($categoryNames));
+               
+               $sql = "SELECT  languageCategoryID, languageCategory
+                       FROM    wcf".WCF_N."_language_category
+                       ".$conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($conditions->getParameters());
+               
+               $existingCategories = array();
+               while ($row = $statement->fetchArray()) {
+                       $existingCategories[$row['languageCategory']] = $row['languageCategoryID'];
+               }
+               
+               // create non-existing categories
+               $createCategories = array_diff($categoryNames, array_keys($existingCategories));
+               if (count($createCategories)) {
+                       // use raw queries for better performance
+                       $sql = "INSERT INTO     wcf".WCF_N."_language_category
+                                               (languageCategory)
+                               VALUES          (?)";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       foreach ($createCategories as $category) {
+                               $statement->execute(array($category));
+                       }
+                       
+                       // get ids for created categories
+                       $conditions = new PreparedStatementConditionBuilder();
+                       $conditions->add("languageCategory IN (?)", array($createCategories));
+                       
+                       $sql = "SELECT  languageCategoryID, languageCategory
+                               FROM    wcf".WCF_N."_language_category
+                               ".$conditions;
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute($conditions->getParameters());
+                       
+                       while ($row = $statement->fetchArray()) {
+                               $existingCategories[$row['languageCategory']] = $row['languageCategoryID'];
+                       }
+               }
+               
+               return $existingCategories;
+       }
+}