diff --git a/system/src/Grav/Common/GPM/GPM.php b/system/src/Grav/Common/GPM/GPM.php index fd82f3642..3a0c4a886 100644 --- a/system/src/Grav/Common/GPM/GPM.php +++ b/system/src/Grav/Common/GPM/GPM.php @@ -1,7 +1,9 @@ 'user/plugins/%name%', 'themes' => 'user/themes/%name%', 'skeletons' => 'user/']; + /** * Creates a new GPM instance with Local and Remote packages available * @param boolean $refresh Applies to Remote Packages only and forces a refetch of data @@ -347,7 +351,20 @@ class GPM extends Iterator $packages = ['total' => 0, 'not_found' => []]; foreach ($searches as $search) { + $repository = ''; + // if this is an object, get the search data from the key + if (is_object($search)) { + $search = (array) $search; + $key = key($search); + $repository = $search[$key]; + $search = $key; + } + if ($found = $this->findPackage($search)) { + // set override respository if provided + if ($repository) { + $found->override_repository = $repository; + } if (!isset($packages[$found->package_type])) { $packages[$found->package_type] = []; } @@ -355,7 +372,19 @@ class GPM extends Iterator $packages[$found->package_type][$found->slug] = $found; $packages['total']++; } else { - $packages['not_found'][] = $search; + // make a best guess at the type based on the repo URL + if (Utils::contains($repository, '-theme')) { + $type = 'themes'; + } else { + $type = 'plugins'; + } + + $not_found = new \stdClass(); + $not_found->name = Inflector::camelize($search); + $not_found->slug = $search; + $not_found->install_path = str_replace('%name%', $search, $this->install_paths[$type]); + $not_found->override_repository = $repository; + $packages['not_found'][$search] = $not_found; } } diff --git a/system/src/Grav/Common/Plugins.php b/system/src/Grav/Common/Plugins.php index dd495963e..c33845fac 100644 --- a/system/src/Grav/Common/Plugins.php +++ b/system/src/Grav/Common/Plugins.php @@ -47,7 +47,8 @@ class Plugins extends Iterator $filePath = $this->grav['locator']('plugins://' . $plugin . DS . $plugin . PLUGIN_EXT); if (!is_file($filePath)) { - throw new \RuntimeException(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin)); + $this->grav['log']->addWarning(sprintf("Plugin '%s' enabled but not found! Try clearing cache with `bin/grav clear-cache`", $plugin)); + continue; } require_once $filePath; diff --git a/system/src/Grav/Console/Gpm/InstallCommand.php b/system/src/Grav/Console/Gpm/InstallCommand.php index 261a73f4a..fd015a0e8 100644 --- a/system/src/Grav/Console/Gpm/InstallCommand.php +++ b/system/src/Grav/Console/Gpm/InstallCommand.php @@ -5,6 +5,8 @@ use Grav\Common\Filesystem\Folder; use Grav\Common\GPM\GPM; use Grav\Common\GPM\Installer; use Grav\Common\GPM\Response; +use Grav\Common\Inflector; +use Grav\Common\Utils; use Grav\Console\ConsoleTrait; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -12,6 +14,10 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\ConfirmationQuestion; +use Symfony\Component\Console\Question\ChoiceQuestion; +use Symfony\Component\Yaml\Yaml; + +define('GIT_REGEX', '/http[s]?:\/\/(?:.*@)?(github|bitbucket)(?:.org|.com)\/.*\/(.*)/'); /** * Class InstallCommand @@ -42,6 +48,9 @@ class InstallCommand extends Command */ protected $tmp; + protected $local_config; + + /** * */ @@ -93,6 +102,11 @@ class InstallCommand extends Command $packages = array_map('strtolower', $this->input->getArgument('package')); $this->data = $this->gpm->findPackages($packages); + $local_config_file = exec('eval echo ~/.grav/config'); + if (file_exists($local_config_file)) { + $this->local_config = Yaml::parse($local_config_file); + } + if ( !Installer::isGravInstance($this->destination) || !Installer::isValidDestination($this->destination, [Installer::EXISTS, Installer::IS_LINK]) @@ -111,7 +125,7 @@ class InstallCommand extends Command if (count($this->data['not_found'])) { $this->output->writeln("These packages were not found on Grav: " . implode(', ', - $this->data['not_found']) . ""); + array_keys($this->data['not_found'])) . ""); } unset($this->data['not_found']); @@ -119,11 +133,131 @@ class InstallCommand extends Command foreach ($this->data as $data) { foreach ($data as $package) { - $version = isset($package->available) ? $package->available : $package->version; - $this->output->writeln("Preparing to install " . $package->name . " [v" . $version . "]"); - $this->output->write(" |- Downloading package... 0%"); - $this->file = $this->downloadPackage($package); + //Check for dependencies + if (isset($package->dependencies)) { + $this->output->writeln("Package " . $package->name . " has ". count($package->dependencies) . " required dependencies that must be installed first..."); + $this->output->writeln(''); + + $dependency_data = $this->gpm->findPackages($package->dependencies); + + if (!$dependency_data['total']) { + $this->output->writeln("No dependencies found..."); + $this->output->writeln(''); + } else { + unset($dependency_data['total']); + + foreach($dependency_data as $type => $dep_data) { + foreach($dep_data as $name => $dep_package) { + + $this->processPackage($dep_package); + } + } + } + } + + $this->processPackage($package); + } + } + + // clear cache after successful upgrade + $this->clearCache(); + } + + /** + * @param $package + */ + private function processPackage($package) + { + $install_options = ['GPM']; + + // if no name, not found in GPM + if (!isset($package->version)) { + unset($install_options[0]); + } + // if local config found symlink is a valid option + if (isset($this->local_config)) { + $install_options[] = 'Symlink'; + } + // if override set, can install via git + if (isset($package->override_repository)) { + $install_options[] = 'Git'; + } + + // reindex list + $install_options = array_values($install_options); + + if (count($install_options) == 0) { + // no valid install options - error and return + $this->output->writeln("not valid installation methods found!"); + return; + } elseif (count($install_options) == 1) { + // only one option, use it... + $method = $install_options[0]; + } else { + $helper = $this->getHelper('question'); + $question = new ChoiceQuestion( + 'Please select installation method for ' . $package->name . ' ('.$install_options[0].' is default)', array_values($install_options), 0 + ); + $question->setErrorMessage('Method %s is invalid'); + $method = $helper->ask($this->input, $this->output, $question); + } + + $this->output->writeln(''); + + $method_name = 'process'.$method; + + $this->$method_name($package); + } + + /** + * @param $package + * + * @return array + */ + private function getGitRegexMatches($package) + { + if (isset($package->override_repository)) { + $repository = $package->override_repository; + } elseif (isset($package->repository)) { + $repository = $package->repository; + } else { + return false; + } + + preg_match(GIT_REGEX, $repository, $matches); + + return $matches; + } + + /** + * @param $package + */ + private function processSymlink($package) + { + + exec('cd ' . $this->destination); + + $to = $this->destination . DS . $package->install_path; + + $matches = $this->getGitRegexMatches($package); + + $this->output->writeln("Preparing to Symlink " . $package->name . ""); + $this->output->write(" |- Checking source... "); + + foreach ($this->local_config as $path) { + + if (Utils::endsWith($matches[2], '.git')) { + $repo_dir = preg_replace('/\.git$/', '', $matches[2]); + } else { + $repo_dir = $matches[2]; + } + + $from = rtrim($path, '/') . '/' . $repo_dir; + + if (file_exists($from)) { + + $this->output->writeln("ok"); $this->output->write(" |- Checking destination... "); $checks = $this->checkDestination($package); @@ -132,21 +266,86 @@ class InstallCommand extends Command $this->output->writeln(" '- Installation failed or aborted."); $this->output->writeln(''); } else { - $this->output->write(" |- Installing package... "); - $installation = $this->installPackage($package); - if (!$installation) { - $this->output->writeln(" '- Installation failed or aborted."); + if (file_exists($to)) { + $this->output->writeln(" '- Symlink cannot overwrite an existing package, please remove first"); $this->output->writeln(''); } else { + symlink($from, $to); + + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Symlinking package... ok "); + $this->output->writeln(" '- Success! "); $this->output->writeln(''); } + + } + return; } } + $this->output->writeln("not found!"); + $this->output->writeln(" '- Installation failed or aborted."); + } - // clear cache after successful upgrade - $this->clearCache(); + /** + * @param $package + */ + private function processGit($package) + { + $matches = $this->getGitRegexMatches($package); + + $to = $this->destination . DS . $package->install_path; + + $this->output->writeln("Preparing to Git clone " . $package->name . " from " . $matches[0]); + + $this->output->write(" |- Checking destination... "); + $checks = $this->checkDestination($package); + + if (!$checks) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $cmd = 'cd ' . $this->destination . ' && git clone ' . $matches[0] . ' ' . $package->install_path; + exec($cmd); + + // extra white spaces to clear out the buffer properly + $this->output->writeln(" |- Cloning package... ok "); + + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + } + + /** + * @param $package + */ + private function processGPM($package) + { + $version = isset($package->available) ? $package->available : $package->version; + + $this->output->writeln("Preparing to install " . $package->name . " [v" . $version . "]"); + + $this->output->write(" |- Downloading package... 0%"); + $this->file = $this->downloadPackage($package); + + $this->output->write(" |- Checking destination... "); + $checks = $this->checkDestination($package); + + if (!$checks) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $this->output->write(" |- Installing package... "); + $installation = $this->installPackage($package); + if (!$installation) { + $this->output->writeln(" '- Installation failed or aborted."); + $this->output->writeln(''); + } else { + $this->output->writeln(" '- Success! "); + $this->output->writeln(''); + } + } } /** @@ -218,6 +417,8 @@ class InstallCommand extends Command $this->output->writeln(" | '- You decided to not delete the symlink automatically."); return false; + } else { + unlink($this->destination . DS . $package->install_path); } }