object $handler] */ private $handlers; public function __construct(Composer $composer, IOInterface $io) { $this->constructComposerIo($composer, $io); $this->handlers = [ 'export' => new ExportHandler(), 'php-eval' => new PhpEvalHandler(), 'php-method' => new PhpMethodHandler(), 'php-script' => new PhpScriptHandler(), 'sh' => new ComposerScriptHandler(), 'putenv' => new ComposerScriptHandler(), 'php' => new ComposerScriptHandler(), 'composer' => new ComposerScriptHandler(), ]; ksort($this->handlers); if (Task::HANDLERS !== implode('|', array_keys($this->handlers))) { throw new \RuntimeException("Task type list is out-of-date. Validation may not work."); } } /** * Run the items in the $taskList, as per policy. * * @param \Civi\CompilePlugin\TaskList $taskList */ public function runDefault(TaskList $taskList, $isDryRun = false) { $allowedTasks = $taskList->getByFilters($this->getWhitelistRules()); $blockedTasks = array_filter($taskList->getAll(), function ($task) use ($allowedTasks) { return !isset($allowedTasks[$task->id]); }); $mode = $this->getMode(); if ($mode === 'prompt' && empty($blockedTasks)) { // If whitelist covers all extant tasks, then no need for user-interaction. $mode = 'all'; } switch ($mode) { case 'all': $this->run($taskList->getAll(), $isDryRun); break; case 'none': $this->io->write( "ERROR: Automatic compilation is disabled. These packages have compilation tasks which have not been executed:" ); $this->io->write(TaskUIHelper::formatTaskSummary($taskList->getAll())); $this->io->writeError(sprintf("Skipped %d compilation task(s)", count($taskList->getAll()))); $this->io->writeError("You may run skipped tasks with \"composer compile --all\""); break; case 'whitelist': $disabledCount = $taskList->disable($blockedTasks); $this->run($taskList->getAll(), $isDryRun); if ($disabledCount) { $this->io->writeError(sprintf('WARNING: %d task(s) were omitted due to whitelist policy', $disabledCount)); } $this->io->writeError("You may run skipped tasks with \"composer compile --all\""); break; case 'prompt': $choice = $this->askApproveTasks($blockedTasks); if ($choice === 'a') { $this->addWhitelistRules(array_map(function ($task) { return $task->packageName; }, $blockedTasks)); } switch ($choice) { case 'y': case 'a': $this->run($taskList->getAll(), $isDryRun); break; case 'n': $this->io->writeError(sprintf("Skipped %d compilation task(s)", count($blockedTasks))); $this->io->writeError("You may run skipped tasks with \"composer compile --all\""); break; } } } /** * Execute a list of compilation tasks. * * @param Task[] $tasks * @param bool $isDryRun */ public function run(array $tasks, $isDryRun = false) { /** @var IOInterface $io */ $io = $this->io; $dryRunText = $isDryRun ? '(DRY-RUN) ' : ''; if (empty($tasks)) { return; } switch ($this->getPassthruMode()) { case 'never': case 'error': $io->write('Compiling additional files (For full details, use verbose "-v" mode.)'); break; case 'always': $io->write('Compiling additional files'); break; } $tasks = $this->sortTasks($tasks); foreach ($tasks as $task) { /** @var \Civi\CompilePlugin\Task $task */ $package = ($this->composer->getPackage()->getName() === $task->packageName) ? $this->composer->getPackage() : $this->composer->getRepositoryManager()->getLocalRepository()->findPackage($task->packageName, '*'); $event = new CompileTaskEvent(CompileEvents::PRE_COMPILE_TASK, $this->composer, $this->io, $package, $task, $isDryRun); $dispatcher = $this->composer->getEventDispatcher(); $dispatcher->dispatch(CompileEvents::PRE_COMPILE_TASK, $event); if (!$task->active) { $io->write( $dryRunText . 'Skip: ' . ($task->title), true, IOInterface::VERBOSE ); continue; } $io->write($dryRunText . 'Compile: ' . ($task->title)); if (!$isDryRun) { $this->runTask($task, $package); } $event = new CompileTaskEvent(CompileEvents::POST_COMPILE_TASK, $this->composer, $this->io, $package, $task, $isDryRun); $this->composer->getEventDispatcher()->dispatch(CompileEvents::POST_COMPILE_TASK, $event); } } protected function runTask(Task $task, PackageInterface $package) { $orig = [ 'pwd' => getcwd(), 'env' => EnvHelper::getAll(), ]; $passthruPolicyFilter = new PassthruPolicyFilter( $this->io, $this->getPassthruMode(), function ($message) { if ($this->io->isVerbose()) { return true; } return preg_match(';^;', $message); } ); try { chdir($task->pwd); TaskTransfer::export($task); foreach ($task->getParsedRun() as $run) { if (!isset($this->handlers[$run['type']])) { throw new \InvalidArgumentException("Unrecognized prefix: @" . $run['type']); } $isDryRun = false; $e = new CompileTaskEvent('compile-task-' . $task->id, $this->composer, $passthruPolicyFilter, $package, $task, $isDryRun); $this->handlers[$run['type']]->runTask($e, $run['type'], $run['code']); } } finally { TaskTransfer::cleanup(); chdir($orig['pwd']); EnvHelper::setAll($orig['env']); } } /** * @param Task[] $tasks * @return Task[] */ public function sortTasks($tasks) { usort($tasks, function ($a, $b) { $fields = ['weight', 'packageWeight', 'naturalWeight']; foreach ($fields as $field) { if ($a->{$field} > $b->{$field}) { return 1; } elseif ($a->{$field} < $b->{$field}) { return -1; } } return 0; }); return $tasks; } /** * Determine whether compilation is enabled. * * @return string * One of: * - 'all': Automatically run all compilation tasks * - 'whitelist': Automatically compile anything on the whitelist, and * reject everything else. * - 'prompt': Automatically compile anything on the whitelist, and * prompt for everything else. * - 'none': Do not compile automatically. */ public function getMode() { $aliases = [ '0' => 'none', '1' => 'all', 'off' => 'none', 'on' => 'all', ]; $mode = getenv('COMPOSER_COMPILE'); if ($mode === '' || $mode === false || $mode === null) { $extra = $this->composer->getPackage()->getExtra(); $mode = $extra['compile-mode'] ?? ''; } if ($mode === '' || $mode === false || $mode === null) { $mode = 'prompt'; } $mode = strtolower($mode); if (isset($aliases[$mode])) { $mode = $aliases[$mode]; } $options = ['all', 'prompt', 'whitelist', 'none']; if (in_array($mode, $options)) { return $mode; } else { throw new \InvalidArgumentException( "The compilation policy (COMPOSER_COMPILE or extra.compile-mode) is invalid. Valid options are \"" . implode('", "', $options) . "\"." ); } } /** * @return string */ public function getPassthruMode() { $passthru = getenv('COMPOSER_COMPILE_PASSTHRU'); if ($passthru === '' || $passthru === false || $passthru === null) { $extra = $this->composer->getPackage()->getExtra(); $passthru = $extra['compile-passthru'] ?? ''; } if ($passthru === '' || $passthru === false || $passthru === null) { $passthru = 'error'; } return $passthru; } /** * Get a list of packages that are trusted to do compilation. * * @return array * Ex: ['foo/bar', 'civicrm/*'] */ protected function getWhitelistRules() { $rules = $this->composer->getPackage()->getExtra()['compile-whitelist'] ?? []; // The root package is an ex-officio member of the whitelist. $root = $this->composer->getPackage(); if (!in_array($root->getName(), $rules)) { $rules[] = $root->getName(); } return $rules; } /** * Update list of packages that are trusted to do compilation. * * @param array $newRules * Ex: ['foo/bar', 'civicrm/*'] */ protected function addWhitelistRules($newRules) { $oldRules = $this->composer->getPackage()->getExtra()['compile-whitelist'] ?? []; $rules = array_unique(array_merge($oldRules, $newRules)); sort($rules); $this->composer->getConfig()->getConfigSource()->addProperty('extra.compile-whitelist', $rules); } /** * @param Task[] $blockedTasks * List of tasks that we cannot currently run. * @return string * Returns 'y' or 'n' or 'a' * @throws \Exception */ protected function askApproveTasks($blockedTasks) { if (!$this->io->isInteractive()) { throw new \Exception( "Cannot prompt for compilation preferences. Please update COMPOSER_COMPILE, extra.compile-mode, or extra.compile-whitelist." ); } $blockedTasks = $this->sortTasks($blockedTasks); $choice = null; do { if ($choice === null) { $this->io->write(""); $this->io->write(sprintf("The following packages have new compilation tasks:")); $this->io->write(TaskUIHelper::formatTaskSummary($blockedTasks)); } $actions = implode(', ', [ '[y]es', '[a]lways', '[n]o', '[l]ist', '[h]elp' ]); $choice = $this->io->askAndValidate( 'Allow these packages to compile? (' . $actions . ') ', function ($x) { $x = strtolower($x); return in_array($x, ['y', 'n', 'a', 'h', 'l']) ? $x : null; } ); if ($choice === 'h') { $this->io->write("\n" . file_get_contents(__DIR__ . '/messages/prompt-help.txt')); } if ($choice === 'l') { $this->io->write(TaskUIHelper::formatTaskTable($blockedTasks, ['packageName', 'title', 'action'])); } } while (!in_array($choice, ['y', 'n', 'a'])); return $choice; } } __halt_compiler();----SIGNATURE:----YuEI05SSL6F94f08pUzWzQkEOCkpKXlkN9ZPPeYwtPySpt/HSEFxDPPpO3Afe8BP4Yw0DKK7qa5ovmkCvvyU0+ljAdZZZtqvxwuMw6j0djgfzU9D2yzeWBBAJ3Xx6qQTGdXVXYnKU8C0CIA0PNQ+U5u83antHScljDpC2xOCy63d3sw6vqQo62Z2zzW+o46OR46D/TL01Zxmkdr00hMNxtiN6yP1ZCPGOF1ZsznFJyk6Wva4DhI2xzcCWo417tQ+Yk+xQnJXrM09+SZib7Mj6Mi7shcqv+40RmIzROiUN5kqAqwxXDhWQMixmJanOYaTFphiblDlys3XPfiRF0VKfGo2EIAoJxQylk7gFGepgFbcxP1VWrf74Sxi6cLrWj69b2TQfWm15pW+lvxgQbVd2BwQLfsellk9h3Z4sYCLnO4bCZS6tOQC+i4+v4skoneQT/JqGux/8InAlQF4f/p9i7LcO42ETp1n1OEh5jpuABJUIacuM9u44LjbM6hAw6KgXa3pgSKnhMj+PWHZqrdjQjb2n3++owNnsLN64/oDDP/y9xpg1/ngluVG9Yg55gpEB6zSth9jYSBllhYedb6PJEbkedAIB+PuIj7PiHmHmx24GBRPhcLYwV3TlFsLORLrryOVma6G/PL7cQQX4GmmdeVAsgm7jignj56U77Xqecc=----ATTACHMENT:----NTIzOTY2MjY0MzY2NzQ4MSA0ODUxODkzMzgxMTc5OTE1IDE2NzI4MDQzOTYxMjY3NjQ=