File: //var/www/payments-gateway/vendor/symfony/flex/src/Configurator/AddLinesConfigurator.php
<?php
namespace Symfony\Flex\Configurator;
use Composer\IO\IOInterface;
use Symfony\Flex\Lock;
use Symfony\Flex\Recipe;
use Symfony\Flex\Update\RecipeUpdate;
/**
* @author Kevin Bond <kevinbond@gmail.com>
* @author Ryan Weaver <ryan@symfonycasts.com>
*/
class AddLinesConfigurator extends AbstractConfigurator
{
private const POSITION_TOP = 'top';
private const POSITION_BOTTOM = 'bottom';
private const POSITION_AFTER_TARGET = 'after_target';
private const VALID_POSITIONS = [
self::POSITION_TOP,
self::POSITION_BOTTOM,
self::POSITION_AFTER_TARGET,
];
/**
* Holds file contents for files that have been loaded.
* This allows us to "change" the contents of a file multiple
* times before we actually write it out.
*
* @var string[]
*/
private $fileContents = [];
public function configure(Recipe $recipe, $config, Lock $lock, array $options = []): void
{
$this->fileContents = [];
$this->executeConfigure($recipe, $config);
foreach ($this->fileContents as $file => $contents) {
$this->write(\sprintf('[add-lines] Patching file "%s"', $this->relativize($file)));
file_put_contents($file, $contents);
}
}
public function unconfigure(Recipe $recipe, $config, Lock $lock): void
{
$this->fileContents = [];
$this->executeUnconfigure($recipe, $config);
foreach ($this->fileContents as $file => $change) {
$this->write(\sprintf('[add-lines] Reverting file "%s"', $this->relativize($file)));
file_put_contents($file, $change);
}
}
public function update(RecipeUpdate $recipeUpdate, array $originalConfig, array $newConfig): void
{
// manually check for "requires", as unconfigure ignores it
$originalConfig = array_filter($originalConfig, function ($item) {
return !isset($item['requires']) || $this->isPackageInstalled($item['requires']);
});
// reset the file content cache
$this->fileContents = [];
$this->executeUnconfigure($recipeUpdate->getOriginalRecipe(), $originalConfig);
$this->executeConfigure($recipeUpdate->getNewRecipe(), $newConfig);
$newFiles = [];
$originalFiles = [];
foreach ($this->fileContents as $file => $contents) {
// set the original file to the current contents
$originalFiles[$this->relativize($file)] = file_get_contents($file);
// and the new file where the old recipe was unconfigured, and the new configured
$newFiles[$this->relativize($file)] = $contents;
}
$recipeUpdate->addOriginalFiles($originalFiles);
$recipeUpdate->addNewFiles($newFiles);
}
public function executeConfigure(Recipe $recipe, $config): void
{
foreach ($config as $patch) {
if (!isset($patch['file'])) {
$this->write(\sprintf('The "file" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
continue;
}
if (isset($patch['requires']) && !$this->isPackageInstalled($patch['requires'])) {
continue;
}
if (!isset($patch['content'])) {
$this->write(\sprintf('The "content" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
continue;
}
$content = $patch['content'];
$file = $this->path->concatenate([$this->options->get('root-dir'), $this->options->expandTargetDir($patch['file'])]);
$warnIfMissing = isset($patch['warn_if_missing']) && $patch['warn_if_missing'];
if (!is_file($file)) {
$this->write([
\sprintf('Could not add lines to file <info>%s</info> as it does not exist. Missing lines:', $patch['file']),
'<comment>"""</comment>',
$content,
'<comment>"""</comment>',
'',
], $warnIfMissing ? IOInterface::NORMAL : IOInterface::VERBOSE);
continue;
}
if (!isset($patch['position'])) {
$this->write(\sprintf('The "position" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
continue;
}
$position = $patch['position'];
if (!\in_array($position, self::VALID_POSITIONS, true)) {
$this->write(\sprintf('The "position" key must be one of "%s" for the "add-lines" configurator for recipe "%s". Skipping', implode('", "', self::VALID_POSITIONS), $recipe->getName()));
continue;
}
if (self::POSITION_AFTER_TARGET === $position && !isset($patch['target'])) {
$this->write(\sprintf('The "target" key is required when "position" is "%s" for the "add-lines" configurator for recipe "%s". Skipping', self::POSITION_AFTER_TARGET, $recipe->getName()));
continue;
}
$target = isset($patch['target']) ? $patch['target'] : null;
$newContents = $this->getPatchedContents($file, $content, $position, $target, $warnIfMissing);
$this->fileContents[$file] = $newContents;
}
}
public function executeUnconfigure(Recipe $recipe, $config): void
{
foreach ($config as $patch) {
if (!isset($patch['file'])) {
$this->write(\sprintf('The "file" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
continue;
}
// Ignore "requires": the target packages may have just become uninstalled.
// Checking for a "content" match is enough.
$file = $this->path->concatenate([$this->options->get('root-dir'), $this->options->expandTargetDir($patch['file'])]);
if (!is_file($file)) {
continue;
}
if (!isset($patch['content'])) {
$this->write(\sprintf('The "content" key is required for the "add-lines" configurator for recipe "%s". Skipping', $recipe->getName()));
continue;
}
$value = $patch['content'];
$newContents = $this->getUnPatchedContents($file, $value);
$this->fileContents[$file] = $newContents;
}
}
private function getPatchedContents(string $file, string $value, string $position, ?string $target, bool $warnIfMissing): string
{
$fileContents = $this->readFile($file);
if (false !== strpos($fileContents, $value)) {
return $fileContents; // already includes value, skip
}
switch ($position) {
case self::POSITION_BOTTOM:
$fileContents .= "\n".$value;
break;
case self::POSITION_TOP:
$fileContents = $value."\n".$fileContents;
break;
case self::POSITION_AFTER_TARGET:
$lines = explode("\n", $fileContents);
$targetFound = false;
foreach ($lines as $key => $line) {
if (false !== strpos($line, $target)) {
array_splice($lines, $key + 1, 0, $value);
$targetFound = true;
break;
}
}
$fileContents = implode("\n", $lines);
if (!$targetFound) {
$this->write([
\sprintf('Could not add lines after "%s" as no such string was found in "%s". Missing lines:', $target, $file),
'<comment>"""</comment>',
$value,
'<comment>"""</comment>',
'',
], $warnIfMissing ? IOInterface::NORMAL : IOInterface::VERBOSE);
}
break;
}
return $fileContents;
}
private function getUnPatchedContents(string $file, $value): string
{
$fileContents = $this->readFile($file);
if (false === strpos($fileContents, $value)) {
return $fileContents; // value already gone!
}
if (false !== strpos($fileContents, "\n".$value)) {
$value = "\n".$value;
} elseif (false !== strpos($fileContents, $value."\n")) {
$value .= "\n";
}
$position = strpos($fileContents, $value);
return substr_replace($fileContents, '', $position, \strlen($value));
}
private function isPackageInstalled($packages): bool
{
if (\is_string($packages)) {
$packages = [$packages];
}
$installedRepo = $this->composer->getRepositoryManager()->getLocalRepository();
foreach ($packages as $packageName) {
if (null === $installedRepo->findPackage($packageName, '*')) {
return false;
}
}
return true;
}
private function relativize(string $path): string
{
$rootDir = $this->options->get('root-dir');
if (0 === strpos($path, $rootDir)) {
$path = substr($path, \strlen($rootDir) + 1);
}
return ltrim($path, '/\\');
}
private function readFile(string $file): string
{
if (isset($this->fileContents[$file])) {
return $this->fileContents[$file];
}
$fileContents = file_get_contents($file);
$this->fileContents[$file] = $fileContents;
return $fileContents;
}
}