├── .gitignore ├── phpunit.xml.dist ├── README.md ├── tests └── WPOffloadS3Installer │ ├── Exceptions │ └── MissingKeyExceptionTest.php │ ├── RemoteFilesystemTest.php │ └── PluginTest.php ├── src └── WPOffloadS3Installer │ ├── Exceptions │ └── MissingKeyException.php │ ├── RemoteFilesystem.php │ └── Plugin.php ├── .travis.yml ├── LICENSE └── composer.json /.gitignore: -------------------------------------------------------------------------------- 1 | # composer.lock 2 | # See: https://getcomposer.org/doc/02-libraries.md#lock-file 3 | composer.lock 4 | 5 | # composer vendor directory 6 | vendor 7 | 8 | # phpunit code coverage report 9 | coverage 10 | build/logs/clover.xml 11 | 12 | .idea -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests 6 | 7 | 8 | 9 | 10 | 11 | src 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WP Offload S3 Installer 2 | 3 | A composer plugin that makes installing [WP Offload S3] with [composer] easier. 4 | 5 | It reads your :key: WP Offload S3 key from the **environment** or a **.env file**. 6 | 7 | All credit to [PhilippBaschke/acf-pro-installer]. 8 | 9 | [WP Offload S3]: https://deliciousbrains.com/wp-offload-s3/ 10 | [composer]: https://github.com/composer/composer 11 | [PhilippBaschke/acf-pro-installer]: https://github.com/PhilippBaschke/acf-pro-installer 12 | 13 | ## Usage 14 | 15 | // TODO: I couldn't test the work as our subscription has expired :-( 16 | -------------------------------------------------------------------------------- /tests/WPOffloadS3Installer/Exceptions/MissingKeyExceptionTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 12 | 'Could not find a key for WP Offload S3. ' . 13 | 'Please make it available via the environment variable ' . 14 | $message, 15 | $e->getMessage() 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/WPOffloadS3Installer/Exceptions/MissingKeyException.php: -------------------------------------------------------------------------------- 1 | io = $this->getMock('Composer\IO\IOInterface'); 13 | } 14 | 15 | public function testExtendsComposerRemoteFilesystem() 16 | { 17 | $this->assertInstanceOf( 18 | 'Composer\Util\RemoteFilesystem', 19 | new RemoteFilesystem('', $this->io) 20 | ); 21 | } 22 | 23 | // Inspired by testCopy of Composer 24 | public function testCopyUsesWpOffloadS3FileUrl() 25 | { 26 | $wpOffloadS3FileUrl = 'file://'.__FILE__; 27 | $rfs = new RemoteFilesystem($wpOffloadS3FileUrl, $this->io); 28 | $file = tempnam(sys_get_temp_dir(), 'pb'); 29 | 30 | $this->assertTrue( 31 | $rfs->copy('http://example.org', 'does-not-exist', $file) 32 | ); 33 | $this->assertFileExists($file); 34 | unlink($file); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "outlandish/wp-offload-s3-installer", 3 | "description": "An install helper for WP Offload S3", 4 | "type": "composer-plugin", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Joaquim d'Souza", 9 | "email": "joaquim@outlandish.com" 10 | }, 11 | { 12 | "name": "Philipp Baschke", 13 | "email": "philipp@baschke.com" 14 | } 15 | ], 16 | "keywords": [ 17 | "wordpress", "composer", "wp", "plugin", "offload", "s3" 18 | ], 19 | "require": { 20 | "php": ">=5.5", 21 | "composer-plugin-api": "^1.0", 22 | "vlucas/phpdotenv": "^2.2" 23 | }, 24 | "require-dev": { 25 | "composer/composer": "1.0.*", 26 | "phpunit/phpunit": "4.8.*", 27 | "squizlabs/php_codesniffer": "2.*", 28 | "satooshi/php-coveralls": "1.*" 29 | }, 30 | "autoload": { 31 | "psr-4": { "Outlandish\\WPOffloadS3Installer\\": "src/WPOffloadS3Installer" } 32 | }, 33 | "extra": { 34 | "class": "Outlandish\\WPOffloadS3Installer\\Plugin" 35 | }, 36 | "scripts": { 37 | "lint": "phpcs src tests --standard=PSR2", 38 | "test": "phpunit", 39 | "coverage": "phpunit --coverage-html coverage", 40 | "coveralls": "coveralls" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/WPOffloadS3Installer/RemoteFilesystem.php: -------------------------------------------------------------------------------- 1 | wpOffloadS3FileUrl = $wpOffloadS3FileUrl; 39 | parent::__construct($io, $config, $options, $disableTls); 40 | } 41 | 42 | /** 43 | * Copy the remote file in local 44 | * 45 | * Use $wpOffloadS3FileUrl instead of the provided $fileUrl 46 | * 47 | * @param string $originUrl The origin URL 48 | * @param string $fileUrl The file URL (ignored) 49 | * @param string $fileName the local filename 50 | * @param bool $progress Display the progression 51 | * @param array $options Additional context options 52 | * 53 | * @return bool true 54 | */ 55 | public function copy( 56 | $originUrl, 57 | $fileUrl, 58 | $fileName, 59 | $progress = true, 60 | $options = [] 61 | ) { 62 | return parent::copy( 63 | $originUrl, 64 | $this->wpOffloadS3FileUrl, 65 | $fileName, 66 | $progress, 67 | $options 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/WPOffloadS3Installer/Plugin.php: -------------------------------------------------------------------------------- 1 | composer = $composer; 73 | $this->io = $io; 74 | } 75 | 76 | /** 77 | * Subscribe this Plugin to relevant Events 78 | * 79 | * Pre Install/Update: The version needs to be added to the url 80 | * (will show up in composer.lock) 81 | * Pre Download: The key needs to be added to the url 82 | * (will not show up in composer.lock) 83 | * 84 | * @access public 85 | * @return array An array of events that the plugin subscribes to 86 | * @static 87 | */ 88 | public static function getSubscribedEvents() 89 | { 90 | return [ 91 | PackageEvents::PRE_PACKAGE_INSTALL => 'addVersion', 92 | PackageEvents::PRE_PACKAGE_UPDATE => 'addVersion', 93 | PluginEvents::PRE_FILE_DOWNLOAD => 'addKey' 94 | ]; 95 | } 96 | 97 | /** 98 | * Add the version to the package url 99 | * 100 | * The version needs to be added in the PRE_PACKAGE_INSTALL/UPDATE 101 | * event to make sure that different version save different urls 102 | * in composer.lock. Composer would load any available version from cache 103 | * although the version numbers might differ (because they have the same 104 | * url). 105 | * 106 | * @access public 107 | * @param PackageEvent $event The event that called the method 108 | * @throws UnexpectedValueException 109 | */ 110 | public function addVersion(PackageEvent $event) 111 | { 112 | $package = $this->getPackageFromOperation($event->getOperation()); 113 | 114 | if ($package->getName() === self::WP_OFFLOAD_S3_PACKAGE_NAME) { 115 | $version = $package->getPrettyVersion(); 116 | $package->setDistUrl( 117 | $this->addParameterToUrl($package->getDistUrl(), 't', $version) 118 | ); 119 | } 120 | } 121 | 122 | 123 | /** 124 | * Add the key from the environment to the event url 125 | * 126 | * The key is not added to the package because it would show up in the 127 | * composer.lock file in this case. A custom file system is used to 128 | * swap out the WP Offload S3 url with a url that contains the key. 129 | * 130 | * @access public 131 | * @param PreFileDownloadEvent $event The event that called this method 132 | * @throws MissingKeyException 133 | */ 134 | public function addKey(PreFileDownloadEvent $event) 135 | { 136 | $processedUrl = $event->getProcessedUrl(); 137 | 138 | if ($this->isWPOffloadS3PackageUrl($processedUrl)) { 139 | $rfs = $event->getRemoteFilesystem(); 140 | $processedUrl = $processedUrl . '/' . $this->getKeyFromEnv(); 141 | $wpOffloadS3Rfs = new RemoteFilesystem( 142 | $processedUrl, 143 | $this->io, 144 | $this->composer->getConfig(), 145 | $rfs->getOptions(), 146 | $rfs->isTlsDisabled() 147 | ); 148 | $event->setRemoteFilesystem($wpOffloadS3Rfs); 149 | } 150 | } 151 | 152 | /** 153 | * Get the package from a given operation 154 | * 155 | * Is needed because update operations don't have a getPackage method 156 | * 157 | * @access protected 158 | * @param OperationInterface $operation The operation 159 | * @return PackageInterface The package of the operation 160 | */ 161 | protected function getPackageFromOperation(OperationInterface $operation) 162 | { 163 | if ($operation->getJobType() === 'update') { 164 | return $operation->getTargetPackage(); 165 | } 166 | return $operation->getPackage(); 167 | } 168 | 169 | /** 170 | * Test if the given url is the WP Offload S3 download url 171 | * 172 | * @access protected 173 | * @param string The url that should be checked 174 | * @return bool 175 | */ 176 | protected function isWPOffloadS3PackageUrl($url) 177 | { 178 | return strpos($url, self::WP_OFFLOAD_S3_PACKAGE_URL) !== false; 179 | } 180 | 181 | /** 182 | * Get the WP Offload S3 key from the environment 183 | * 184 | * Loads the .env file that is in the same directory as composer.json 185 | * and gets the key from the environment variable KEY_ENV_VARIABLE. 186 | * Already set variables will not be overwritten by the variables in .env 187 | * @link https://github.com/vlucas/phpdotenv#immutability 188 | * 189 | * @access protected 190 | * @return string The key from the environment 191 | * @throws MissingKeyException 192 | */ 193 | protected function getKeyFromEnv() 194 | { 195 | $this->loadDotEnv(); 196 | $key = getenv(self::KEY_ENV_VARIABLE); 197 | 198 | if (!$key) { 199 | throw new MissingKeyException(self::KEY_ENV_VARIABLE); 200 | } 201 | 202 | return $key; 203 | } 204 | 205 | /** 206 | * Make environment variables in .env available if .env exists 207 | * 208 | * getcwd() returns the directory of composer.json. 209 | * 210 | * @access protected 211 | */ 212 | protected function loadDotEnv() 213 | { 214 | if (file_exists(getcwd().DIRECTORY_SEPARATOR.'.env')) { 215 | $dotenv = new Dotenv(getcwd()); 216 | $dotenv->load(); 217 | } 218 | } 219 | 220 | /** 221 | * Add a parameter to the given url 222 | * 223 | * Adds the given parameter at the end of the given url. It only works with 224 | * urls that already have parameters (e.g. test.com?p=true) because it 225 | * uses & as a separation character. 226 | * 227 | * @access protected 228 | * @param string $url The url that should be appended 229 | * @param string $parameter The name of the parameter 230 | * @param string $value The value of the parameter 231 | * @return string The url appended with ¶meter=value 232 | */ 233 | protected function addParameterToUrl($url, $parameter, $value) 234 | { 235 | $cleanUrl = $this->removeParameterFromUrl($url, $parameter); 236 | $urlParameter = '&' . $parameter . '=' . urlencode($value); 237 | 238 | return $cleanUrl .= $urlParameter; 239 | } 240 | 241 | /** 242 | * Remove a given parameter from the given url 243 | * 244 | * Removes ¶meter=value from the given url. Only works with urls that 245 | * have multiple parameters and the parameter that should be removed is 246 | * not the first (because of the & character). 247 | * 248 | * @access protected 249 | * @param string $url The url where the parameter should be removed 250 | * @param string $parameter The name of the parameter 251 | * @return string The url with the ¶meter=value removed 252 | */ 253 | protected function removeParameterFromUrl($url, $parameter) 254 | { 255 | // e.g. &t=1.2.3 in example.com?p=index.php&t=1.2.3&k=key 256 | $pattern = "/(&$parameter=[^&]*)/"; 257 | return preg_replace($pattern, '', $url); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /tests/WPOffloadS3Installer/PluginTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf( 31 | 'Composer\Plugin\PluginInterface', 32 | new Plugin() 33 | ); 34 | } 35 | 36 | public function testImplementsEventSubscriberInterface() 37 | { 38 | $this->assertInstanceOf( 39 | 'Composer\EventDispatcher\EventSubscriberInterface', 40 | new Plugin() 41 | ); 42 | } 43 | 44 | public function testActivateMakesComposerAndIOAvailable() 45 | { 46 | $composer = $this->getMockBuilder('Composer\Composer')->getMock(); 47 | $io = $this->getMockBuilder('Composer\IO\IOInterface')->getMock(); 48 | $plugin = new Plugin; 49 | $plugin->activate($composer, $io); 50 | 51 | $this->assertAttributeEquals($composer, 'composer', $plugin); 52 | $this->assertAttributeEquals($io, 'io', $plugin); 53 | } 54 | 55 | public function testSubscribesToPrePackageInstallEvent() 56 | { 57 | $subscribedEvents = Plugin::getSubscribedEvents(); 58 | $this->assertEquals( 59 | $subscribedEvents[PackageEvents::PRE_PACKAGE_INSTALL], 60 | 'addVersion' 61 | ); 62 | } 63 | 64 | public function testSubscribesToPreUpdateInstallEvent() 65 | { 66 | $subscribedEvents = Plugin::getSubscribedEvents(); 67 | $this->assertEquals( 68 | $subscribedEvents[PackageEvents::PRE_PACKAGE_UPDATE], 69 | 'addVersion' 70 | ); 71 | } 72 | 73 | public function testSubscribesToPreFileDownloadEvent() 74 | { 75 | $subscribedEvents = Plugin::getSubscribedEvents(); 76 | $this->assertEquals( 77 | $subscribedEvents[PluginEvents::PRE_FILE_DOWNLOAD], 78 | 'addKey' 79 | ); 80 | } 81 | 82 | public function testAddVersionOnInstall() 83 | { 84 | // The version that should be required 85 | $version = '1.2.3'; 86 | 87 | // Make key available in the ENVIRONMENT 88 | putenv(self::KEY_ENV_VARIABLE . '=KEY'); 89 | 90 | // Mock a Package 91 | $package = $this 92 | ->getMockBuilder('Composer\Package\PackageInterface') 93 | ->setMethods([ 94 | 'getName', 95 | 'getPrettyVersion', 96 | 'getDistUrl', 97 | 'setDistUrl' 98 | ]) 99 | ->getMockForAbstractClass(); 100 | 101 | $package 102 | ->expects($this->once()) 103 | ->method('getName') 104 | ->willReturn(self::REPO_NAME); 105 | 106 | $package 107 | ->expects($this->once()) 108 | ->method('getPrettyVersion') 109 | ->willReturn($version); 110 | 111 | $package 112 | ->expects($this->once()) 113 | ->method('getDistUrl') 114 | ->willReturn(self::REPO_URL); 115 | 116 | $package 117 | ->expects($this->once()) 118 | ->method('setDistUrl') 119 | ->with(self::REPO_URL . "&t=$version"); 120 | 121 | // Mock an Operation 122 | $operationClass = 123 | 'Composer\DependencyResolver\Operation\InstallOperation'; 124 | $operation = $this 125 | ->getMockBuilder($operationClass) 126 | ->disableOriginalConstructor() 127 | ->setMethods(['getJobType', 'getPackage']) 128 | ->getMock(); 129 | 130 | $operation 131 | ->expects($this->once()) 132 | ->method('getJobType') 133 | ->willReturn('install'); 134 | 135 | $operation 136 | ->expects($this->once()) 137 | ->method('getPackage') 138 | ->willReturn($package); 139 | 140 | // Mock a PackageEvent 141 | $packageEvent = $this 142 | ->getMockBuilder('Composer\Installer\PackageEvent') 143 | ->disableOriginalConstructor() 144 | ->setMethods(['getOperation']) 145 | ->getMock(); 146 | 147 | $packageEvent 148 | ->expects($this->once()) 149 | ->method('getOperation') 150 | ->willReturn($operation); 151 | 152 | // Call addVersion 153 | $plugin = new Plugin(); 154 | $plugin->addVersion($packageEvent); 155 | } 156 | 157 | public function testAddVersionOnUpdate() 158 | { 159 | // The version that should be required 160 | $version = '1.2.3'; 161 | 162 | // Make key available in the ENVIRONMENT 163 | putenv(self::KEY_ENV_VARIABLE . '=KEY'); 164 | 165 | // Mock a Package 166 | $package = $this 167 | ->getMockBuilder('Composer\Package\PackageInterface') 168 | ->setMethods([ 169 | 'getName', 170 | 'getPrettyVersion', 171 | 'getDistUrl', 172 | 'setDistUrl' 173 | ]) 174 | ->getMockForAbstractClass(); 175 | 176 | $package 177 | ->expects($this->once()) 178 | ->method('getName') 179 | ->willReturn(self::REPO_NAME); 180 | 181 | $package 182 | ->expects($this->once()) 183 | ->method('getPrettyVersion') 184 | ->willReturn($version); 185 | 186 | $package 187 | ->expects($this->once()) 188 | ->method('getDistUrl') 189 | ->willReturn(self::REPO_URL); 190 | 191 | $package 192 | ->expects($this->once()) 193 | ->method('setDistUrl') 194 | ->with(self::REPO_URL . "&t=$version"); 195 | 196 | // Mock an Operation 197 | $operationClass = 198 | 'Composer\DependencyResolver\Operation\UpdateOperation'; 199 | $operation = $this 200 | ->getMockBuilder($operationClass) 201 | ->disableOriginalConstructor() 202 | ->setMethods(['getJobType', 'getTargetPackage']) 203 | ->getMock(); 204 | 205 | $operation 206 | ->expects($this->once()) 207 | ->method('getJobType') 208 | ->willReturn('update'); 209 | 210 | $operation 211 | ->expects($this->once()) 212 | ->method('getTargetPackage') 213 | ->willReturn($package); 214 | 215 | // Mock a PackageEvent 216 | $packageEvent = $this 217 | ->getMockBuilder('Composer\Installer\PackageEvent') 218 | ->disableOriginalConstructor() 219 | ->setMethods(['getOperation']) 220 | ->getMock(); 221 | 222 | $packageEvent 223 | ->expects($this->once()) 224 | ->method('getOperation') 225 | ->willReturn($operation); 226 | 227 | // Call addVersion 228 | $plugin = new Plugin(); 229 | $plugin->addVersion($packageEvent); 230 | } 231 | 232 | public function testDontAddVersionOnOtherPackages() 233 | { 234 | // Make key available in the ENVIRONMENT 235 | putenv(self::KEY_ENV_VARIABLE . '=KEY'); 236 | 237 | // Mock a Package 238 | $package = $this 239 | ->getMockBuilder('Composer\Package\PackageInterface') 240 | ->setMethods([ 241 | 'getName', 242 | 'getPrettyVersion', 243 | 'getDistUrl', 244 | 'setDistUrl' 245 | ]) 246 | ->getMockForAbstractClass(); 247 | 248 | $package 249 | ->expects($this->once()) 250 | ->method('getName') 251 | ->willReturn('another-package'); 252 | 253 | $package 254 | ->expects($this->never()) 255 | ->method('getPrettyVersion'); 256 | 257 | $package 258 | ->expects($this->never()) 259 | ->method('getDistUrl'); 260 | 261 | $package 262 | ->expects($this->never()) 263 | ->method('setDistUrl'); 264 | 265 | // Mock an Operation 266 | $operationClass = 267 | 'Composer\DependencyResolver\Operation\InstallOperation'; 268 | $operation = $this 269 | ->getMockBuilder($operationClass) 270 | ->disableOriginalConstructor() 271 | ->setMethods(['getJobType', 'getPackage']) 272 | ->getMock(); 273 | 274 | $operation 275 | ->expects($this->once()) 276 | ->method('getJobType') 277 | ->willReturn('install'); 278 | 279 | $operation 280 | ->expects($this->once()) 281 | ->method('getPackage') 282 | ->willReturn($package); 283 | 284 | // Mock a PackageEvent 285 | $packageEvent = $this 286 | ->getMockBuilder('Composer\Installer\PackageEvent') 287 | ->disableOriginalConstructor() 288 | ->setMethods(['getOperation']) 289 | ->getMock(); 290 | 291 | $packageEvent 292 | ->expects($this->once()) 293 | ->method('getOperation') 294 | ->willReturn($operation); 295 | 296 | // Call addVersion 297 | $plugin = new Plugin(); 298 | $plugin->addVersion($packageEvent); 299 | } 300 | 301 | protected function versionPassesValidationHelper($version) 302 | { 303 | // Make key available in the ENVIRONMENT 304 | putenv(self::KEY_ENV_VARIABLE . '=KEY'); 305 | 306 | // Mock a Package 307 | $package = $this 308 | ->getMockBuilder('Composer\Package\PackageInterface') 309 | ->setMethods([ 310 | 'getName', 311 | 'getPrettyVersion', 312 | 'getDistUrl', 313 | 'setDistUrl' 314 | ]) 315 | ->getMockForAbstractClass(); 316 | 317 | $package 318 | ->expects($this->once()) 319 | ->method('getName') 320 | ->willReturn(self::REPO_NAME); 321 | 322 | $package 323 | ->expects($this->once()) 324 | ->method('getPrettyVersion') 325 | ->willReturn($version); 326 | 327 | $package 328 | ->expects($this->once()) 329 | ->method('getDistUrl') 330 | ->willReturn(self::REPO_URL); 331 | 332 | $package 333 | ->expects($this->once()) 334 | ->method('setDistUrl'); 335 | 336 | // Mock an Operation 337 | $operationClass = 338 | 'Composer\DependencyResolver\Operation\InstallOperation'; 339 | $operation = $this 340 | ->getMockBuilder($operationClass) 341 | ->disableOriginalConstructor() 342 | ->setMethods(['getJobType', 'getPackage']) 343 | ->getMock(); 344 | 345 | $operation 346 | ->expects($this->once()) 347 | ->method('getJobType') 348 | ->willReturn('install'); 349 | 350 | $operation 351 | ->expects($this->once()) 352 | ->method('getPackage') 353 | ->willReturn($package); 354 | 355 | // Mock a PackageEvent 356 | $packageEvent = $this 357 | ->getMockBuilder('Composer\Installer\PackageEvent') 358 | ->disableOriginalConstructor() 359 | ->setMethods(['getOperation']) 360 | ->getMock(); 361 | 362 | $packageEvent 363 | ->expects($this->once()) 364 | ->method('getOperation') 365 | ->willReturn($operation); 366 | 367 | // Call addVersion 368 | $plugin = new Plugin(); 369 | $plugin->addVersion($packageEvent); 370 | } 371 | 372 | public function testExactVersionWith3DigitsPassesValidation() 373 | { 374 | $this->versionPassesValidationHelper('1.2.3'); 375 | } 376 | 377 | public function testExactVersionWith4DigitsPassesValidation() 378 | { 379 | $this->versionPassesValidationHelper('1.2.3.4'); 380 | } 381 | 382 | public function testExactVersionWithPatchDoubleDigitsPassesValidation() 383 | { 384 | $this->versionPassesValidationHelper('1.2.30'); 385 | } 386 | 387 | protected function versionFailsValidationHelper($version) 388 | { 389 | // Expect an Exception 390 | $this->setExpectedException( 391 | 'UnexpectedValueException', 392 | 'The version constraint of ' . self::REPO_NAME . 393 | ' should be exact (with 3 or 4 digits). ' . 394 | 'Invalid version string "' . $version . '"' 395 | ); 396 | 397 | // Mock a Package 398 | $package = $this 399 | ->getMockBuilder('Composer\Package\PackageInterface') 400 | ->setMethods([ 401 | 'getName', 402 | 'getPrettyVersion' 403 | ]) 404 | ->getMockForAbstractClass(); 405 | 406 | $package 407 | ->expects($this->once()) 408 | ->method('getName') 409 | ->willReturn(self::REPO_NAME); 410 | 411 | $package 412 | ->expects($this->once()) 413 | ->method('getPrettyVersion') 414 | ->willReturn($version); 415 | 416 | // Mock an Operation 417 | $operationClass = 418 | 'Composer\DependencyResolver\Operation\InstallOperation'; 419 | $operation = $this 420 | ->getMockBuilder($operationClass) 421 | ->disableOriginalConstructor() 422 | ->setMethods(['getJobType', 'getPackage']) 423 | ->getMock(); 424 | 425 | $operation 426 | ->expects($this->once()) 427 | ->method('getJobType') 428 | ->willReturn('install'); 429 | 430 | $operation 431 | ->expects($this->once()) 432 | ->method('getPackage') 433 | ->willReturn($package); 434 | 435 | // Mock a PackageEvent 436 | $packageEvent = $this 437 | ->getMockBuilder('Composer\Installer\PackageEvent') 438 | ->disableOriginalConstructor() 439 | ->setMethods(['getOperation']) 440 | ->getMock(); 441 | 442 | $packageEvent 443 | ->expects($this->once()) 444 | ->method('getOperation') 445 | ->willReturn($operation); 446 | 447 | // Call addVersion 448 | $plugin = new Plugin(); 449 | $plugin->addVersion($packageEvent); 450 | } 451 | 452 | public function testDontAddVersionTwice() 453 | { 454 | // The version that should be required 455 | $version = '1.2.3'; 456 | 457 | // Make key available in the ENVIRONMENT 458 | putenv(self::KEY_ENV_VARIABLE . '=KEY'); 459 | 460 | // Mock a Package 461 | $package = $this 462 | ->getMockBuilder('Composer\Package\PackageInterface') 463 | ->setMethods([ 464 | 'getName', 465 | 'getPrettyVersion', 466 | 'getDistUrl', 467 | 'setDistUrl' 468 | ]) 469 | ->getMockForAbstractClass(); 470 | 471 | $package 472 | ->expects($this->once()) 473 | ->method('getName') 474 | ->willReturn(self::REPO_NAME); 475 | 476 | $package 477 | ->expects($this->once()) 478 | ->method('getPrettyVersion') 479 | ->willReturn($version); 480 | 481 | $package 482 | ->expects($this->once()) 483 | ->method('getDistUrl') 484 | ->willReturn(self::REPO_URL . '&t=' . $version); 485 | 486 | $package 487 | ->expects($this->once()) 488 | ->method('setDistUrl') 489 | ->with(self::REPO_URL . "&t=$version"); 490 | 491 | // Mock an Operation 492 | $operationClass = 493 | 'Composer\DependencyResolver\Operation\InstallOperation'; 494 | $operation = $this 495 | ->getMockBuilder($operationClass) 496 | ->disableOriginalConstructor() 497 | ->setMethods(['getJobType', 'getPackage']) 498 | ->getMock(); 499 | 500 | $operation 501 | ->expects($this->once()) 502 | ->method('getJobType') 503 | ->willReturn('install'); 504 | 505 | $operation 506 | ->expects($this->once()) 507 | ->method('getPackage') 508 | ->willReturn($package); 509 | 510 | // Mock a PackageEvent 511 | $packageEvent = $this 512 | ->getMockBuilder('Composer\Installer\PackageEvent') 513 | ->disableOriginalConstructor() 514 | ->setMethods(['getOperation']) 515 | ->getMock(); 516 | 517 | $packageEvent 518 | ->expects($this->once()) 519 | ->method('getOperation') 520 | ->willReturn($operation); 521 | 522 | // Call addVersion 523 | $plugin = new Plugin(); 524 | $plugin->addVersion($packageEvent); 525 | } 526 | 527 | public function testReplaceVersionInUrl() 528 | { 529 | // The version that should be required 530 | $version = '1.2.3'; 531 | 532 | // Make key available in the ENVIRONMENT 533 | putenv(self::KEY_ENV_VARIABLE . '=KEY'); 534 | 535 | // Mock a Package 536 | $package = $this 537 | ->getMockBuilder('Composer\Package\PackageInterface') 538 | ->setMethods([ 539 | 'getName', 540 | 'getPrettyVersion', 541 | 'getDistUrl', 542 | 'setDistUrl' 543 | ]) 544 | ->getMockForAbstractClass(); 545 | 546 | $package 547 | ->expects($this->once()) 548 | ->method('getName') 549 | ->willReturn(self::REPO_NAME); 550 | 551 | $package 552 | ->expects($this->once()) 553 | ->method('getPrettyVersion') 554 | ->willReturn($version); 555 | 556 | $package 557 | ->expects($this->once()) 558 | ->method('getDistUrl') 559 | ->willReturn(self::REPO_URL . '&t=' . $version . '.4'); 560 | 561 | $package 562 | ->expects($this->once()) 563 | ->method('setDistUrl') 564 | ->with(self::REPO_URL . "&t=$version"); 565 | 566 | // Mock an Operation 567 | $operationClass = 568 | 'Composer\DependencyResolver\Operation\InstallOperation'; 569 | $operation = $this 570 | ->getMockBuilder($operationClass) 571 | ->disableOriginalConstructor() 572 | ->setMethods(['getJobType', 'getPackage']) 573 | ->getMock(); 574 | 575 | $operation 576 | ->expects($this->once()) 577 | ->method('getJobType') 578 | ->willReturn('install'); 579 | 580 | $operation 581 | ->expects($this->once()) 582 | ->method('getPackage') 583 | ->willReturn($package); 584 | 585 | // Mock a PackageEvent 586 | $packageEvent = $this 587 | ->getMockBuilder('Composer\Installer\PackageEvent') 588 | ->disableOriginalConstructor() 589 | ->setMethods(['getOperation']) 590 | ->getMock(); 591 | 592 | $packageEvent 593 | ->expects($this->once()) 594 | ->method('getOperation') 595 | ->willReturn($operation); 596 | 597 | // Call addVersion 598 | $plugin = new Plugin(); 599 | $plugin->addVersion($packageEvent); 600 | } 601 | 602 | public function testAddKeyCreatesCustomFilesystemWithOldValues() 603 | { 604 | // Make key available in the ENVIRONMENT 605 | putenv(self::KEY_ENV_VARIABLE . '=KEY'); 606 | 607 | // Mock a RemoteFilesystem 608 | $options = ['options' => 'array']; 609 | $tlsDisabled = true; 610 | 611 | $rfs = $this 612 | ->getMockBuilder('Composer\Util\RemoteFilesystem') 613 | ->disableOriginalConstructor() 614 | ->setMethods(['getOptions', 'isTlsDisabled']) 615 | ->getMock(); 616 | 617 | $rfs 618 | ->expects($this->once()) 619 | ->method('getOptions') 620 | ->willReturn($options); 621 | 622 | $rfs 623 | ->expects($this->once()) 624 | ->method('isTlsDisabled') 625 | ->willReturn($tlsDisabled); 626 | 627 | // Mock Config 628 | $config = $this 629 | ->getMockBuilder('Composer\Config') 630 | ->getMock(); 631 | 632 | // Mock Composer 633 | $composer = $this 634 | ->getMockBuilder('Composer\Composer') 635 | ->setMethods(['getConfig']) 636 | ->getMock(); 637 | 638 | $composer 639 | ->expects($this->once()) 640 | ->method('getConfig') 641 | ->willReturn($config); 642 | 643 | // Mock IOInterface 644 | $io = $this 645 | ->getMockBuilder('Composer\IO\IOInterface') 646 | ->getMock(); 647 | 648 | // Mock an Event 649 | $event = $this 650 | ->getMockBuilder('Composer\Plugin\PreFileDownloadEvent') 651 | ->disableOriginalConstructor() 652 | ->setMethods([ 653 | 'getProcessedUrl', 654 | 'getRemoteFilesystem', 655 | 'setRemoteFilesystem' 656 | ]) 657 | ->getMock(); 658 | 659 | $event 660 | ->expects($this->once()) 661 | ->method('getProcessedUrl') 662 | ->willReturn(self::REPO_URL); 663 | 664 | $event 665 | ->expects($this->once()) 666 | ->method('getRemoteFilesystem') 667 | ->willReturn($rfs); 668 | 669 | $event 670 | ->expects($this->once()) 671 | ->method('setRemoteFilesystem') 672 | ->with($this->callback( 673 | function ($rfs) use ($config, $io, $options, $tlsDisabled) { 674 | $this->assertAttributeEquals($config, 'config', $rfs); 675 | $this->assertAttributeEquals($io, 'io', $rfs); 676 | $this->assertEquals($options, $rfs->getOptions()); 677 | $this->assertEquals($tlsDisabled, $rfs->isTlsDisabled()); 678 | return true; 679 | } 680 | )); 681 | 682 | // Call addKey 683 | $plugin = new Plugin(); 684 | $plugin->activate($composer, $io); 685 | $plugin->addKey($event); 686 | } 687 | 688 | public function testAddKeyFromENV() 689 | { 690 | // The key that should be available in the ENVIRONMENT 691 | $key = 'ENV_KEY'; 692 | 693 | // Make key available in the ENVIRONMENT 694 | putenv(self::KEY_ENV_VARIABLE . '=' . $key); 695 | 696 | // Mock a RemoteFilesystem 697 | $rfs = $this 698 | ->getMockBuilder('Composer\Util\RemoteFilesystem') 699 | ->disableOriginalConstructor() 700 | ->setMethods(['getOptions', 'isTlsDisabled']) 701 | ->getMock(); 702 | 703 | $rfs 704 | ->expects($this->once()) 705 | ->method('getOptions') 706 | ->willReturn([]); 707 | 708 | $rfs 709 | ->expects($this->once()) 710 | ->method('isTlsDisabled') 711 | ->willReturn(true); 712 | 713 | // Mock Config 714 | $config = $this 715 | ->getMockBuilder('Composer\Config') 716 | ->getMock(); 717 | 718 | // Mock Composer 719 | $composer = $this 720 | ->getMockBuilder('Composer\Composer') 721 | ->setMethods(['getConfig']) 722 | ->getMock(); 723 | 724 | $composer 725 | ->expects($this->once()) 726 | ->method('getConfig') 727 | ->willReturn($config); 728 | 729 | // Mock IOInterface 730 | $io = $this 731 | ->getMockBuilder('Composer\IO\IOInterface') 732 | ->getMock(); 733 | 734 | // Mock an Event 735 | $event = $this 736 | ->getMockBuilder('Composer\Plugin\PreFileDownloadEvent') 737 | ->disableOriginalConstructor() 738 | ->setMethods([ 739 | 'getProcessedUrl', 740 | 'getRemoteFilesystem', 741 | 'setRemoteFilesystem' 742 | ]) 743 | ->getMock(); 744 | 745 | $event 746 | ->expects($this->once()) 747 | ->method('getProcessedUrl') 748 | ->willReturn(self::REPO_URL); 749 | 750 | $event 751 | ->expects($this->once()) 752 | ->method('getRemoteFilesystem') 753 | ->willReturn($rfs); 754 | 755 | $event 756 | ->expects($this->once()) 757 | ->method('setRemoteFilesystem') 758 | ->with($this->callback( 759 | function ($rfs) use ($key) { 760 | $this->assertAttributeContains( 761 | $key, 762 | 'wpOffloadS3FileUrl', 763 | $rfs 764 | ); 765 | return true; 766 | } 767 | )); 768 | 769 | // Call addKey 770 | $plugin = new Plugin(); 771 | $plugin->activate($composer, $io); 772 | $plugin->addKey($event); 773 | } 774 | 775 | public function testAddKeyFromDotEnv() 776 | { 777 | // The key that should be available in the .env file 778 | $key = 'DOT_ENV_KEY'; 779 | 780 | // Make key available in the .env file 781 | file_put_contents( 782 | getcwd().DIRECTORY_SEPARATOR.'.env', 783 | self::KEY_ENV_VARIABLE . '=' . $key 784 | ); 785 | 786 | // Mock a RemoteFilesystem 787 | $rfs = $this 788 | ->getMockBuilder('Composer\Util\RemoteFilesystem') 789 | ->disableOriginalConstructor() 790 | ->setMethods(['getOptions', 'isTlsDisabled']) 791 | ->getMock(); 792 | 793 | $rfs 794 | ->expects($this->once()) 795 | ->method('getOptions') 796 | ->willReturn([]); 797 | 798 | $rfs 799 | ->expects($this->once()) 800 | ->method('isTlsDisabled') 801 | ->willReturn(true); 802 | 803 | // Mock Config 804 | $config = $this 805 | ->getMockBuilder('Composer\Config') 806 | ->getMock(); 807 | 808 | // Mock Composer 809 | $composer = $this 810 | ->getMockBuilder('Composer\Composer') 811 | ->setMethods(['getConfig']) 812 | ->getMock(); 813 | 814 | $composer 815 | ->expects($this->once()) 816 | ->method('getConfig') 817 | ->willReturn($config); 818 | 819 | // Mock IOInterface 820 | $io = $this 821 | ->getMockBuilder('Composer\IO\IOInterface') 822 | ->getMock(); 823 | 824 | // Mock an Event 825 | $event = $this 826 | ->getMockBuilder('Composer\Plugin\PreFileDownloadEvent') 827 | ->disableOriginalConstructor() 828 | ->setMethods([ 829 | 'getProcessedUrl', 830 | 'getRemoteFilesystem', 831 | 'setRemoteFilesystem' 832 | ]) 833 | ->getMock(); 834 | 835 | $event 836 | ->expects($this->once()) 837 | ->method('getProcessedUrl') 838 | ->willReturn(self::REPO_URL); 839 | 840 | $event 841 | ->expects($this->once()) 842 | ->method('getRemoteFilesystem') 843 | ->willReturn($rfs); 844 | 845 | $event 846 | ->expects($this->once()) 847 | ->method('setRemoteFilesystem') 848 | ->with($this->callback( 849 | function ($rfs) use ($key) { 850 | $this->assertAttributeContains( 851 | $key, 852 | 'wpOffloadS3FileUrl', 853 | $rfs 854 | ); 855 | return true; 856 | } 857 | )); 858 | 859 | // Call addKey 860 | $plugin = new Plugin(); 861 | $plugin->activate($composer, $io); 862 | $plugin->addKey($event); 863 | } 864 | 865 | public function testPreferKeyFromEnv() 866 | { 867 | // The key that should be available in the .env file 868 | $fileKey = 'DOT_ENV_KEY'; 869 | $key = 'ENV_KEY'; 870 | 871 | // Make key available in the .env file 872 | file_put_contents( 873 | getcwd().DIRECTORY_SEPARATOR.'.env', 874 | self::KEY_ENV_VARIABLE . '=' . $fileKey 875 | ); 876 | 877 | // Make key available in the ENVIRONMENT 878 | putenv(self::KEY_ENV_VARIABLE . '=' . $key); 879 | 880 | // Mock a RemoteFilesystem 881 | $rfs = $this 882 | ->getMockBuilder('Composer\Util\RemoteFilesystem') 883 | ->disableOriginalConstructor() 884 | ->setMethods(['getOptions', 'isTlsDisabled']) 885 | ->getMock(); 886 | 887 | $rfs 888 | ->expects($this->once()) 889 | ->method('getOptions') 890 | ->willReturn([]); 891 | 892 | $rfs 893 | ->expects($this->once()) 894 | ->method('isTlsDisabled') 895 | ->willReturn(true); 896 | 897 | // Mock Config 898 | $config = $this 899 | ->getMockBuilder('Composer\Config') 900 | ->getMock(); 901 | 902 | // Mock Composer 903 | $composer = $this 904 | ->getMockBuilder('Composer\Composer') 905 | ->setMethods(['getConfig']) 906 | ->getMock(); 907 | 908 | $composer 909 | ->expects($this->once()) 910 | ->method('getConfig') 911 | ->willReturn($config); 912 | 913 | // Mock IOInterface 914 | $io = $this 915 | ->getMockBuilder('Composer\IO\IOInterface') 916 | ->getMock(); 917 | 918 | // Mock an Event 919 | $event = $this 920 | ->getMockBuilder('Composer\Plugin\PreFileDownloadEvent') 921 | ->disableOriginalConstructor() 922 | ->setMethods([ 923 | 'getProcessedUrl', 924 | 'getRemoteFilesystem', 925 | 'setRemoteFilesystem' 926 | ]) 927 | ->getMock(); 928 | 929 | $event 930 | ->expects($this->once()) 931 | ->method('getProcessedUrl') 932 | ->willReturn(self::REPO_URL); 933 | 934 | $event 935 | ->expects($this->once()) 936 | ->method('getRemoteFilesystem') 937 | ->willReturn($rfs); 938 | 939 | $event 940 | ->expects($this->once()) 941 | ->method('setRemoteFilesystem') 942 | ->with($this->callback( 943 | function ($rfs) use ($key) { 944 | $this->assertAttributeContains( 945 | $key, 946 | 'wpOffloadS3FileUrl', 947 | $rfs 948 | ); 949 | return true; 950 | } 951 | )); 952 | 953 | // Call addKey 954 | $plugin = new Plugin(); 955 | $plugin->activate($composer, $io); 956 | $plugin->addKey($event); 957 | } 958 | 959 | public function testThrowExceptionWhenKeyIsMissing() 960 | { 961 | // Expect an Exception 962 | $this->setExpectedException( 963 | 'Outlandish\WPOffloadS3Installer\Exceptions\MissingKeyException', 964 | 'WP_OFFLOAD_S3_KEY' 965 | ); 966 | 967 | // Mock a RemoteFilesystem 968 | $rfs = $this 969 | ->getMockBuilder('Composer\Util\RemoteFilesystem') 970 | ->disableOriginalConstructor() 971 | ->getMock(); 972 | 973 | // Mock an Event 974 | $event = $this 975 | ->getMockBuilder('Composer\Plugin\PreFileDownloadEvent') 976 | ->disableOriginalConstructor() 977 | ->setMethods([ 978 | 'getProcessedUrl', 979 | 'getRemoteFilesystem' 980 | ]) 981 | ->getMock(); 982 | 983 | $event 984 | ->expects($this->once()) 985 | ->method('getProcessedUrl') 986 | ->willReturn(self::REPO_URL); 987 | 988 | $event 989 | ->expects($this->once()) 990 | ->method('getRemoteFilesystem') 991 | ->willReturn($rfs); 992 | 993 | // Call addKey 994 | $plugin = new Plugin(); 995 | $plugin->addKey($event); 996 | } 997 | 998 | public function testOnlyAddKeyOnWpOffloadS3Url() 999 | { 1000 | // Make key available in the ENVIRONMENT 1001 | putenv(self::KEY_ENV_VARIABLE . '=KEY'); 1002 | 1003 | // Mock an Event 1004 | $event = $this 1005 | ->getMockBuilder('Composer\Plugin\PreFileDownloadEvent') 1006 | ->disableOriginalConstructor() 1007 | ->setMethods([ 1008 | 'getProcessedUrl', 1009 | 'getRemoteFilesystem', 1010 | 'setRemoteFilesystem' 1011 | ]) 1012 | ->getMock(); 1013 | 1014 | $event 1015 | ->expects($this->once()) 1016 | ->method('getProcessedUrl') 1017 | ->willReturn('another-url'); 1018 | 1019 | $event 1020 | ->expects($this->never()) 1021 | ->method('getRemoteFilesystem'); 1022 | 1023 | $event 1024 | ->expects($this->never()) 1025 | ->method('setRemoteFilesystem'); 1026 | 1027 | // Call addKey 1028 | $plugin = new Plugin(); 1029 | $plugin->addKey($event); 1030 | } 1031 | } 1032 | --------------------------------------------------------------------------------