From 3fda22744e5d3dab9522ad38c8368bf6d4f53879 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B8rn=20Johansen?= <bjorn@dekode.no>
Date: Fri, 10 Aug 2018 21:00:11 +0200
Subject: [PATCH] Version 0.1.0

---
 README.md            |  34 ++++++-
 composer.json        |  24 +++++
 src/Translatable.php | 231 +++++++++++++++++++++++++++++++++++++++++++
 src/Wplang.php       | 133 +++++++++++++++++++++++++
 4 files changed, 420 insertions(+), 2 deletions(-)
 create mode 100644 composer.json
 create mode 100644 src/Translatable.php
 create mode 100644 src/Wplang.php

diff --git a/README.md b/README.md
index 9144778..f7d276b 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,32 @@
-# wplang
-Composer plugin to download translation files from wordpress.org
+# bjornjohansen/wplang
+
+Composer plugin to download translation files for WordPress core, plugins and themes from wordpress.org.
+
+## Installation
+
+First run:
+
+```
+composer require bjornjohansen/wplang
+```
+
+Then you’ll have to edit your `composer.json` file. You need to add the following section:
+```
+"extra": {
+    "wordpress-languages": [ "en_GB", "nb_NO", "sv_SE" ],
+    "wordpress-language-dir": "wp-content/languages"
+}
+```
+
+You should propbably want to customize these values to suit your needs.
+
+Finally run:
+```
+composer update
+```
+
+Now Composer will try to pull down translations for your packages from wordpress.org every time you install or update a package.
+
+## Credits
+
+This package Started as a fork of Angry Creative’s [Composer Auto Language Updates](https://github.com/Angrycreative/composer-plugin-language-update), but has since been rewritten. It is not compatible with the original package at all, but this package would probably not have existed with the first. There are probably some code in this package that the original author will still recognize.
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..4abbe97
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,24 @@
+{
+    "name": "bjornjohansen/wplang",
+    "description": "Composer plugin to download translation files from wordpress.org",
+    "version": "0.1.0",
+    "license": "GPL-2.0+",
+    "type": "composer-plugin",
+    "authors": [
+        {
+            "name": "Bjørn Johansen",
+            "email": "post@bjornjohansen.no"
+        }
+    ],
+    "require": {
+        "composer-plugin-api": "^1.0"
+    },
+    "autoload": {
+        "psr-4": {
+            "BJ\\Wplang\\": "src"
+        }
+    },
+    "extra": {
+        "class": "BJ\\Wplang\\Wplang"
+    }
+}
\ No newline at end of file
diff --git a/src/Translatable.php b/src/Translatable.php
new file mode 100644
index 0000000..6649fe6
--- /dev/null
+++ b/src/Translatable.php
@@ -0,0 +1,231 @@
+<?php
+/**
+ * @package BJ\WPlang
+ */
+
+namespace BJ\WPlang;
+
+/**
+ * Class Translatable
+ *
+ * @package BJ\WPlang
+ */
+class Translatable {
+
+	/**
+	 * The package type: 'plugin', 'theme' or 'core'.
+	 *
+	 * @var string
+	 */
+	protected $type;
+
+	/**
+	 * The plugin/theme slug, E.g. 'query-monitor'. In case of core, this is 'wordpress'.
+	 *
+	 * @var string
+	 */
+	protected $slug;
+
+	/**
+	 * The package version.
+	 *
+	 * @var float|string
+	 */
+	protected $version;
+
+	/**
+	 * Array of the languages we are using.
+	 *
+	 * @var array
+	 */
+	protected $languages = [];
+
+	/**
+	 * Full path to the language files target directory.
+	 *
+	 * @var string
+	 */
+	protected $wpLanguageDir;
+
+	/**
+	 * Array of translation packs available in our languages.
+	 *
+	 * @var array
+	 */
+	protected $translations = [];
+
+	/**
+	 * Constructor.
+	 *
+	 * @param string $type          The package type: 'plugin', 'theme' or 'core'.
+	 * @param string $slug          The plugin/theme slug, E.g. 'query-monitor'. In case of core, this is 'wordpress'.
+	 * @param string $version       The package version.
+	 * @param string $languages     Array of the languages we are using.
+	 * @param string $wpLanguageDir Full path to the language files target directory.
+	 */
+	public function __construct( $type, $slug, $version, $languages, $wpLanguageDir ) {
+
+		$this->type          = $type;
+		$this->slug          = $slug;
+		$this->version       = $version;
+		$this->languages     = $languages;
+		$this->wpLanguageDir = $wpLanguageDir;
+
+		try {
+			$this->translations = $this->getAvailableTranslations();
+		} catch ( \Exception $e ) {
+			throw new \Exception( $e->getMessage() );
+		}
+	}
+
+	/**
+	 * Get a list of available translations in our languages from the API.
+	 *
+	 * @throws \Exception
+	 * @return array
+	 */
+	protected function getAvailableTranslations() : array {
+
+		switch ( $this->type ) {
+			case 'plugin':
+				$url = sprintf( 'https://api.wordpress.org/translations/plugins/1.0/?slug=%s&version=%s', $this->slug, $this->version );
+				break;
+
+			case 'theme':
+				$url = sprintf( 'https://api.wordpress.org/translations/themes/1.0/?slug=%s&version=%s', $this->slug, $this->version );
+				break;
+
+			case 'core':
+				$url = sprintf( 'https://api.wordpress.org/translations/core/1.0/?version=%s', $this->version );
+				break;
+
+			default:
+				throw new \Exception( 'Unknown package type' );
+		}
+
+		$body = file_get_contents( $url );
+		$res = json_decode( $body );
+
+		if ( ! isset( $res->translations ) || empty( $res->translations ) ) {
+			throw new \Exception( 'No translations found' );
+		}
+
+		$translations = [];
+
+		foreach ( $res->translations as $translation ) {
+			if ( in_array( $translation->language, $this->languages, true ) ) {
+				$translations[ $translation->language ][] = $translation;
+			}
+		}
+
+		return $translations;
+	}
+
+	/**
+	 * Fetch the translations to our languages for our package.
+	 *
+	 * @return array
+	 */
+	public function fetch() : array {
+
+		$results = [];
+
+		foreach ( $this->languages as $language ) {
+			$result = $this->installTranslation( $this->translations[ $language ][0]->package );
+
+			if ( $result ) {
+				$results[] = $language;
+			}
+		}
+
+		return $results;
+	}
+
+
+	/**
+	 * Get the destination path for type type of object.
+	 *
+	 * This will also create the directory if if doesn't exist.
+	 *
+	 * @throws \Exception
+	 * @return string path to the destination directory.
+	 */
+	public function getDestPath() : string {
+		$destPath = $this->wpLanguageDir;
+
+		switch ( $this->type ) {
+			case 'plugin':
+				$destPath .= '/plugins';
+				break;
+
+			case 'theme':
+				$destPath .= '/themes';
+				break;
+		}
+
+		if ( ! is_dir( $destPath ) ) {
+			$result = mkdir( $destPath, 0775, true );
+			if ( ! $result ) {
+				throw new \Exception( 'Failed to create directory at: ' . $destPath );
+			}
+		}
+
+		return $destPath;
+	}
+
+	/**
+	 * Unpack the downloaded translation ZIP file in the destination directory.
+	 *
+	 * @param string $tmpZipFileName Path to the translation ZIP file.
+	 * @return bool Whether the operation was successful or not.
+	 *
+	 * @throws \Exception
+	 */
+	public function unpackTranslation( $tmpZipFileName ) : bool {
+
+		$result   = false;
+		$destPath = $this->getDestPath();
+		$zip      = new \ZipArchive();
+
+		if ( true === $zip->open( $tmpZipFileName ) ) {
+			for ( $i = 0; $i < $zip->numFiles; $i++ ) {
+				$ok = $zip->extractTo( $destPath, [ $zip->getNameIndex( $i ) ] );
+				if ( false === $ok ) {
+					throw new \Exception( 'There was an error moving the translation to the destination directory' );
+				}
+			}
+			$zip->close();
+
+			$result = true;
+
+		} else {
+			throw new \Exception( 'The was an error unzipping the translation files' );
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Download and extract the translation ZIP file in our destination directory.
+	 *
+	 * @param string $packageUrl The URL to the translation package ZIP file.
+	 * @return bool Whether the operation was successful or not.
+	 */
+	public function installTranslation( $packageUrl ) : bool {
+		$result = false;
+
+		try {
+			$tmpZipFileName = sys_get_temp_dir() . '/' . $this->type . '-' . $this->slug . '-' . $this->version . '-' . basename( $packageUrl );
+			$result = copy( $packageUrl, $tmpZipFileName );
+
+			if ( $result ) {
+				$result = $this->unpackTranslation( $tmpZipFileName );
+				unlink( $tmpZipFileName );
+			}
+		} catch ( \Exception $e ) {
+		}
+
+		return $result;
+	}
+
+}
diff --git a/src/Wplang.php b/src/Wplang.php
new file mode 100644
index 0000000..81f3058
--- /dev/null
+++ b/src/Wplang.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace BJ\Wplang;
+
+use Composer\Composer;
+use Composer\Script\Event;
+use Composer\EventDispatcher\EventSubscriberInterface;
+use Composer\IO\IOInterface;
+use Composer\Plugin\PluginInterface;
+use Composer\Installer\PackageEvent;
+use Composer\Package\PackageInterface;
+
+class Wplang implements PluginInterface, EventSubscriberInterface {
+
+	/**
+	 * Array of the languages we are using.
+	 *
+	 * @var array
+	 */
+	protected $languages = [];
+
+	/**
+	 * Full path to the language files target directory.
+	 *
+	 * @var string
+	 */
+	protected $wpLanguageDir = '';
+
+	/**
+	 * @var Composer
+	 */
+	protected $composer;
+
+	/**
+	 * @var IOInterface
+	 */
+	protected $io;
+
+	/**
+	 * Composer plugin activation.
+	 */
+	public function activate( Composer $composer, IOInterface $io ) {
+		$this->composer = $composer;
+		$this->io = $io;
+
+		$extra = $this->composer->getPackage()->getExtra();
+
+		if ( ! empty( $extra['wordpress-languages'] ) ) {
+			$this->languages = $extra['wordpress-languages'];
+		}
+
+		if ( ! empty( $extra['wordpress-language-dir'] ) ) {
+			$this->wpLanguageDir = dirname( dirname( dirname( dirname( __DIR__ ) ) ) ) . '/' . $extra['wordpress-language-dir'];
+		}
+	}
+
+
+	/**
+	 * Subscribe to Composer events.
+	 *
+	 * @return array The events and callbacks.
+	 */
+	public static function getSubscribedEvents() {
+		return [
+			'post-package-install' => [
+				[ 'onPackageAction', 0 ],
+			],
+			'post-package-update' => [
+				[ 'onPackageAction', 0 ],
+			],
+		];
+	}
+
+	/**
+	 * Our callback for the post-package-install|update events.
+	 *
+	 * @param  PackageEvent $event The package event object.
+	 */
+	public function onPackageAction( PackageEvent $event ) {
+		$package = $event->getOperation()->getPackage();
+		$this->getTranslations( $package );
+	}
+
+	/**
+	 * Get translations for a package, where applicable.
+	 *
+	 * @param PackageInterface $package
+	 */
+	protected function getTranslations( PackageInterface $package ) {
+
+		try {
+
+			$t = new \stdClass();
+
+			list( $provider, $name ) = explode( '/', $package->getName(), 2 );
+
+			switch ( $package->getType() ) {
+				case 'wordpress-plugin':
+					$t = new Translatable( 'plugin', $name, $package->getVersion(), $this->languages, $this->wpLanguageDir );
+					break;
+				case 'wordpress-theme':
+					$t = new Translatable( 'theme', $name, $package->getVersion(), $this->languages, $this->wpLanguageDir );
+					break;
+				case 'package':
+					if ( 'johnpbloch' === $provider && 'wordpress' === $name ) {
+						$t = new Translatable( 'core', $name, $package->getVersion(), $this->languages, $this->wpLanguageDir );
+					}
+					break;
+
+				default:
+					break;
+			}
+
+			if ( is_a( $t, __NAMESPACE__ . '\Translatable' ) ) {
+
+				$results = $t->fetch();
+
+				if ( empty( $results ) ) {
+					$this->io->write( '      - ' . sprintf( 'No translations updated for %s', $package->getName() ) );
+				} else {
+					foreach ( $results as $result ) {
+						$this->io->write( '      - ' . sprintf( 'Updated translation to %1$s for %2$s', $result, $package->getName() ) );
+					}
+				}
+			}
+		} catch ( \Exception $e ) {
+			$this->io->writeError( $e->getMessage() );
+		}
+
+	}
+
+}
+
-- 
GitLab