| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 | <?php/** * @package    Grav\Console\Cli * * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved. * @license    MIT License; see LICENSE file for details. */namespace Grav\Console\Cli;use Grav\Console\GravCommand;use Grav\Framework\File\Formatter\JsonFormatter;use Grav\Framework\File\JsonFile;use RocketTheme\Toolbox\File\YamlFile;use Symfony\Component\Console\Input\InputArgument;use Symfony\Component\Console\Input\InputOption;use function is_array;/** * Class InstallCommand * @package Grav\Console\Cli */class InstallCommand extends GravCommand{    /** @var array */    protected $config;    /** @var string */    protected $destination;    /** @var string */    protected $user_path;    /**     * @return void     */    protected function configure(): void    {        $this            ->setName('install')            ->addOption(                'symlink',                's',                InputOption::VALUE_NONE,                'Symlink the required bits'            )            ->addOption(                'plugin',                'p',                InputOption::VALUE_REQUIRED,                'Install plugin (symlink)'            )            ->addOption(                'theme',                't',                InputOption::VALUE_REQUIRED,                'Install theme (symlink)'            )            ->addArgument(                'destination',                InputArgument::OPTIONAL,                'Where to install the required bits (default to current project)'            )            ->setDescription('Installs the dependencies needed by Grav. Optionally can create symbolic links')            ->setHelp('The <info>install</info> command installs the dependencies needed by Grav. Optionally can create symbolic links');    }    /**     * @return int     */    protected function serve(): int    {        $input = $this->getInput();        $io = $this->getIO();        $dependencies_file = '.dependencies';        $this->destination = $input->getArgument('destination') ?: GRAV_WEBROOT;        // fix trailing slash        $this->destination = rtrim($this->destination, DS) . DS;        $this->user_path = $this->destination . GRAV_USER_PATH . DS;        if ($local_config_file = $this->loadLocalConfig()) {            $io->writeln('Read local config from <cyan>' . $local_config_file . '</cyan>');        }        // Look for dependencies file in ROOT and USER dir        if (file_exists($this->user_path . $dependencies_file)) {            $file = YamlFile::instance($this->user_path . $dependencies_file);        } elseif (file_exists($this->destination . $dependencies_file)) {            $file = YamlFile::instance($this->destination . $dependencies_file);        } else {            $io->writeln('<red>ERROR</red> Missing .dependencies file in <cyan>user/</cyan> folder');            if ($input->getArgument('destination')) {                $io->writeln('<yellow>HINT</yellow> <info>Are you trying to install a plugin or a theme? Make sure you use <cyan>bin/gpm install <something></cyan>, not <cyan>bin/grav install</cyan>. This command is only used to install Grav skeletons.');            } else {                $io->writeln('<yellow>HINT</yellow> <info>Are you trying to install Grav? Grav is already installed. You need to run this command only if you download a skeleton from GitHub directly.');            }            return 1;        }        $this->config = $file->content();        $file->free();        // If no config, fail.        if (!$this->config) {            $io->writeln('<red>ERROR</red> invalid YAML in ' . $dependencies_file);            return 1;        }        $plugin = $input->getOption('plugin');        $theme = $input->getOption('theme');        $name = $plugin ?? $theme;        $symlink = $name || $input->getOption('symlink');        if (!$symlink) {            // Updates composer first            $io->writeln("\nInstalling vendor dependencies");            $io->writeln($this->composerUpdate(GRAV_ROOT, 'install'));            $error = $this->gitclone();        } else {            $type = $name ? ($plugin ? 'plugin' : 'theme') : null;            $error = $this->symlink($name, $type);        }        return $error;    }    /**     * Clones from Git     *     * @return int     */    private function gitclone(): int    {        $io = $this->getIO();        $io->newLine();        $io->writeln('<green>Cloning Bits</green>');        $io->writeln('============');        $io->newLine();        $error = 0;        $this->destination = rtrim($this->destination, DS);        foreach ($this->config['git'] as $repo => $data) {            $path = $this->destination . DS . $data['path'];            if (!file_exists($path)) {                exec('cd ' . escapeshellarg($this->destination) . ' && git clone -b ' . $data['branch'] . ' --depth 1 ' . $data['url'] . ' ' . $data['path'], $output, $return);                if (!$return) {                    $io->writeln('<green>SUCCESS</green> cloned <magenta>' . $data['url'] . '</magenta> -> <cyan>' . $path . '</cyan>');                } else {                    $io->writeln('<red>ERROR</red> cloning <magenta>' . $data['url']);                    $error = 1;                }                $io->newLine();            } else {                $io->writeln('<yellow>' . $path . ' already exists, skipping...</yellow>');                $io->newLine();            }        }        return $error;    }    /**     * Symlinks     *     * @param string|null $name     * @param string|null $type     * @return int     */    private function symlink(string $name = null, string $type = null): int    {        $io = $this->getIO();        $io->newLine();        $io->writeln('<green>Symlinking Bits</green>');        $io->writeln('===============');        $io->newLine();        if (!$this->local_config) {            $io->writeln('<red>No local configuration available, aborting...</red>');            $io->newLine();            return 1;        }        $error = 0;        $this->destination = rtrim($this->destination, DS);        if ($name) {            $src = "grav-{$type}-{$name}";            $links = [                $name => [                    'scm' => 'github', // TODO: make configurable                    'src' => $src,                    'path' => "user/{$type}s/{$name}"                ]            ];        } else {            $links = $this->config['links'];        }        foreach ($links as $name => $data) {            $scm = $data['scm'] ?? null;            $src = $data['src'] ?? null;            $path = $data['path'] ?? null;            if (!isset($scm, $src, $path)) {                $io->writeln("<red>Dependency '$name' has broken configuration, skipping...</red>");                $io->newLine();                $error = 1;                continue;            }            $locations = (array) $this->local_config["{$scm}_repos"];            $to = $this->destination . DS . $path;            $from = null;            foreach ($locations as $location) {                $test = rtrim($location, '\\/') . DS . $src;                if (file_exists($test)) {                    $from = $test;                    continue;                }            }            if (is_link($to) && !realpath($to)) {                $io->writeln('<yellow>Removed broken symlink '. $path .'</yellow>');                unlink($to);            }            if (null === $from) {                $io->writeln('<red>source for ' . $src . ' does not exists, skipping...</red>');                $io->newLine();                $error = 1;            } elseif (!file_exists($to)) {                $error = $this->addSymlinks($from, $to, ['name' => $name, 'src' => $src, 'path' => $path]);                $io->newLine();            } else {                $io->writeln('<yellow>destination: ' . $path . ' already exists, skipping...</yellow>');                $io->newLine();            }        }        return $error;    }    private function addSymlinks(string $from, string $to, array $options): int    {        $io = $this->getIO();        $hebe = $this->readHebe($from);        if (null === $hebe) {            symlink($from, $to);            $io->writeln('<green>SUCCESS</green> symlinked <magenta>' . $options['src'] . '</magenta> -> <cyan>' . $options['path'] . '</cyan>');        } else {            $to = GRAV_ROOT;            $name = $options['name'];            $io->writeln("Processing <magenta>{$name}</magenta>");            foreach ($hebe as $section => $symlinks) {                foreach ($symlinks as $symlink) {                    $src = trim($symlink['source'], '/');                    $dst = trim($symlink['destination'], '/');                    $s = "{$from}/{$src}";                    $d = "{$to}/{$dst}";                    if (is_link($d) && !realpath($d)) {                        unlink($d);                        $io->writeln('    <yellow>Removed broken symlink '. $dst .'</yellow>');                    }                    if (!file_exists($d)) {                        symlink($s, $d);                        $io->writeln('    symlinked <magenta>' . $src . '</magenta> -> <cyan>' . $dst . '</cyan>');                    }                }            }            $io->writeln('<green>SUCCESS</green>');        }        return 0;    }    private function readHebe(string $folder): ?array    {        $filename = "{$folder}/hebe.json";        if (!is_file($filename)) {            return null;        }        $formatter = new JsonFormatter();        $file = new JsonFile($filename, $formatter);        $hebe = $file->load();        $paths = $hebe['platforms']['grav']['nodes'] ?? null;        return is_array($paths) ? $paths : null;    }}
 |