protected function extract($file, $path) { $processError = null; // Try to use unrar on *nix if (!Platform::isWindows()) { $command = 'unrar x ' . ProcessExecutor::escape($file) . ' ' . ProcessExecutor::escape($path) . ' >/dev/null && chmod -R u+w ' . ProcessExecutor::escape($path); if (0 === $this->process->execute($command, $ignoredOutput)) { return; } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); } if (!class_exists('RarArchive')) { // php.ini path is added to the error message to help users find the correct file $iniMessage = IniHelper::getMessage(); $error = "Could not decompress the archive, enable the PHP rar extension or install unrar.\n" . $iniMessage . "\n" . $processError; if (!Platform::isWindows()) { $error = "Could not decompress the archive, enable the PHP rar extension.\n" . $iniMessage; } throw new \RuntimeException($error); } $rarArchive = RarArchive::open($file); if (false === $rarArchive) { throw new \UnexpectedValueException('Could not open RAR archive: ' . $file); } $entries = $rarArchive->getEntries(); if (false === $entries) { throw new \RuntimeException('Could not retrieve RAR archive entries'); } foreach ($entries as $entry) { if (false === $entry->extract($path)) { throw new \RuntimeException('Could not extract entry'); } } $rarArchive->close(); }
/** * runs a process on the commandline * * @param string $command the command to execute * @param mixed $output the output will be written into this var if passed by ref * if a callable is passed it will be used as output handler * @param string $cwd the working directory * @return int statuscode */ public function execute($command, &$output = null, $cwd = null) { if ($this->io && $this->io->isDebug()) { $safeCommand = preg_replace_callback('{://(?P<user>[^:/\\s]+):(?P<password>[^@\\s/]+)@}i', function ($m) { if (preg_match('{^[a-f0-9]{12,}$}', $m['user'])) { return '://***:***@'; } return '://' . $m['user'] . ':***@'; }, $command); $this->io->writeError('Executing command (' . ($cwd ?: 'CWD') . '): ' . $safeCommand); } // make sure that null translate to the proper directory in case the dir is a symlink // and we call a git command, because msysgit does not handle symlinks properly if (null === $cwd && Platform::isWindows() && false !== strpos($command, 'git') && getcwd()) { $cwd = realpath(getcwd()); } $this->captureOutput = count(func_get_args()) > 1; $this->errorOutput = null; $process = new Process($command, $cwd, null, null, static::getTimeout()); $callback = is_callable($output) ? $output : array($this, 'outputHandler'); $process->run($callback); if ($this->captureOutput && !is_callable($output)) { $output = $process->getOutput(); } $this->errorOutput = $process->getErrorOutput(); return $process->getExitCode(); }
private function getCmd($cmd) { if (Platform::isWindows()) { return strtr($cmd, "'", '"'); } return $cmd; }
protected function extract($file, $path) { $processError = null; if (self::$hasSystemUnzip && !(class_exists('ZipArchive') && Platform::isWindows())) { $command = 'unzip ' . ProcessExecutor::escape($file) . ' -d ' . ProcessExecutor::escape($path); if (!Platform::isWindows()) { $command .= ' && chmod -R u+w ' . ProcessExecutor::escape($path); } try { if (0 === $this->process->execute($command, $ignoredOutput)) { return; } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); } catch (\Exception $e) { $processError = 'Failed to execute ' . $command . "\n\n" . $e->getMessage(); } if (!class_exists('ZipArchive')) { throw new \RuntimeException($processError); } } $zipArchive = new ZipArchive(); if (true !== ($retval = $zipArchive->open($file))) { throw new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file) . "\n" . $processError), $retval); } if (true !== $zipArchive->extractTo($path)) { throw new \RuntimeException(rtrim("There was an error extracting the ZIP file, it is either corrupted or using an invalid format.\n" . $processError)); } $zipArchive->close(); }
/** * {@inheritdoc} */ public function download(PackageInterface $package, $path) { $url = $package->getDistUrl(); $realUrl = realpath($url); if (false === $realUrl || !file_exists($realUrl) || !is_dir($realUrl)) { throw new \RuntimeException(sprintf('Source path "%s" is not found for package %s', $url, $package->getName())); } if (strpos(realpath($path) . DIRECTORY_SEPARATOR, $realUrl . DIRECTORY_SEPARATOR) === 0) { throw new \RuntimeException(sprintf('Package %s cannot install to "%s" inside its source at "%s"', $package->getName(), realpath($path), $realUrl)); } $fileSystem = new Filesystem(); $this->filesystem->removeDirectory($path); $this->io->writeError(sprintf(' - Installing <info>%s</info> (<comment>%s</comment>)', $package->getName(), $package->getFullPrettyVersion())); try { if (Platform::isWindows()) { // Implement symlinks as NTFS junctions on Windows $this->filesystem->junction($realUrl, $path); $this->io->writeError(sprintf(' Junctioned from %s', $url)); } else { $shortestPath = $this->filesystem->findShortestPath($path, $realUrl); $fileSystem->symlink($shortestPath, $path); $this->io->writeError(sprintf(' Symlinked from %s', $url)); } } catch (IOException $e) { $fileSystem->mirror($realUrl, $path); $this->io->writeError(sprintf(' Mirrored from %s', $url)); } $this->io->writeError(''); }
public function setUp() { if (Platform::isWindows()) { $this->markTestSkipped('Skip test on Windows'); } $this->testDir = $this->getUniqueTmpDirectory(); }
private function extractUsingExt($file, $targetFilepath) { $archiveFile = gzopen($file, 'rb'); $targetFile = fopen($targetFilepath, 'wb'); while ($string = gzread($archiveFile, 4096)) { fwrite($targetFile, $string, Platform::strlen($string)); } gzclose($archiveFile); fclose($targetFile); }
protected function installCode(PackageInterface $package) { parent::installCode($package); $isWindows = Platform::isWindows(); $php_bin = $this->binDir . ($isWindows ? '/composer-php.bat' : '/composer-php'); if (!$isWindows) { $php_bin = '/usr/bin/env ' . $php_bin; } $installPath = $this->getInstallPath($package); $vars = array('os' => $isWindows ? 'windows' : 'linux', 'php_bin' => $php_bin, 'pear_php' => $installPath, 'php_dir' => $installPath, 'bin_dir' => $installPath . '/bin', 'data_dir' => $installPath . '/data', 'version' => $package->getPrettyVersion()); $packageArchive = $this->getInstallPath($package) . '/' . pathinfo($package->getDistUrl(), PATHINFO_BASENAME); $pearExtractor = new PearPackageExtractor($packageArchive); $pearExtractor->extractTo($this->getInstallPath($package), array('php' => '/', 'script' => '/bin', 'data' => '/data'), $vars); $this->io->writeError(' Cleaning up', true, IOInterface::VERBOSE); $this->filesystem->unlink($packageArchive); }
public function installBinaries(PackageInterface $package, $installPath, $warnOnOverwrite = true) { $binaries = $this->getBinaries($package); if (!$binaries) { return; } foreach ($binaries as $bin) { $binPath = $installPath . '/' . $bin; if (!file_exists($binPath)) { $this->io->writeError(' <warning>Skipped installation of bin ' . $bin . ' for package ' . $package->getName() . ': file not found in package</warning>'); continue; } // in case a custom installer returned a relative path for the // $package, we can now safely turn it into a absolute path (as we // already checked the binary's existence). The following helpers // will require absolute paths to work properly. $binPath = realpath($binPath); $this->initializeBinDir(); $link = $this->binDir . '/' . basename($bin); if (file_exists($link)) { if (is_link($link)) { // likely leftover from a previous install, make sure // that the target is still executable in case this // is a fresh install of the vendor. Silencer::call('chmod', $link, 0777 & ~umask()); } if ($warnOnOverwrite) { $this->io->writeError(' Skipped installation of bin ' . $bin . ' for package ' . $package->getName() . ': name conflicts with an existing file'); } continue; } if ($this->binCompat === "auto") { if (Platform::isWindows()) { $this->installFullBinaries($binPath, $link, $bin, $package); } else { $this->installSymlinkBinaries($binPath, $link); } } elseif ($this->binCompat === "full") { $this->installFullBinaries($binPath, $link, $bin, $package); } Silencer::call('chmod', $link, 0777 & ~umask()); } }
protected function extract($file, $path) { $targetFilepath = $path . DIRECTORY_SEPARATOR . basename(substr($file, 0, -3)); // Try to use gunzip on *nix if (!Platform::isWindows()) { $command = 'gzip -cd ' . ProcessExecutor::escape($file) . ' > ' . ProcessExecutor::escape($targetFilepath); if (0 === $this->process->execute($command, $ignoredOutput)) { return; } if (extension_loaded('zlib')) { // Fallback to using the PHP extension. $this->extractUsingExt($file, $targetFilepath); return; } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); throw new \RuntimeException($processError); } // Windows version of PHP has built-in support of gzip functions $this->extractUsingExt($file, $targetFilepath); }
protected function extract($file, $path) { $processError = null; if (self::$hasSystemUnzip) { $command = 'unzip ' . ProcessExecutor::escape($file) . ' -d ' . ProcessExecutor::escape($path); if (!Platform::isWindows()) { $command .= ' && chmod -R u+w ' . ProcessExecutor::escape($path); } try { if (0 === $this->process->execute($command, $ignoredOutput)) { return; } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); } catch (\Exception $e) { $processError = 'Failed to execute ' . $command . "\n\n" . $e->getMessage(); } } if (!class_exists('ZipArchive')) { // php.ini path is added to the error message to help users find the correct file $iniPath = php_ini_loaded_file(); if ($iniPath) { $iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath; } else { $iniMessage = 'A php.ini file does not exist. You will have to create one.'; } $error = "Could not decompress the archive, enable the PHP zip extension or install unzip.\n" . $iniMessage . ($processError ? "\n" . $processError : ''); throw new \RuntimeException($error); } $zipArchive = new ZipArchive(); if (true !== ($retval = $zipArchive->open($file))) { throw new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file) . "\n" . $processError), $retval); } if (true !== $zipArchive->extractTo($path)) { $this->io->writeError("<warn>As there is no 'unzip' command installed zip files are being unpacked using the PHP zip extension.</warn>"); $this->io->writeError("<warn>This may cause invalid reports of corrupted archives. Installing 'unzip' may remediate them.</warn>"); throw new \RuntimeException("There was an error extracting the ZIP file, it is either corrupted or using an invalid format"); } $zipArchive->close(); }
protected function extract($file, $path) { $processError = null; // try to use unzip on *nix if (!Platform::isWindows()) { $command = 'unzip ' . ProcessExecutor::escape($file) . ' -d ' . ProcessExecutor::escape($path) . ' && chmod -R u+w ' . ProcessExecutor::escape($path); try { if (0 === $this->process->execute($command, $ignoredOutput)) { return; } $processError = 'Failed to execute ' . $command . "\n\n" . $this->process->getErrorOutput(); } catch (\Exception $e) { $processError = 'Failed to execute ' . $command . "\n\n" . $e->getMessage(); } } if (!class_exists('ZipArchive')) { // php.ini path is added to the error message to help users find the correct file $iniPath = php_ini_loaded_file(); if ($iniPath) { $iniMessage = 'The php.ini used by your command-line PHP is: ' . $iniPath; } else { $iniMessage = 'A php.ini file does not exist. You will have to create one.'; } $error = "Could not decompress the archive, enable the PHP zip extension or install unzip.\n" . $iniMessage . "\n" . $processError; if (!Platform::isWindows()) { $error = "Could not decompress the archive, enable the PHP zip extension.\n" . $iniMessage; } throw new \RuntimeException($error); } $zipArchive = new ZipArchive(); if (true !== ($retval = $zipArchive->open($file))) { throw new \UnexpectedValueException(rtrim($this->getErrorMessage($retval, $file) . "\n" . $processError), $retval); } if (true !== $zipArchive->extractTo($path)) { throw new \RuntimeException("There was an error extracting the ZIP file. Corrupt file?"); } $zipArchive->close(); }
private function getCmd($cmd) { return Platform::isWindows() ? strtr($cmd, "'", '"') : $cmd; }
/** * {@inheritDoc} */ private function hintCommonErrors($exception) { $io = $this->getIO(); Silencer::suppress(); try { $composer = $this->getComposer(false, true); if ($composer) { $config = $composer->getConfig(); $minSpaceFree = 1024 * 1024; if (($df = disk_free_space($dir = $config->get('home'))) !== false && $df < $minSpaceFree || ($df = disk_free_space($dir = $config->get('vendor-dir'))) !== false && $df < $minSpaceFree || ($df = disk_free_space($dir = sys_get_temp_dir())) !== false && $df < $minSpaceFree) { $io->writeError('<error>The disk hosting ' . $dir . ' is full, this may be the cause of the following exception</error>', true, IOInterface::QUIET); } } } catch (\Exception $e) { } Silencer::restore(); if (Platform::isWindows() && false !== strpos($exception->getMessage(), 'The system cannot find the path specified')) { $io->writeError('<error>The following exception may be caused by a stale entry in your cmd.exe AutoRun</error>', true, IOInterface::QUIET); $io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#-the-system-cannot-find-the-path-specified-windows- for details</error>', true, IOInterface::QUIET); } if (false !== strpos($exception->getMessage(), 'fork failed - Cannot allocate memory')) { $io->writeError('<error>The following exception is caused by a lack of memory and not having swap configured</error>', true, IOInterface::QUIET); $io->writeError('<error>Check https://getcomposer.org/doc/articles/troubleshooting.md#proc-open-fork-failed-errors for details</error>', true, IOInterface::QUIET); } }
/** * Removes a Windows NTFS junction. * * @param string $junction * @return bool */ public function removeJunction($junction) { if (!Platform::isWindows()) { return false; } $junction = rtrim(str_replace('/', DIRECTORY_SEPARATOR, $junction), DIRECTORY_SEPARATOR); if (!$this->isJunction($junction)) { throw new IOException(sprintf('%s is not a junction and thus cannot be removed as one', $junction)); } $cmd = sprintf('rmdir /S /Q %s', ProcessExecutor::escape($junction)); clearstatcache(true, $junction); return $this->getProcess()->execute($cmd, $output) === 0; }
private function winCompat($cmd) { if (Platform::isWindows()) { $cmd = str_replace('cd ', 'cd /D ', $cmd); $cmd = str_replace('composerPath', getcwd().'/composerPath', $cmd); return str_replace('""', '', strtr($cmd, "'", '"')); } return $cmd; }
/** * Returns a setting * * @param string $key * @param int $flags Options (see class constants) * @throws \RuntimeException * @return mixed */ public function get($key, $flags = 0) { switch ($key) { case 'vendor-dir': case 'bin-dir': case 'process-timeout': case 'data-dir': case 'cache-dir': case 'cache-files-dir': case 'cache-repo-dir': case 'cache-vcs-dir': case 'cafile': case 'capath': // convert foo-bar to COMPOSER_FOO_BAR and check if it exists since it overrides the local config $env = 'COMPOSER_' . strtoupper(strtr($key, '-', '_')); $val = rtrim($this->process($this->getComposerEnv($env) ?: $this->config[$key], $flags), '/\\'); $val = Platform::expandPath($val); if (substr($key, -4) !== '-dir') { return $val; } return ($flags & self::RELATIVE_PATHS) == self::RELATIVE_PATHS ? $val : $this->realpath($val); case 'cache-ttl': return (int) $this->config[$key]; case 'cache-files-maxsize': if (!preg_match('/^\\s*([0-9.]+)\\s*(?:([kmg])(?:i?b)?)?\\s*$/i', $this->config[$key], $matches)) { throw new \RuntimeException("Could not parse the value of 'cache-files-maxsize': {$this->config[$key]}"); } $size = $matches[1]; if (isset($matches[2])) { switch (strtolower($matches[2])) { case 'g': $size *= 1024; // intentional fallthrough // intentional fallthrough case 'm': $size *= 1024; // intentional fallthrough // intentional fallthrough case 'k': $size *= 1024; break; } } return $size; case 'cache-files-ttl': if (isset($this->config[$key])) { return (int) $this->config[$key]; } return (int) $this->config['cache-ttl']; case 'home': $val = preg_replace('#^(\\$HOME|~)(/|$)#', rtrim(getenv('HOME') ?: getenv('USERPROFILE'), '/\\') . '/', $this->config[$key]); return rtrim($this->process($val, $flags), '/\\'); case 'bin-compat': $value = $this->getComposerEnv('COMPOSER_BIN_COMPAT') ?: $this->config[$key]; if (!in_array($value, array('auto', 'full'))) { throw new \RuntimeException("Invalid value for 'bin-compat': {$value}. Expected auto, full"); } return $value; case 'discard-changes': if ($env = $this->getComposerEnv('COMPOSER_DISCARD_CHANGES')) { if (!in_array($env, array('stash', 'true', 'false', '1', '0'), true)) { throw new \RuntimeException("Invalid value for COMPOSER_DISCARD_CHANGES: {$env}. Expected 1, 0, true, false or stash"); } if ('stash' === $env) { return 'stash'; } // convert string value to bool return $env !== 'false' && (bool) $env; } if (!in_array($this->config[$key], array(true, false, 'stash'), true)) { throw new \RuntimeException("Invalid value for 'discard-changes': {$this->config[$key]}. Expected true, false or stash"); } return $this->config[$key]; case 'github-protocols': $protos = $this->config['github-protocols']; if ($this->config['secure-http'] && false !== ($index = array_search('git', $protos))) { unset($protos[$index]); } if (reset($protos) === 'http') { throw new \RuntimeException('The http protocol for github is not available anymore, update your config\'s github-protocols to use "https", "git" or "ssh"'); } return $protos; case 'disable-tls': return $this->config[$key] !== 'false' && (bool) $this->config[$key]; case 'secure-http': return $this->config[$key] !== 'false' && (bool) $this->config[$key]; default: if (!isset($this->config[$key])) { return null; } return $this->process($this->config[$key], $flags); } }
/** * opens a url in your system default browser * * @param string $url */ private function openBrowser($url) { $url = ProcessExecutor::escape($url); if (Platform::isWindows()) { return passthru('start "web" explorer "' . $url . '"'); } passthru('which xdg-open', $linux); passthru('which open', $osx); if (0 === $linux) { passthru('xdg-open ' . $url); } elseif (0 === $osx) { passthru('open ' . $url); } else { $this->getIO()->writeError('no suitable browser opening command found, open yourself: ' . $url); } }
/** * @param Event $event * @param bool $lazy * * @throws LintErrorException * @throws RequiresDependencyException */ public static function yaml(Event $event, $lazy = false) { $bin = self::getOption('lint-yaml-bin', $event); $includes = self::getOption('lint-yaml-include', $event); $excludes = self::getOption('lint-yaml-exclude', $event); $logPrepend = self::getOption('lint-yaml-log-prepend', $event); if (Platform::isWindows()) { self::lintFinder($event, $includes, $excludes, '*.yml', $bin, $logPrepend, $lazy); } else { self::lintLinuxFind($event, $includes, $excludes, '*.yml', $bin, $logPrepend, $lazy); } }
/** * Initializes path repository. * * @param array $repoConfig * @param IOInterface $io * @param Config $config */ public function __construct(array $repoConfig, IOInterface $io, Config $config) { if (!isset($repoConfig['url'])) { throw new \RuntimeException('You must specify the `url` configuration for the path repository'); } $this->loader = new ArrayLoader(null, true); $this->url = Platform::expandPath($repoConfig['url']); $this->process = new ProcessExecutor($io); $this->versionGuesser = new VersionGuesser($config, $this->process, new VersionParser()); $this->repoConfig = $repoConfig; $this->options = isset($repoConfig['options']) ? $repoConfig['options'] : array(); parent::__construct(); }
/** * {@inheritDoc} */ public function remove(PackageInterface $package, $path) { /** * For junctions don't blindly rely on Filesystem::removeDirectory as it may be overzealous. If a process * inadvertently locks the file the removal will fail, but it would fall back to recursive delete which * is disastrous within a junction. So in that case we have no other real choice but to fail hard. */ if (Platform::isWindows() && $this->filesystem->isJunction($path)) { $this->io->writeError(" - Removing junction for <info>" . $package->getName() . "</info> (<comment>" . $package->getFullPrettyVersion() . "</comment>)"); if (!$this->filesystem->removeJunction($path)) { $this->io->writeError("<warn>Could not remove junction at " . $path . " - is another process locking it?</warn>"); throw new \RuntimeException('Could not reliably remove junction for package ' . $package->getName()); } } else { parent::remove($package, $path); } }
public static function create($repoConfig, $port, $path, ProcessExecutor $process, IOInterface $io) { return new Perforce($repoConfig, $port, $path, $process, Platform::isWindows(), $io); }
/** * delete symbolic link implementation (commonly known as "unlink()") * * symbolic links on windows which link to directories need rmdir instead of unlink * * @param string $path * * @return bool */ private function unlinkImplementation($path) { if (Platform::isWindows() && is_dir($path) && is_link($path)) { return rmdir($path); } return unlink($path); }
public function testWindows() { // Compare 2 common tests for Windows to the built-in Windows test $this->assertEquals('\\' === DIRECTORY_SEPARATOR, Platform::isWindows()); $this->assertEquals(defined('PHP_WINDOWS_VERSION_MAJOR'), Platform::isWindows()); }
/** * @param string $home * @return string */ protected static function getDataDir($home) { $homeEnv = getenv('COMPOSER_HOME'); if ($homeEnv) { return $homeEnv; } if (Platform::isWindows()) { return strtr($home, '\\', '/'); } $userDir = self::getUserDir(); if ($home !== $userDir . '/.composer' && self::useXdg()) { $xdgData = getenv('XDG_DATA_HOME') ?: $userDir . '/.local/share'; return $xdgData . '/composer'; } return $home; }
protected function execute(InputInterface $input, OutputInterface $output) { $this->versionParser = new VersionParser(); if ($input->getOption('tree')) { $this->initStyles($output); } $composer = $this->getComposer(false); $io = $this->getIO(); if ($input->getOption('installed')) { $io->writeError('<warning>You are using the deprecated option "installed". Only installed packages are shown by default now. The --all option can be used to show all packages.</warning>'); } if ($input->getOption('outdated')) { $input->setOption('latest', true); } if ($input->getOption('direct') && ($input->getOption('all') || $input->getOption('available') || $input->getOption('platform'))) { $io->writeError('The --direct (-D) option is not usable in combination with --all, --platform (-p) or --available (-a)'); return 1; } if ($input->getOption('tree') && ($input->getOption('all') || $input->getOption('available'))) { $io->writeError('The --tree (-t) option is not usable in combination with --all or --available (-a)'); return 1; } // init repos $platformOverrides = array(); if ($composer) { $platformOverrides = $composer->getConfig()->get('platform') ?: array(); } $platformRepo = new PlatformRepository(array(), $platformOverrides); $phpVersion = $platformRepo->findPackage('php', '*')->getVersion(); if ($input->getOption('self')) { $package = $this->getComposer()->getPackage(); $repos = $installedRepo = new ArrayRepository(array($package)); } elseif ($input->getOption('platform')) { $repos = $installedRepo = $platformRepo; } elseif ($input->getOption('available')) { $installedRepo = $platformRepo; if ($composer) { $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); } else { $defaultRepos = RepositoryFactory::defaultRepos($io); $repos = new CompositeRepository($defaultRepos); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); } } elseif ($input->getOption('all') && $composer) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository(array($localRepo, $platformRepo)); $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories())); } elseif ($input->getOption('all')) { $defaultRepos = RepositoryFactory::defaultRepos($io); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); $installedRepo = $platformRepo; $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos)); } else { $repos = $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository(); } if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); } if ($input->getOption('latest') && null === $composer) { $io->writeError('No composer.json found in the current directory, disabling "latest" option'); $input->setOption('latest', false); } $packageFilter = $input->getArgument('package'); // show single package or single version if ($packageFilter && false === strpos($packageFilter, '*') || !empty($package)) { if (empty($package)) { list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version')); if (!$package) { throw new \InvalidArgumentException('Package ' . $input->getArgument('package') . ' not found'); } } else { $versions = array($package->getPrettyVersion() => $package->getVersion()); } if ($input->getOption('tree')) { $this->displayPackageTree($package, $installedRepo, $repos); } else { $latestPackage = null; if ($input->getOption('latest')) { $latestPackage = $this->findLatestPackage($package, $composer, $phpVersion); } $this->printMeta($package, $versions, $installedRepo, $latestPackage); $this->printLinks($package, 'requires'); $this->printLinks($package, 'devRequires', 'requires (dev)'); if ($package->getSuggests()) { $io->write("\n<info>suggests</info>"); foreach ($package->getSuggests() as $suggested => $reason) { $io->write($suggested . ' <comment>' . $reason . '</comment>'); } } $this->printLinks($package, 'provides'); $this->printLinks($package, 'conflicts'); $this->printLinks($package, 'replaces'); } return; } // show tree view if requested if ($input->getOption('tree')) { $rootRequires = $this->getRootRequires(); foreach ($installedRepo->getPackages() as $package) { if (in_array($package->getName(), $rootRequires, true)) { $this->displayPackageTree($package, $installedRepo, $repos); } } return 0; } if ($repos instanceof CompositeRepository) { $repos = $repos->getRepositories(); } elseif (!is_array($repos)) { $repos = array($repos); } // list packages $packages = array(); if (null !== $packageFilter) { $packageFilter = '{^' . str_replace('\\*', '.*?', preg_quote($packageFilter)) . '$}i'; } $packageListFilter = array(); if ($input->getOption('direct')) { $packageListFilter = $this->getRootRequires(); } foreach ($repos as $repo) { if ($repo === $platformRepo) { $type = '<info>platform</info>:'; } elseif ($repo === $installedRepo || $installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true)) { $type = '<info>installed</info>:'; } else { $type = '<comment>available</comment>:'; } if ($repo instanceof ComposerRepository && $repo->hasProviders()) { foreach ($repo->getProviderNames() as $name) { if (!$packageFilter || preg_match($packageFilter, $name)) { $packages[$type][$name] = $name; } } } else { foreach ($repo->getPackages() as $package) { if (!isset($packages[$type][$package->getName()]) || !is_object($packages[$type][$package->getName()]) || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<')) { if (!$packageFilter || preg_match($packageFilter, $package->getName())) { if (!$packageListFilter || in_array($package->getName(), $packageListFilter, true)) { $packages[$type][$package->getName()] = $package; } } } } } } $showAllTypes = $input->getOption('all'); $showLatest = $input->getOption('latest'); $showMinorOnly = $input->getOption('minor-only'); $indent = $showAllTypes ? ' ' : ''; $latestPackages = array(); foreach (array('<info>platform</info>:' => true, '<comment>available</comment>:' => false, '<info>installed</info>:' => true) as $type => $showVersion) { if (isset($packages[$type])) { if ($showAllTypes) { $io->write($type); } ksort($packages[$type]); $nameLength = $versionLength = $latestLength = 0; foreach ($packages[$type] as $package) { if (is_object($package)) { $nameLength = max($nameLength, strlen($package->getPrettyName())); if ($showVersion) { $versionLength = max($versionLength, strlen($package->getFullPrettyVersion())); if ($showLatest) { $latestPackage = $this->findLatestPackage($package, $composer, $phpVersion, $showMinorOnly); if ($latestPackage === false) { continue; } $latestPackages[$package->getPrettyName()] = $latestPackage; $latestLength = max($latestLength, strlen($latestPackage->getFullPrettyVersion())); } } } else { $nameLength = max($nameLength, $package); } } list($width) = $this->getApplication()->getTerminalDimensions(); if (null === $width) { // In case the width is not detected, we're probably running the command // outside of a real terminal, use space without a limit $width = PHP_INT_MAX; } if (Platform::isWindows()) { $width--; } if ($input->getOption('path') && null === $composer) { $io->writeError('No composer.json found in the current directory, disabling "path" option'); $input->setOption('path', false); } $writePath = !$input->getOption('name-only') && $input->getOption('path'); $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion && $nameLength + $versionLength + 3 <= $width; $writeLatest = $writeVersion && $showLatest && $nameLength + $versionLength + $latestLength + 3 <= $width; $writeDescription = !$input->getOption('name-only') && !$input->getOption('path') && $nameLength + $versionLength + $latestLength + 24 <= $width; foreach ($packages[$type] as $package) { if (is_object($package)) { $latestPackackage = null; if ($showLatest && isset($latestPackages[$package->getPrettyName()])) { $latestPackackage = $latestPackages[$package->getPrettyName()]; } if ($input->getOption('outdated') && $latestPackackage && $latestPackackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && !$latestPackackage->isAbandoned()) { continue; } $io->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false); if ($writeVersion) { $io->write(' ' . str_pad($package->getFullPrettyVersion(), $versionLength, ' '), false); } if ($writeLatest && $latestPackackage) { $latestVersion = $latestPackackage->getFullPrettyVersion(); $style = $this->getVersionStyle($latestPackackage, $package); $io->write(' <' . $style . '>' . str_pad($latestVersion, $latestLength, ' ') . '</' . $style . '>', false); } if ($writeDescription) { $description = strtok($package->getDescription(), "\r\n"); $remaining = $width - $nameLength - $versionLength - 4; if ($writeLatest) { $remaining -= $latestLength; } if (strlen($description) > $remaining) { $description = substr($description, 0, $remaining - 3) . '...'; } $io->write(' ' . $description, false); } if ($writePath) { $path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"); $io->write(' ' . $path, false); } if ($latestPackackage && $latestPackackage->isAbandoned()) { $replacement = is_string($latestPackackage->getReplacementPackage()) ? 'Use ' . $latestPackackage->getReplacementPackage() . ' instead' : 'No replacement was suggested'; $io->writeError(''); $io->writeError(sprintf("<warning>Package %s is abandoned, you should avoid using it. %s.</warning>", $package->getPrettyName(), $replacement), false); } } else { $io->write($indent . $package, false); } $io->write(''); } if ($showAllTypes) { $io->write(''); } } } }
protected function normalizePath($path) { if (Platform::isWindows() && strlen($path) > 0) { $basePath = $path; $removed = array(); while (!is_dir($basePath) && $basePath !== '\\') { array_unshift($removed, basename($basePath)); $basePath = dirname($basePath); } if ($basePath === '\\') { return $path; } $path = rtrim(realpath($basePath) . '/' . implode('/', $removed), '/'); } return $path; }
/** * Get file content or copy action. * * @param string $originUrl The origin URL * @param string $fileUrl The file URL * @param array $additionalOptions context options * @param string $fileName the local filename * @param bool $progress Display the progression * * @throws TransportException|\Exception * @throws TransportException When the file could not be downloaded * * @return bool|string */ protected function get($originUrl, $fileUrl, $additionalOptions = array(), $fileName = null, $progress = true) { if (strpos($originUrl, '.github.com') === strlen($originUrl) - 11) { $originUrl = 'github.com'; } $this->scheme = parse_url($fileUrl, PHP_URL_SCHEME); $this->bytesMax = 0; $this->originUrl = $originUrl; $this->fileUrl = $fileUrl; $this->fileName = $fileName; $this->progress = $progress; $this->lastProgress = null; $this->retryAuthFailure = true; $this->lastHeaders = array(); $this->redirects = 1; // The first request counts. // capture username/password from URL if there is one if (preg_match('{^https?://(.+):(.+)@([^/]+)}i', $fileUrl, $match)) { $this->io->setAuthentication($originUrl, urldecode($match[1]), urldecode($match[2])); } $tempAdditionalOptions = $additionalOptions; if (isset($tempAdditionalOptions['retry-auth-failure'])) { $this->retryAuthFailure = (bool) $tempAdditionalOptions['retry-auth-failure']; unset($tempAdditionalOptions['retry-auth-failure']); } $isRedirect = false; if (isset($tempAdditionalOptions['redirects'])) { $this->redirects = $tempAdditionalOptions['redirects']; $isRedirect = true; unset($tempAdditionalOptions['redirects']); } $options = $this->getOptionsForUrl($originUrl, $tempAdditionalOptions); unset($tempAdditionalOptions); $userlandFollow = isset($options['http']['follow_location']) && !$options['http']['follow_location']; $origFileUrl = $fileUrl; if (isset($options['github-token'])) { // only add the access_token if it is actually a github URL (in case we were redirected to S3) if (preg_match('{^https?://([a-z0-9-]+\\.)*github\\.com/}', $fileUrl)) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token=' . $options['github-token']; } unset($options['github-token']); } if (isset($options['gitlab-token'])) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token=' . $options['gitlab-token']; unset($options['gitlab-token']); } if (isset($options['bitbucket-token'])) { $fileUrl .= (false === strpos($fileUrl, '?') ? '?' : '&') . 'access_token=' . $options['bitbucket-token']; unset($options['bitbucket-token']); } if (isset($options['http'])) { $options['http']['ignore_errors'] = true; } if ($this->degradedMode && substr($fileUrl, 0, 21) === 'http://packagist.org/') { // access packagist using the resolved IPv4 instead of the hostname to force IPv4 protocol $fileUrl = 'http://' . gethostbyname('packagist.org') . substr($fileUrl, 20); $degradedPackagist = true; } $ctx = StreamContextFactory::getContext($fileUrl, $options, array('notification' => array($this, 'callbackGet'))); $actualContextOptions = stream_context_get_options($ctx); $usingProxy = !empty($actualContextOptions['http']['proxy']) ? ' using proxy ' . $actualContextOptions['http']['proxy'] : ''; $this->io->writeError((substr($origFileUrl, 0, 4) === 'http' ? 'Downloading ' : 'Reading ') . $origFileUrl . $usingProxy, true, IOInterface::DEBUG); unset($origFileUrl, $actualContextOptions); // Check for secure HTTP, but allow insecure Packagist calls to $hashed providers as file integrity is verified with sha256 if ((substr($fileUrl, 0, 23) !== 'http://packagist.org/p/' || false === strpos($fileUrl, '$') && false === strpos($fileUrl, '%24')) && empty($degradedPackagist) && $this->config) { $this->config->prohibitUrlByConfig($fileUrl, $this->io); } if ($this->progress && !$isRedirect) { $this->io->writeError(" Downloading: <comment>Connecting...</comment>", false); } $errorMessage = ''; $errorCode = 0; $result = false; set_error_handler(function ($code, $msg) use(&$errorMessage) { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_get_contents\\(.*?\\): }', '', $msg); }); try { $result = file_get_contents($fileUrl, false, $ctx); $contentLength = !empty($http_response_header[0]) ? $this->findHeaderValue($http_response_header, 'content-length') : null; if ($contentLength && Platform::strlen($result) < $contentLength) { // alas, this is not possible via the stream callback because STREAM_NOTIFY_COMPLETED is documented, but not implemented anywhere in PHP throw new TransportException('Content-Length mismatch'); } if (PHP_VERSION_ID < 50600 && !empty($options['ssl']['peer_fingerprint'])) { // Emulate fingerprint validation on PHP < 5.6 $params = stream_context_get_params($ctx); $expectedPeerFingerprint = $options['ssl']['peer_fingerprint']; $peerFingerprint = TlsHelper::getCertificateFingerprint($params['options']['ssl']['peer_certificate']); // Constant time compare??! if ($expectedPeerFingerprint !== $peerFingerprint) { throw new TransportException('Peer fingerprint did not match'); } } } catch (\Exception $e) { if ($e instanceof TransportException && !empty($http_response_header[0])) { $e->setHeaders($http_response_header); $e->setStatusCode($this->findStatusCode($http_response_header)); } if ($e instanceof TransportException && $result !== false) { $e->setResponse($result); } $result = false; } if ($errorMessage && !ini_get('allow_url_fopen')) { $errorMessage = 'allow_url_fopen must be enabled in php.ini (' . $errorMessage . ')'; } restore_error_handler(); if (isset($e) && !$this->retry) { if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { $this->degradedMode = true; $this->io->writeError(array('<error>' . $e->getMessage() . '</error>', '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>')); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } throw $e; } $statusCode = null; $contentType = null; if (!empty($http_response_header[0])) { $statusCode = $this->findStatusCode($http_response_header); $contentType = $this->findHeaderValue($http_response_header, 'content-type'); } // check for bitbucket login page asking to authenticate if ($originUrl === 'bitbucket.org' && substr($fileUrl, -4) === '.zip' && preg_match('{^text/html\\b}i', $contentType)) { $result = false; if ($this->retryAuthFailure) { $this->promptAuthAndRetry(401); } } // handle 3xx redirects for php<5.6, 304 Not Modified is excluded $hasFollowedRedirect = false; if ($userlandFollow && $statusCode >= 300 && $statusCode <= 399 && $statusCode !== 304 && $this->redirects < $this->maxRedirects) { $hasFollowedRedirect = true; $result = $this->handleRedirect($http_response_header, $additionalOptions, $result); } // fail 4xx and 5xx responses and capture the response if ($statusCode && $statusCode >= 400 && $statusCode <= 599) { if (!$this->retry) { if ($this->progress && !$this->retry && !$isRedirect) { $this->io->overwriteError(" Downloading: <error>Failed</error>"); } $e = new TransportException('The "' . $this->fileUrl . '" file could not be downloaded (' . $http_response_header[0] . ')', $statusCode); $e->setHeaders($http_response_header); $e->setResponse($result); $e->setStatusCode($statusCode); throw $e; } $result = false; } if ($this->progress && !$this->retry && !$isRedirect) { $this->io->overwriteError(" Downloading: " . ($result === false ? '<error>Failed</error>' : '<comment>100%</comment>')); } // decode gzip if ($result && extension_loaded('zlib') && substr($fileUrl, 0, 4) === 'http' && !$hasFollowedRedirect) { $decode = 'gzip' === strtolower($this->findHeaderValue($http_response_header, 'content-encoding')); if ($decode) { try { if (PHP_VERSION_ID >= 50400) { $result = zlib_decode($result); } else { // work around issue with gzuncompress & co that do not work with all gzip checksums $result = file_get_contents('compress.zlib://data:application/octet-stream;base64,' . base64_encode($result)); } if (!$result) { throw new TransportException('Failed to decode zlib stream'); } } catch (\Exception $e) { if ($this->degradedMode) { throw $e; } $this->degradedMode = true; $this->io->writeError(array('<error>Failed to decode response: ' . $e->getMessage() . '</error>', '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>')); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } } } // handle copy command if download was successful if (false !== $result && null !== $fileName && !$isRedirect) { if ('' === $result) { throw new TransportException('"' . $this->fileUrl . '" appears broken, and returned an empty 200 response'); } $errorMessage = ''; set_error_handler(function ($code, $msg) use(&$errorMessage) { if ($errorMessage) { $errorMessage .= "\n"; } $errorMessage .= preg_replace('{^file_put_contents\\(.*?\\): }', '', $msg); }); $result = (bool) file_put_contents($fileName, $result); restore_error_handler(); if (false === $result) { throw new TransportException('The "' . $this->fileUrl . '" file could not be written to ' . $fileName . ': ' . $errorMessage); } } // Handle SSL cert match issues if (false === $result && false !== strpos($errorMessage, 'Peer certificate') && PHP_VERSION_ID < 50600) { // Certificate name error, PHP doesn't support subjectAltName on PHP < 5.6 // The procedure to handle sAN for older PHP's is: // // 1. Open socket to remote server and fetch certificate (disabling peer // validation because PHP errors without giving up the certificate.) // // 2. Verifying the domain in the URL against the names in the sAN field. // If there is a match record the authority [host/port], certificate // common name, and certificate fingerprint. // // 3. Retry the original request but changing the CN_match parameter to // the common name extracted from the certificate in step 2. // // 4. To prevent any attempt at being hoodwinked by switching the // certificate between steps 2 and 3 the fingerprint of the certificate // presented in step 3 is compared against the one recorded in step 2. if (CaBundle::isOpensslParseSafe()) { $certDetails = $this->getCertificateCnAndFp($this->fileUrl, $options); if ($certDetails) { $this->peerCertificateMap[$this->getUrlAuthority($this->fileUrl)] = $certDetails; $this->retry = true; } } else { $this->io->writeError(sprintf('<error>Your version of PHP, %s, is affected by CVE-2013-6420 and cannot safely perform certificate validation, we strongly suggest you upgrade.</error>', PHP_VERSION)); } } if ($this->retry) { $this->retry = false; $result = $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); if ($this->storeAuth && $this->config) { $authHelper = new AuthHelper($this->io, $this->config); $authHelper->storeAuth($this->originUrl, $this->storeAuth); $this->storeAuth = false; } return $result; } if (false === $result) { $e = new TransportException('The "' . $this->fileUrl . '" file could not be downloaded: ' . $errorMessage, $errorCode); if (!empty($http_response_header[0])) { $e->setHeaders($http_response_header); } if (!$this->degradedMode && false !== strpos($e->getMessage(), 'Operation timed out')) { $this->degradedMode = true; $this->io->writeError(array('<error>' . $e->getMessage() . '</error>', '<error>Retrying with degraded mode, check https://getcomposer.org/doc/articles/troubleshooting.md#degraded-mode for more info</error>')); return $this->get($this->originUrl, $this->fileUrl, $additionalOptions, $this->fileName, $this->progress); } throw $e; } if (!empty($http_response_header[0])) { $this->lastHeaders = $http_response_header; } return $result; }
/** * {@inheritDoc} */ protected function execute(InputInterface $input, OutputInterface $output) { // Open file in editor if ($input->getOption('editor')) { $editor = escapeshellcmd(getenv('EDITOR')); if (!$editor) { if (Platform::isWindows()) { $editor = 'notepad'; } else { foreach (array('editor', 'vim', 'vi', 'nano', 'pico', 'ed') as $candidate) { if (exec('which ' . $candidate)) { $editor = $candidate; break; } } } } $file = $input->getOption('auth') ? $this->authConfigFile->getPath() : $this->configFile->getPath(); system($editor . ' ' . $file . (Platform::isWindows() ? '' : ' > `tty`')); return 0; } if (!$input->getOption('global')) { $this->config->merge($this->configFile->read()); $this->config->merge(array('config' => $this->authConfigFile->exists() ? $this->authConfigFile->read() : array())); } // List the configuration of the file settings if ($input->getOption('list')) { $this->listConfiguration($this->config->all(), $this->config->raw(), $output); return 0; } $settingKey = $input->getArgument('setting-key'); if (!$settingKey) { return 0; } // If the user enters in a config variable, parse it and save to file if (array() !== $input->getArgument('setting-value') && $input->getOption('unset')) { throw new \RuntimeException('You can not combine a setting value with --unset'); } // show the value if no value is provided if (array() === $input->getArgument('setting-value') && !$input->getOption('unset')) { $data = $this->config->all(); if (preg_match('/^repos?(?:itories)?(?:\\.(.+))?/', $settingKey, $matches)) { if (empty($matches[1])) { $value = isset($data['repositories']) ? $data['repositories'] : array(); } else { if (!isset($data['repositories'][$matches[1]])) { throw new \InvalidArgumentException('There is no ' . $matches[1] . ' repository defined'); } $value = $data['repositories'][$matches[1]]; } } elseif (strpos($settingKey, '.')) { $bits = explode('.', $settingKey); $data = $data['config']; $match = false; foreach ($bits as $bit) { $key = isset($key) ? $key . '.' . $bit : $bit; $match = false; if (isset($data[$key])) { $match = true; $data = $data[$key]; unset($key); } } if (!$match) { throw new \RuntimeException($settingKey . ' is not defined.'); } $value = $data; } elseif (isset($data['config'][$settingKey])) { $value = $this->config->get($settingKey, $input->getOption('absolute') ? 0 : Config::RELATIVE_PATHS); } else { throw new \RuntimeException($settingKey . ' is not defined'); } if (is_array($value)) { $value = json_encode($value); } $this->getIO()->write($value); return 0; } $values = $input->getArgument('setting-value'); // what the user is trying to add/change $booleanValidator = function ($val) { return in_array($val, array('true', 'false', '1', '0'), true); }; $booleanNormalizer = function ($val) { return $val !== 'false' && (bool) $val; }; // handle config values $uniqueConfigValues = array('process-timeout' => array('is_numeric', 'intval'), 'use-include-path' => array($booleanValidator, $booleanNormalizer), 'preferred-install' => array(function ($val) { return in_array($val, array('auto', 'source', 'dist'), true); }, function ($val) { return $val; }), 'store-auths' => array(function ($val) { return in_array($val, array('true', 'false', 'prompt'), true); }, function ($val) { if ('prompt' === $val) { return 'prompt'; } return $val !== 'false' && (bool) $val; }), 'notify-on-install' => array($booleanValidator, $booleanNormalizer), 'vendor-dir' => array('is_string', function ($val) { return $val; }), 'bin-dir' => array('is_string', function ($val) { return $val; }), 'archive-dir' => array('is_string', function ($val) { return $val; }), 'archive-format' => array('is_string', function ($val) { return $val; }), 'data-dir' => array('is_string', function ($val) { return $val; }), 'cache-dir' => array('is_string', function ($val) { return $val; }), 'cache-files-dir' => array('is_string', function ($val) { return $val; }), 'cache-repo-dir' => array('is_string', function ($val) { return $val; }), 'cache-vcs-dir' => array('is_string', function ($val) { return $val; }), 'cache-ttl' => array('is_numeric', 'intval'), 'cache-files-ttl' => array('is_numeric', 'intval'), 'cache-files-maxsize' => array(function ($val) { return preg_match('/^\\s*([0-9.]+)\\s*(?:([kmg])(?:i?b)?)?\\s*$/i', $val) > 0; }, function ($val) { return $val; }), 'bin-compat' => array(function ($val) { return in_array($val, array('auto', 'full')); }, function ($val) { return $val; }), 'discard-changes' => array(function ($val) { return in_array($val, array('stash', 'true', 'false', '1', '0'), true); }, function ($val) { if ('stash' === $val) { return 'stash'; } return $val !== 'false' && (bool) $val; }), 'autoloader-suffix' => array('is_string', function ($val) { return $val === 'null' ? null : $val; }), 'sort-packages' => array($booleanValidator, $booleanNormalizer), 'optimize-autoloader' => array($booleanValidator, $booleanNormalizer), 'classmap-authoritative' => array($booleanValidator, $booleanNormalizer), 'prepend-autoloader' => array($booleanValidator, $booleanNormalizer), 'disable-tls' => array($booleanValidator, $booleanNormalizer), 'secure-http' => array($booleanValidator, $booleanNormalizer), 'cafile' => array(function ($val) { return file_exists($val) && is_readable($val); }, function ($val) { return $val === 'null' ? null : $val; }), 'capath' => array(function ($val) { return is_dir($val) && is_readable($val); }, function ($val) { return $val === 'null' ? null : $val; }), 'github-expose-hostname' => array($booleanValidator, $booleanNormalizer)); $multiConfigValues = array('github-protocols' => array(function ($vals) { if (!is_array($vals)) { return 'array expected'; } foreach ($vals as $val) { if (!in_array($val, array('git', 'https', 'ssh'))) { return 'valid protocols include: git, https, ssh'; } } return true; }, function ($vals) { return $vals; }), 'github-domains' => array(function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, function ($vals) { return $vals; }), 'gitlab-domains' => array(function ($vals) { if (!is_array($vals)) { return 'array expected'; } return true; }, function ($vals) { return $vals; })); foreach ($uniqueConfigValues as $name => $callbacks) { if ($settingKey === $name) { if ($input->getOption('unset')) { return $this->configSource->removeConfigSetting($settingKey); } list($validator, $normalizer) = $callbacks; if (1 !== count($values)) { throw new \RuntimeException('You can only pass one value. Example: php composer.phar config process-timeout 300'); } if (true !== ($validation = $validator($values[0]))) { throw new \RuntimeException(sprintf('"%s" is an invalid value' . ($validation ? ' (' . $validation . ')' : ''), $values[0])); } return $this->configSource->addConfigSetting($settingKey, $normalizer($values[0])); } } foreach ($multiConfigValues as $name => $callbacks) { if ($settingKey === $name) { if ($input->getOption('unset')) { return $this->configSource->removeConfigSetting($settingKey); } list($validator, $normalizer) = $callbacks; if (true !== ($validation = $validator($values))) { throw new \RuntimeException(sprintf('%s is an invalid value' . ($validation ? ' (' . $validation . ')' : ''), json_encode($values))); } return $this->configSource->addConfigSetting($settingKey, $normalizer($values)); } } // handle repositories if (preg_match('/^repos?(?:itories)?\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { return $this->configSource->removeRepository($matches[1]); } if (2 === count($values)) { return $this->configSource->addRepository($matches[1], array('type' => $values[0], 'url' => $values[1])); } if (1 === count($values)) { $value = strtolower($values[0]); if (true === $booleanValidator($value)) { if (false === $booleanNormalizer($value)) { return $this->configSource->addRepository($matches[1], false); } } else { $value = JsonFile::parseJson($values[0]); return $this->configSource->addRepository($matches[1], $value); } } throw new \RuntimeException('You must pass the type and a url. Example: php composer.phar config repositories.foo vcs https://bar.com'); } // handle platform if (preg_match('/^platform\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { return $this->configSource->removeConfigSetting($settingKey); } return $this->configSource->addConfigSetting($settingKey, $values[0]); } // handle github-oauth if (preg_match('/^(github-oauth|gitlab-oauth|http-basic)\\.(.+)/', $settingKey, $matches)) { if ($input->getOption('unset')) { $this->authConfigSource->removeConfigSetting($matches[1] . '.' . $matches[2]); $this->configSource->removeConfigSetting($matches[1] . '.' . $matches[2]); return; } if ($matches[1] === 'github-oauth' || $matches[1] === 'gitlab-oauth') { if (1 !== count($values)) { throw new \RuntimeException('Too many arguments, expected only one token'); } $this->configSource->removeConfigSetting($matches[1] . '.' . $matches[2]); $this->authConfigSource->addConfigSetting($matches[1] . '.' . $matches[2], $values[0]); } elseif ($matches[1] === 'http-basic') { if (2 !== count($values)) { throw new \RuntimeException('Expected two arguments (username, password), got ' . count($values)); } $this->configSource->removeConfigSetting($matches[1] . '.' . $matches[2]); $this->authConfigSource->addConfigSetting($matches[1] . '.' . $matches[2], array('username' => $values[0], 'password' => $values[1])); } return; } throw new \InvalidArgumentException('Setting ' . $settingKey . ' does not exist or is not supported by this command'); }
protected function execute(InputInterface $input, OutputInterface $output) { $this->versionParser = new VersionParser(); if ($input->getOption('tree')) { $this->initStyles($output); } $composer = $this->getComposer(false); $io = $this->getIO(); if ($input->getOption('tree') && !$input->getOption('installed')) { $io->writeError('The --tree (-t) option is only usable in combination with --installed (-i) or by passing a single package name to show, assuming -i'); $input->setOption('installed', true); } // init repos $platformOverrides = array(); if ($composer) { $platformOverrides = $composer->getConfig()->get('platform') ?: array(); } $platformRepo = new PlatformRepository(array(), $platformOverrides); if ($input->getOption('self')) { $package = $this->getComposer()->getPackage(); $repos = $installedRepo = new ArrayRepository(array($package)); } elseif ($input->getOption('platform')) { $repos = $installedRepo = $platformRepo; } elseif ($input->getOption('installed')) { $repos = $installedRepo = $this->getComposer()->getRepositoryManager()->getLocalRepository(); } elseif ($input->getOption('available')) { $installedRepo = $platformRepo; if ($composer) { $repos = new CompositeRepository($composer->getRepositoryManager()->getRepositories()); } else { $defaultRepos = Factory::createDefaultRepositories($io); $repos = new CompositeRepository($defaultRepos); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); } } elseif ($composer) { $localRepo = $composer->getRepositoryManager()->getLocalRepository(); $installedRepo = new CompositeRepository(array($localRepo, $platformRepo)); $repos = new CompositeRepository(array_merge(array($installedRepo), $composer->getRepositoryManager()->getRepositories())); } else { $defaultRepos = Factory::createDefaultRepositories($io); $io->writeError('No composer.json found in the current directory, showing available packages from ' . implode(', ', array_keys($defaultRepos))); $installedRepo = $platformRepo; $repos = new CompositeRepository(array_merge(array($installedRepo), $defaultRepos)); } if ($composer) { $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'show', $input, $output); $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent); } // show single package or single version if ($input->getArgument('package') || !empty($package)) { $versions = array(); if (empty($package)) { list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version')); if (!$package) { throw new \InvalidArgumentException('Package ' . $input->getArgument('package') . ' not found'); } } else { $versions = array($package->getPrettyVersion() => $package->getVersion()); } if ($input->getOption('tree')) { $this->displayPackageTree($package, $installedRepo, $repos, $output); } else { $this->printMeta($package, $versions, $installedRepo, $repos); $this->printLinks($package, 'requires'); $this->printLinks($package, 'devRequires', 'requires (dev)'); if ($package->getSuggests()) { $io->write("\n<info>suggests</info>"); foreach ($package->getSuggests() as $suggested => $reason) { $io->write($suggested . ' <comment>' . $reason . '</comment>'); } } $this->printLinks($package, 'provides'); $this->printLinks($package, 'conflicts'); $this->printLinks($package, 'replaces'); } return; } // show tree view if requested if ($input->getOption('tree')) { $rootPackage = $this->getComposer()->getPackage(); $rootRequires = array_map('strtolower', array_keys(array_merge($rootPackage->getRequires(), $rootPackage->getDevRequires()))); foreach ($installedRepo->getPackages() as $package) { if (in_array($package->getName(), $rootRequires, true)) { $this->displayPackageTree($package, $installedRepo, $repos, $output); } } return 0; } if ($repos instanceof CompositeRepository) { $repos = $repos->getRepositories(); } elseif (!is_array($repos)) { $repos = array($repos); } // list packages $packages = array(); foreach ($repos as $repo) { if ($repo === $platformRepo) { $type = '<info>platform</info>:'; } elseif ($repo === $installedRepo || $installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true)) { $type = '<info>installed</info>:'; } else { $type = '<comment>available</comment>:'; } if ($repo instanceof ComposerRepository && $repo->hasProviders()) { foreach ($repo->getProviderNames() as $name) { $packages[$type][$name] = $name; } } else { foreach ($repo->getPackages() as $package) { if (!isset($packages[$type][$package->getName()]) || !is_object($packages[$type][$package->getName()]) || version_compare($packages[$type][$package->getName()]->getVersion(), $package->getVersion(), '<')) { $packages[$type][$package->getName()] = $package; } } } } $showAllTypes = !$input->getOption('platform') && !$input->getOption('installed') && !$input->getOption('available'); $indent = $showAllTypes ? ' ' : ''; foreach (array('<info>platform</info>:' => true, '<comment>available</comment>:' => false, '<info>installed</info>:' => true) as $type => $showVersion) { if (isset($packages[$type])) { if ($showAllTypes) { $io->write($type); } ksort($packages[$type]); $nameLength = $versionLength = 0; foreach ($packages[$type] as $package) { if (is_object($package)) { $nameLength = max($nameLength, strlen($package->getPrettyName())); $versionLength = max($versionLength, strlen($package->getFullPrettyVersion())); } else { $nameLength = max($nameLength, $package); } } list($width) = $this->getApplication()->getTerminalDimensions(); if (null === $width) { // In case the width is not detected, we're probably running the command // outside of a real terminal, use space without a limit $width = PHP_INT_MAX; } if (Platform::isWindows()) { $width--; } if ($input->getOption('path') && null === $composer) { $io->writeError('No composer.json found in the current directory, disabling "path" option'); $input->setOption('path', false); } $writePath = !$input->getOption('name-only') && $input->getOption('path'); $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion && $nameLength + $versionLength + 3 <= $width; $writeDescription = !$input->getOption('name-only') && !$input->getOption('path') && $nameLength + ($showVersion ? $versionLength : 0) + 24 <= $width; foreach ($packages[$type] as $package) { if (is_object($package)) { $io->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false); if ($writeVersion) { $io->write(' ' . str_pad($package->getFullPrettyVersion(), $versionLength, ' '), false); } if ($writeDescription) { $description = strtok($package->getDescription(), "\r\n"); $remaining = $width - $nameLength - $versionLength - 4; if (strlen($description) > $remaining) { $description = substr($description, 0, $remaining - 3) . '...'; } $io->write(' ' . $description, false); } if ($writePath) { $path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"); $io->write(' ' . $path, false); } } else { $io->write($indent . $package, false); } $io->write(''); } if ($showAllTypes) { $io->write(''); } } } }