Add FontManager
authorTim Düsterhus <duesterhus@woltlab.com>
Wed, 15 Jul 2020 09:24:12 +0000 (11:24 +0200)
committerTim Düsterhus <duesterhus@woltlab.com>
Wed, 15 Jul 2020 10:17:33 +0000 (12:17 +0200)
see #3394

wcfsetup/install/files/lib/system/style/FontManager.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/style/exception/FontDownloadFailed.class.php [new file with mode: 0644]

diff --git a/wcfsetup/install/files/lib/system/style/FontManager.class.php b/wcfsetup/install/files/lib/system/style/FontManager.class.php
new file mode 100644 (file)
index 0000000..0e29078
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+namespace wcf\system\style;
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Exception\ClientException;
+use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Psr7\Request;
+use wcf\system\io\AtomicWriter;
+use wcf\system\io\HttpFactory;
+use wcf\system\SingletonFactory;
+use wcf\system\style\exception\FontDownloadFailed;
+use wcf\util\FileUtil;
+use wcf\util\JSON;
+
+/**
+ * Manages webfont families.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Style
+ * @since      5.3
+ */
+class FontManager extends SingletonFactory {
+       /**
+        * @var ClientInterface
+        */
+       protected $http;
+       
+       /**
+        * @inheritDoc
+        */
+       protected function init() {
+               $this->http = HttpFactory::makeClient([
+                       'base_uri' => 'https://fonts.woltlab.com/'
+               ]);
+       }
+       
+       /**
+        * Returns the path to the family's CSS file.
+        * 
+        * @return      string
+        */
+       public function getCssFilename($family) {
+               return WCF_DIR.'font/families/'.$family.'/font.css';
+       }
+       
+       /**
+        * Returns whether the family's CSS file exists, implying that
+        * the family is available.
+        * 
+        * @return      boolean
+        */
+       public function isFamilyDownloaded($family) {
+               return is_readable($this->getCssFilename($family));
+       }
+       
+       /**
+        * Fetch the list of available families and returns it as an array.
+        * 
+        * @return      string[]
+        */
+       public function fetchAvailableFamilies() {
+               $response = $this->http->send(new Request('GET', 'families.json'));
+               return JSON::decode($response->getBody());
+       }
+       
+       /**
+        * Downloads the given font family, stores it in font/families/<family> and
+        * returns the decoded font manifest.
+        * 
+        * @param       string  $family
+        * @return      mixed[]
+        */
+       public function downloadFamily($family) {
+               try {
+                       $response = $this->http->send(new Request('GET', $family.'/manifest.json'));
+                       $manifest = JSON::decode($response->getBody());
+                       
+                       $familyDirectory = dirname($this->getCssFilename($family));
+                       FileUtil::makePath($familyDirectory);
+                       
+                       $css = $manifest['css'];
+                       
+                       foreach ($manifest['font_files'] as $filename) {
+                               if ($filename !== basename($filename)) {
+                                       throw new \InvalidArgumentException("Invalid filename '".$filename."' given.");
+                               }
+                               
+                               $response = $this->http->send(new Request('GET', $family.'/'.$filename));
+                               
+                               $file = new AtomicWriter($familyDirectory.'/'.$filename);
+                               while (!$response->getBody()->eof()) {
+                                       $file->write($response->getBody()->read(4096));
+                               }
+                               $response->getBody()->close();
+                               $file->flush();
+                               $file->close();
+                               
+                               $css = str_replace('url("' . $filename . '")', 'url("../font/getFont.php?family='.rawurlencode($family).'&filename='.rawurlencode($filename).'")', $css);
+                       }
+                       
+                       file_put_contents($this->getCssFilename($family), $css);
+                       
+                       return $manifest;
+               }
+               catch (ClientException $e) {
+                       if ($e->getResponse()->getStatusCode() == 404) {
+                               throw new FontDownloadFailed("Unable to download font family '".$family."'.", 'notFound', $e);
+                       }
+                       else {
+                               throw new FontDownloadFailed("Unable to download font family '".$family."'.", '', $e);
+                       }
+               }               
+               catch (RequestException $e) {
+                       throw new FontDownloadFailed("Unable to download font family '".$family."'.", '', $e);
+               }
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/style/exception/FontDownloadFailed.class.php b/wcfsetup/install/files/lib/system/style/exception/FontDownloadFailed.class.php
new file mode 100644 (file)
index 0000000..0d9c6ee
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+namespace wcf\system\style\exception;
+
+/**
+ * Indicates that the font download failed.
+ * 
+ * @author     Tim Duesterhus
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Style
+ * @since      5.3
+ */
+class FontDownloadFailed extends \Exception {
+       /**
+        * @var string
+        */
+       private $reason = '';
+       
+       public function __construct($message, $reason = '', \Throwable $previous = null) {
+               parent::__construct($message, 0, $previous);
+               
+               $this->reason = $reason;
+       }
+       
+       /**
+        * @return string
+        */
+       public function getReason() {
+               return $this->reason;
+       }
+}