<NME> PackageManager.php
<BEF> <?php

namespace Bolt\Composer;

use Bolt;
use Bolt\Composer\Package\Dependency;
use Bolt\Composer\Satis\PingService;
use Bolt\Extension\ResolvedExtension;
use Bolt\Filesystem\Exception\ParseException;
use Bolt\Translation\Translator as Trans;
use Composer\CaBundle\CaBundle;
use Composer\Package\CompletePackageInterface;
use Silex\Application;

class PackageManager
{
    /** @var Application */
    protected $app;
    /** @var bool */
    protected $started = false;
    /** @var bool */
    protected $useSsl;

    /** @var array|null */
    private $json;

    /**
     * Constructor.
     *
     * @param Application $app
     */
    public function __construct(Application $app)
    {
        $this->app = $app;

        $this->setup();
    }

    /**
     * Get the stored messages.
     *
     * @return string[]
     */
    public function getMessages()
    {
        /** @var PingService $pinger */
        $pinger = $this->app['extend.ping'];

        return $pinger->getMessages()->toArray();
    }

    /**
     * Return the output from the last IO.
     *
     * @return string
     */
    public function getOutput()
    {
        return $this->app['extend.action.io']->getOutput();
    }

    /**
     * Set up function.
     *
     * - Copy/update the installer event class
     * - Update the composer.json
     * - Test connection to the server
     */
    private function setup()
    {
        if ($this->started) {
            return;
        }

        if ($this->app['extend.writeable']) {
            // Do required JSON update/set up
            try {
                $this->updateJson();
            } catch (ParseException $e) {
                $this->app['logger.flash']->danger(Trans::__('Error reading extensions/composer.json file: %ERROR%', ['%ERROR%' => $e->getMessage()]));
                $this->started = false;

                return;
            }
        }

        $this->started = true;
    }

    /**
     * Check if we can/should use SSL/TLS/HTTP2 or HTTP.
     *
     * @throws \Exception
     *
     * @return bool
     */
    public function useSsl()
    {
        if ($this->useSsl !== null) {
            return $this->useSsl;
        }

        if (!extension_loaded('openssl')) {
            return $this->useSsl = false;
        }

        if (!CaBundle::getSystemCaRootBundlePath($this->app['logger.system'])) {
            throw new \Exception('Unable to get system CA bundle, or the bundled Composer CA. Your system is badly misconfigured and there is nothing Bolt can do.');
        }

        return $this->useSsl = true;
    }

    /**
     * Check for packages that need to be installed or updated.
     *
     * @return array
     */
    public function checkPackage()
    {
        return $this->app['extend.action']['check']->execute();
    }

    /**
     * Find which packages cause the given package to be installed.
     *
     * @param string $packageName
     * @param string $constraint
     *
     * @return Dependency[]|null
     */
    public function dependsPackage($packageName, $constraint)
    {
        return $this->app['extend.action']['depends']->execute($packageName, $constraint);
    }

    /**
     * Dump fresh autoloader.
     *
     * @return int 0 on success or a positive error code on failure
     */
    public function dumpAutoload()
    {
        return $this->app['extend.action']['autoload']->execute();
    }

    /**
     * Install configured packages.
     *
     * @return int 0 on success or a positive error code on failure
     */
    public function installPackages()
    {
        return $this->app['extend.action']['install']->execute();
    }

    /**
     * Find which packages prevent the given package from being installed.
     *
     * @param string $packageName
     * @param string $constraint
     *
     * @return Dependency[]|null
     */
    public function prohibitsPackage($packageName, $constraint)
    {
        return $this->app['extend.action']['prohibits']->execute($packageName, $constraint);
    }

    /**
     * Remove packages from the root install.
     *
     * @param $packages array Indexed array of package names to remove
     *
     * @return int 0 on success or a positive error code on failure
     */
    public function removePackage(array $packages)
    {
        return $this->app['extend.action']['remove']->execute($packages);
    }

    /**
     * Require (install) packages.
     *
     * @param $packages array Associative array of package names/versions to remove
     *                        Format: ['name' => '', 'version' => '']
     *
     * @return int 0 on success or a positive error code on failure
     */
    public function requirePackage(array $packages)
    {
        return $this->app['extend.action']['require']->execute($packages);
    }

    /**
     * Search for packages.
     *
     * @param $packages array Indexed array of package names to search
     *
     * @return array List of matching packages
     */
    public function searchPackage(array $packages)
    {
        return $this->app['extend.action']['search']->execute($packages);
    }

    /**
     * Show packages.
     *
     * @param string $target
     * @param string $package
     * @param string $version
     * @param bool   $root
     *
     * @return array
     */
    public function showPackage($target, $package = '', $version = '', $root = false)
    {
        return $this->app['extend.action']['show']->execute($target, $package, $version, $root);
    }

    /**
     * Update packages in the root install.
     *
     * @param  $packages array Indexed array of package names to update
     *
     * @return int 0 on success or a positive error code on failure
     */
    public function updatePackage(array $packages)
    {
        return $this->app['extend.action']['update']->execute($packages);
    }

    /**
     * Initialise a new JSON file.
     *
     * @param string $file File to initialise
     * @param array  $data Data to be added as JSON parameter/value pairs
     */
    public function initJson($file, array $data = [])
    {
        $this->app['extend.manager.json']->init($file, $data);
    }

    /**
     * Get packages that a properly installed, pending installed and locally installed.
     *
     * @return PackageCollection
     */
    public function getAllPackages()
    {
        $collection = new PackageCollection();

        if ($this->started === false) {
            return $collection;
        }

        // Installed
        $installed = $this->app['extend.action']['show']->execute('installed');
        foreach ($installed as $composerPackage) {
            /** @var CompletePackageInterface $composerPackage */
            $composerPackage = $composerPackage['package'];
            $package = Package::createFromComposerPackage($composerPackage);
            $name = $package->getName();
            $extension = $this->app['extensions']->getResolved($name);

            // Handle non-Bolt packages
            if ($extension instanceof ResolvedExtension) {
                $title = $extension->getDisplayName();
                $descriptor = $extension->getDescriptor();
                $constraint = $descriptor ? $descriptor->getConstraint() : null;
                $readme = $this->linkReadMe($extension);
                $config = $this->linkConfig($extension);
                $valid = $extension->isValid();
                $enabled = $extension->isEnabled();
            } else {
                $title = $name;
                $constraint = null;
                $readme = null;
                $config = null;
                $valid = true;
                $enabled = true;
            }

            $package->setStatus('installed');
            $package->setTitle($title);
            $package->setReadmeLink($readme);
            $package->setConfigLink($config);
            $package->setRepositoryLink($composerPackage->getSourceUrl());
            $package->setConstraint($constraint);
            $package->setValid($valid);
            $package->setEnabled($enabled);

            $collection->add($package);
        }

        // Pending
        $requires = isset($this->json['require']) ? $this->json['require'] : [];
        foreach ($requires as $name => $version) {
            if ($collection->get($name)) {
                continue;
            }
            $package = new Package();
            $package->setStatus('pending');
            $package->setName($name);
            $package->setTitle($name);
            $package->setReadmeLink(null);
            $package->setConfigLink(null);
            $package->setVersion($version);
            $package->setType('unknown');
            $package->setDescription(Trans::__('general.phrase.not-installed-yet'));

            $collection->add($package);
        }

        return $collection;
    }

    /**
     * Return the URI for a package's readme.
     *
     * @param ResolvedExtension $extension
     *
     * @return string
     */
    private function linkReadMe(ResolvedExtension $extension)
    {
        return $this->app['url_generator']->generate('readme', [
            'extension' => $extension->getId(),
        ]);
    }

    /**
     * Return the URI for a package's config file edit window.
     *
     * @param ResolvedExtension $extension
     *
     * @return string
     */
    private function linkConfig(ResolvedExtension $extension)
    {
        $file = $this->app['filesystem']->getFile(strtolower("extensions_config://{$extension->getName()}.{$extension->getVendor()}.yml"));
        if ($file->exists()) {
            return $this->app['url_generator']->generate('fileedit', ['namespace' => $file->getMountPoint(), 'file' => $file->getPath()]);
        }

        return null;
    }

    /**
     * Set up Composer JSON file.
     */
    private function updateJson()
    {
        $this->json = $this->app['extend.manager.json']->update();
    }
}
    {
        $class = new \ReflectionClass("Bolt\\Composer\\ExtensionInstaller");
        $filename = $class->getFileName();
        copy($filename, $this->options['basedir'] . '/installer.php');
    }

    /**

<MSG> Merge pull request #2739 from rossriley/fix/double-autoload-extensions

Fix double autoloading of extension support files
<DFF> @@ -438,7 +438,7 @@ class PackageManager
     {
         $class = new \ReflectionClass("Bolt\\Composer\\ExtensionInstaller");
         $filename = $class->getFileName();
-        copy($filename, $this->options['basedir'] . '/installer.php');
+        copy($filename, $this->options['basedir'] . '/ExtensionInstaller.php');
     }
 
     /**

{'commit': 'diff --git a/src/Composer/Action/BoltExtendJson.php b/src/Composer/Action/BoltExtendJson.php\nindex 2bb81f19d..04c0771fa 100644\n--- a/src/Composer/Action/BoltExtendJson.php\n+++ b/src/Composer/Action/BoltExtendJson.php\n@@ -92,12 +92,14 @@ final class BoltExtendJson\n             \'preferred-install\' => \'dist\'\n         );\n         $json[\'provide\'][\'bolt/bolt\'] = $app[\'bolt_version\'];\n+        $json[\'extra\'] = array(\'bolt-web-path\' => $pathToWeb);\n+        $json[\'autoload\'] = array(\n+            \'psr-4\' => array(\'Bolt\\\\Composer\\\\\'=>"")\n+        );\n         $json[\'scripts\'] = array(\n             \'post-package-install\' => "Bolt\\\\Composer\\\\ExtensionInstaller::handle",\n             \'post-package-update\' => "Bolt\\\\Composer\\\\ExtensionInstaller::handle"\n         );\n-        $json[\'extra\'] = array(\'bolt-web-path\' => $pathToWeb);\n-        $json[\'autoload\'] = array(\'files\' => array(\'installer.php\'));\n \n         // Write out the file, but only if it\'s actually changed, and if it\'s writable.\n         if ($json != $jsonorig) {\ndiff --git a/src/Composer/ExtensionInstaller.php b/src/Composer/ExtensionInstaller.php\nindex 69c53074d..518bd4033 100644\n--- a/src/Composer/ExtensionInstaller.php\n+++ b/src/Composer/ExtensionInstaller.php\n@@ -1,11 +1,9 @@\n <?php\n namespace Bolt\\Composer;\n \n-use Composer\\EventDispatcher\\Event;\n-\n class ExtensionInstaller\n {\n-    public static function handle(Event $event)\n+    public static function handle($event)\n     {\n         try {\n             $installedPackage = $event->getComposer()->getPackage();\ndiff --git a/src/Composer/PackageManager.php b/src/Composer/PackageManager.php\nindex d9f0500bf..453dfed2f 100644\n--- a/src/Composer/PackageManager.php\n+++ b/src/Composer/PackageManager.php\n@@ -438,7 +438,7 @@ class PackageManager\n     {\n         $class = new \\ReflectionClass("Bolt\\\\Composer\\\\ExtensionInstaller");\n         $filename = $class->getFileName();\n-        copy($filename, $this->options[\'basedir\'] . \'/installer.php\');\n+        copy($filename, $this->options[\'basedir\'] . \'/ExtensionInstaller.php\');\n     }\n \n     /**\ndiff --git a/tests/Extensions/StatServiceTest.php b/tests/Extensions/StatServiceTest.php\nindex c0d2b1222..7a71bb86a 100644\n--- a/tests/Extensions/StatServiceTest.php\n+++ b/tests/Extensions/StatServiceTest.php\n@@ -16,13 +16,12 @@ class StatServiceTest extends BoltUnitTest\n     public function testSetup()\n     {\n         $app = $this->getApp();\n-        $stat = $this->getMock(\'Bolt\\Extensions\\StatService\', array(\'recordInstall\'), array($app));\n-\n         $stat = $this->getMock(\'Bolt\\Extensions\\StatService\', array(\'recordInstall\'), array($app));\n         $stat->expects($this->once())\n             ->method(\'recordInstall\')\n             ->with(\'mytest\', \'1.0.0\');\n-        $stat->recordInstall("mytest",\'1.0.0\');\n+\n+        $response = $stat->recordInstall("mytest",\'1.0.0\');\n     }\n \n }\n', 'commit_subject': 'Merge pull request #2739 from rossriley/fix/double-autoload-extensions', 'repo_name': 'bolt/bolt', 'committer': {'name': 'Gawain Lynch', 'email': 'f745ec5336bcd096400627fc09f6ec7139408726@gmail.com', 'time_sec': '1423332115', 'tz_offset': '0', 'date': {'seconds': '1423332115'}}, 'commit_tree': 'e6f1180361e6446b75f024edfec28280baceaabd', 'commit_message': 'Merge pull request #2739 from rossriley/fix/double-autoload-extensions\n\nFix double autoloading of extension support files', 'language_name': 'PHP', 'license': 'mit', 'hunks': '@@ -438,7 +438,7 @@ class PackageManager\n     {\n         $class = new \\ReflectionClass("Bolt\\\\Composer\\\\ExtensionInstaller");\n         $filename = $class->getFileName();\n-        copy($filename, $this->options[\'basedir\'] . \'/installer.php\');\n+        copy($filename, $this->options[\'basedir\'] . \'/ExtensionInstaller.php\');\n     }\n \n     /**\n', 'addition_count': 1, 'deletion_count': 1, 'src_file': 'a/src/Composer/PackageManager.php', 'tgt_file': 'b/src/Composer/PackageManager.php', 'file_extension': '.php', 'before_file': '<?php\n\nnamespace Bolt\\Composer;\n\nuse Bolt;\nuse Bolt\\Composer\\Package\\Dependency;\nuse Bolt\\Composer\\Satis\\PingService;\nuse Bolt\\Extension\\ResolvedExtension;\nuse Bolt\\Filesystem\\Exception\\ParseException;\nuse Bolt\\Translation\\Translator as Trans;\nuse Composer\\CaBundle\\CaBundle;\nuse Composer\\Package\\CompletePackageInterface;\nuse Silex\\Application;\n\nclass PackageManager\n{\n    /** @var Application */\n    protected $app;\n    /** @var bool */\n    protected $started = false;\n    /** @var bool */\n    protected $useSsl;\n\n    /** @var array|null */\n    private $json;\n\n    /**\n     * Constructor.\n     *\n     * @param Application $app\n     */\n    public function __construct(Application $app)\n    {\n        $this->app = $app;\n\n        $this->setup();\n    }\n\n    /**\n     * Get the stored messages.\n     *\n     * @return string[]\n     */\n    public function getMessages()\n    {\n        /** @var PingService $pinger */\n        $pinger = $this->app[\'extend.ping\'];\n\n        return $pinger->getMessages()->toArray();\n    }\n\n    /**\n     * Return the output from the last IO.\n     *\n     * @return string\n     */\n    public function getOutput()\n    {\n        return $this->app[\'extend.action.io\']->getOutput();\n    }\n\n    /**\n     * Set up function.\n     *\n     * - Copy/update the installer event class\n     * - Update the composer.json\n     * - Test connection to the server\n     */\n    private function setup()\n    {\n        if ($this->started) {\n            return;\n        }\n\n        if ($this->app[\'extend.writeable\']) {\n            // Do required JSON update/set up\n            try {\n                $this->updateJson();\n            } catch (ParseException $e) {\n                $this->app[\'logger.flash\']->danger(Trans::__(\'Error reading extensions/composer.json file: %ERROR%\', [\'%ERROR%\' => $e->getMessage()]));\n                $this->started = false;\n\n                return;\n            }\n        }\n\n        $this->started = true;\n    }\n\n    /**\n     * Check if we can/should use SSL/TLS/HTTP2 or HTTP.\n     *\n     * @throws \\Exception\n     *\n     * @return bool\n     */\n    public function useSsl()\n    {\n        if ($this->useSsl !== null) {\n            return $this->useSsl;\n        }\n\n        if (!extension_loaded(\'openssl\')) {\n            return $this->useSsl = false;\n        }\n\n        if (!CaBundle::getSystemCaRootBundlePath($this->app[\'logger.system\'])) {\n            throw new \\Exception(\'Unable to get system CA bundle, or the bundled Composer CA. Your system is badly misconfigured and there is nothing Bolt can do.\');\n        }\n\n        return $this->useSsl = true;\n    }\n\n    /**\n     * Check for packages that need to be installed or updated.\n     *\n     * @return array\n     */\n    public function checkPackage()\n    {\n        return $this->app[\'extend.action\'][\'check\']->execute();\n    }\n\n    /**\n     * Find which packages cause the given package to be installed.\n     *\n     * @param string $packageName\n     * @param string $constraint\n     *\n     * @return Dependency[]|null\n     */\n    public function dependsPackage($packageName, $constraint)\n    {\n        return $this->app[\'extend.action\'][\'depends\']->execute($packageName, $constraint);\n    }\n\n    /**\n     * Dump fresh autoloader.\n     *\n     * @return int 0 on success or a positive error code on failure\n     */\n    public function dumpAutoload()\n    {\n        return $this->app[\'extend.action\'][\'autoload\']->execute();\n    }\n\n    /**\n     * Install configured packages.\n     *\n     * @return int 0 on success or a positive error code on failure\n     */\n    public function installPackages()\n    {\n        return $this->app[\'extend.action\'][\'install\']->execute();\n    }\n\n    /**\n     * Find which packages prevent the given package from being installed.\n     *\n     * @param string $packageName\n     * @param string $constraint\n     *\n     * @return Dependency[]|null\n     */\n    public function prohibitsPackage($packageName, $constraint)\n    {\n        return $this->app[\'extend.action\'][\'prohibits\']->execute($packageName, $constraint);\n    }\n\n    /**\n     * Remove packages from the root install.\n     *\n     * @param $packages array Indexed array of package names to remove\n     *\n     * @return int 0 on success or a positive error code on failure\n     */\n    public function removePackage(array $packages)\n    {\n        return $this->app[\'extend.action\'][\'remove\']->execute($packages);\n    }\n\n    /**\n     * Require (install) packages.\n     *\n     * @param $packages array Associative array of package names/versions to remove\n     *                        Format: [\'name\' => \'\', \'version\' => \'\']\n     *\n     * @return int 0 on success or a positive error code on failure\n     */\n    public function requirePackage(array $packages)\n    {\n        return $this->app[\'extend.action\'][\'require\']->execute($packages);\n    }\n\n    /**\n     * Search for packages.\n     *\n     * @param $packages array Indexed array of package names to search\n     *\n     * @return array List of matching packages\n     */\n    public function searchPackage(array $packages)\n    {\n        return $this->app[\'extend.action\'][\'search\']->execute($packages);\n    }\n\n    /**\n     * Show packages.\n     *\n     * @param string $target\n     * @param string $package\n     * @param string $version\n     * @param bool   $root\n     *\n     * @return array\n     */\n    public function showPackage($target, $package = \'\', $version = \'\', $root = false)\n    {\n        return $this->app[\'extend.action\'][\'show\']->execute($target, $package, $version, $root);\n    }\n\n    /**\n     * Update packages in the root install.\n     *\n     * @param  $packages array Indexed array of package names to update\n     *\n     * @return int 0 on success or a positive error code on failure\n     */\n    public function updatePackage(array $packages)\n    {\n        return $this->app[\'extend.action\'][\'update\']->execute($packages);\n    }\n\n    /**\n     * Initialise a new JSON file.\n     *\n     * @param string $file File to initialise\n     * @param array  $data Data to be added as JSON parameter/value pairs\n     */\n    public function initJson($file, array $data = [])\n    {\n        $this->app[\'extend.manager.json\']->init($file, $data);\n    }\n\n    /**\n     * Get packages that a properly installed, pending installed and locally installed.\n     *\n     * @return PackageCollection\n     */\n    public function getAllPackages()\n    {\n        $collection = new PackageCollection();\n\n        if ($this->started === false) {\n            return $collection;\n        }\n\n        // Installed\n        $installed = $this->app[\'extend.action\'][\'show\']->execute(\'installed\');\n        foreach ($installed as $composerPackage) {\n            /** @var CompletePackageInterface $composerPackage */\n            $composerPackage = $composerPackage[\'package\'];\n            $package = Package::createFromComposerPackage($composerPackage);\n            $name = $package->getName();\n            $extension = $this->app[\'extensions\']->getResolved($name);\n\n            // Handle non-Bolt packages\n            if ($extension instanceof ResolvedExtension) {\n                $title = $extension->getDisplayName();\n                $descriptor = $extension->getDescriptor();\n                $constraint = $descriptor ? $descriptor->getConstraint() : null;\n                $readme = $this->linkReadMe($extension);\n                $config = $this->linkConfig($extension);\n                $valid = $extension->isValid();\n                $enabled = $extension->isEnabled();\n            } else {\n                $title = $name;\n                $constraint = null;\n                $readme = null;\n                $config = null;\n                $valid = true;\n                $enabled = true;\n            }\n\n            $package->setStatus(\'installed\');\n            $package->setTitle($title);\n            $package->setReadmeLink($readme);\n            $package->setConfigLink($config);\n            $package->setRepositoryLink($composerPackage->getSourceUrl());\n            $package->setConstraint($constraint);\n            $package->setValid($valid);\n            $package->setEnabled($enabled);\n\n            $collection->add($package);\n        }\n\n        // Pending\n        $requires = isset($this->json[\'require\']) ? $this->json[\'require\'] : [];\n        foreach ($requires as $name => $version) {\n            if ($collection->get($name)) {\n                continue;\n            }\n            $package = new Package();\n            $package->setStatus(\'pending\');\n            $package->setName($name);\n            $package->setTitle($name);\n            $package->setReadmeLink(null);\n            $package->setConfigLink(null);\n            $package->setVersion($version);\n            $package->setType(\'unknown\');\n            $package->setDescription(Trans::__(\'general.phrase.not-installed-yet\'));\n\n            $collection->add($package);\n        }\n\n        return $collection;\n    }\n\n    /**\n     * Return the URI for a package\'s readme.\n     *\n     * @param ResolvedExtension $extension\n     *\n     * @return string\n     */\n    private function linkReadMe(ResolvedExtension $extension)\n    {\n        return $this->app[\'url_generator\']->generate(\'readme\', [\n            \'extension\' => $extension->getId(),\n        ]);\n    }\n\n    /**\n     * Return the URI for a package\'s config file edit window.\n     *\n     * @param ResolvedExtension $extension\n     *\n     * @return string\n     */\n    private function linkConfig(ResolvedExtension $extension)\n    {\n        $file = $this->app[\'filesystem\']->getFile(strtolower("extensions_config://{$extension->getName()}.{$extension->getVendor()}.yml"));\n        if ($file->exists()) {\n            return $this->app[\'url_generator\']->generate(\'fileedit\', [\'namespace\' => $file->getMountPoint(), \'file\' => $file->getPath()]);\n        }\n\n        return null;\n    }\n\n    /**\n     * Set up Composer JSON file.\n     */\n    private function updateJson()\n    {\n        $this->json = $this->app[\'extend.manager.json\']->update();\n    }\n}\n    {\n        $class = new \\ReflectionClass("Bolt\\\\Composer\\\\ExtensionInstaller");\n        $filename = $class->getFileName();\n        copy($filename, $this->options[\'basedir\'] . \'/installer.php\');\n    }\n\n    /**\n', 'message': 'Merge pull request #2739 from rossriley/fix/double-autoload-extensions\n\nFix double autoloading of extension support files', 'author_name': 'Gawain Lynch', 'lang': 'php'}