diff --git a/CHANGELOG.md b/CHANGELOG.md index d60a7ab36..fcbd4e51f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ +# v1.0.8 +## 01/08/2016 + +1. [](#new) + * Added `rotate`, `flip` and `fixOrientation` image medium methods +1. [](#bugfix) + * Removed IP from Nonce generation. Should be more reliable in a variety of scenarios + # v1.0.7 -## 01/XX/2016 +## 01/07/2016 1. [](#new) * Added `composer create-project` as an additional installation method #585 @@ -11,6 +19,7 @@ * Added global setting for `twig_first` processing (false by default) * New configuration options for Session settings #553 1. [](#improved) + * Switched to SSL for GPM calls * Use `URI->host()` for session domain * Add support for `open_basedir` when installing packages via GPM * Improved `Utils::generateNonceString()` method to handle reverse proxies @@ -30,6 +39,7 @@ * Fix for markdown attributes on external URLs * Fixed issue where `data:` page header was acting as `publish_date:` * Fix for special characters in URL parameters (e.g. /tag:c++) #541 + * Safety check for an array of nonces to only use the first one # v1.0.6 ## 12/22/2015 diff --git a/composer.json b/composer.json index 4b5d1efe3..bf9467b7c 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,10 @@ "donatj/phpuseragentparser": "~0.3", "pimple/pimple": "~3.0", "rockettheme/toolbox": "~1.2", - "maximebf/debugbar": "~1.10" + "maximebf/debugbar": "~1.10", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-curl": "*" }, "require-dev": { "codeception/codeception": "^2.1", diff --git a/composer.lock b/composer.lock index 2e4ff147c..5252c1f7a 100644 --- a/composer.lock +++ b/composer.lock @@ -1076,16 +1076,16 @@ }, { "name": "twig/twig", - "version": "v1.23.1", + "version": "v1.23.3", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6" + "reference": "ae53fc2c312fdee63773b75cb570304f85388b08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/d9b6333ae8dd2c8e3fd256e127548def0bc614c6", - "reference": "d9b6333ae8dd2c8e3fd256e127548def0bc614c6", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/ae53fc2c312fdee63773b75cb570304f85388b08", + "reference": "ae53fc2c312fdee63773b75cb570304f85388b08", "shasum": "" }, "require": { @@ -1133,7 +1133,7 @@ "keywords": [ "templating" ], - "time": "2015-11-05 12:49:06" + "time": "2016-01-11 14:02:19" } ], "packages-dev": [ diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml index 2d7f78b56..ecde4be87 100644 --- a/system/blueprints/config/system.yaml +++ b/system/blueprints/config/system.yaml @@ -848,6 +848,17 @@ form: underline: true fields: + reverse_proxy_setup: + type: toggle + label: PLUGIN_ADMIN.REVERSE_PROXY + highlight: 0 + help: PLUGIN_ADMIN.REVERSE_PROXY_HELP + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + wrapped_site: type: toggle label: PLUGIN_ADMIN.WRAPPED_SITE diff --git a/system/blueprints/pages/default.yaml b/system/blueprints/pages/default.yaml index d421bbd62..6eab5d464 100644 --- a/system/blueprints/pages/default.yaml +++ b/system/blueprints/pages/default.yaml @@ -139,7 +139,7 @@ form: type: select label: PLUGIN_ADMIN.PARENT classes: fancy - '@data-options': '\Grav\Common\Page\Pages::parents' + '@data-options': '\Grav\Common\Page\Pages::parentsRawRoutes' '@data-default': '\Grav\Plugin\admin::route' options: '/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT diff --git a/system/blueprints/pages/new_folder.yaml b/system/blueprints/pages/new_folder.yaml new file mode 100644 index 000000000..311d814bd --- /dev/null +++ b/system/blueprints/pages/new_folder.yaml @@ -0,0 +1,35 @@ +rules: + slug: + pattern: "[a-z][a-z0-9_\-]+" + min: 2 + max: 80 + +form: + validation: loose + fields: + + section: + type: section + title: PLUGIN_ADMIN.ADD_PAGE + + folder: + type: text + label: PLUGIN_ADMIN.FOLDER_NAME + help: PLUGIN_ADMIN.FOLDER_NAME_HELP + validate: + type: slug + required: true + + route: + type: select + label: PLUGIN_ADMIN.PARENT_PAGE + classes: fancy + '@data-options': '\Grav\Common\Page\Pages::parents' + '@data-default': '\Grav\Plugin\admin::getLastPageRoute' + options: + '/': PLUGIN_ADMIN.DEFAULT_OPTION_ROOT + validate: + required: true + + blueprint: + type: blueprint diff --git a/system/config/system.yaml b/system/config/system.yaml index 4b0bdb486..17a2bf3d4 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -3,6 +3,7 @@ timezone: '' # Valid values: http://php.net/manua default_locale: # Default locale (defaults to system) param_sep: ':' # Parameter separator, use ';' for Apache on windows wrapped_site: false # For themes/plugins to know if Grav is wrapped by another platform +reverse_proxy_setup: false # Running in a reverse proxy scenario with different webserver ports than proxy languages: supported: [] # List of languages supported. eg: [en, fr, de] diff --git a/system/defines.php b/system/defines.php index 929d5d2e4..6127aca1e 100644 --- a/system/defines.php +++ b/system/defines.php @@ -2,7 +2,7 @@ // Some standard defines define('GRAV', true); -define('GRAV_VERSION', '1.0.6'); +define('GRAV_VERSION', '1.0.8'); define('DS', '/'); define('GRAV_PHP_MIN', '5.5.9'); diff --git a/system/languages/hr.yaml b/system/languages/hr.yaml index 3dbd80218..098b8f922 100644 --- a/system/languages/hr.yaml +++ b/system/languages/hr.yaml @@ -50,3 +50,5 @@ NICETIME: FORM: VALIDATION_FAIL: Validacija nije uspjela: INVALID_INPUT: Unos nije valjan +MONTHS_OF_THE_YEAR: ['Siječanj', 'Veljača', 'Ožujak', 'Travanj', 'Svibanj', 'Lipanj', 'Srpanj', 'Kolovoz', 'Rujan', 'Listopad', 'Studeni', 'Prosinac'] +DAYS_OF_THE_WEEK: ['ponedjeljak', 'utorak', 'srijeda', 'četvrtak', 'petak', 'subota', 'nedjelja'] diff --git a/system/src/Grav/Common/Config/Setup.php b/system/src/Grav/Common/Config/Setup.php index b345426c1..8b6b70750 100644 --- a/system/src/Grav/Common/Config/Setup.php +++ b/system/src/Grav/Common/Config/Setup.php @@ -158,7 +158,7 @@ class Setup extends Data } // Update streams. - foreach ($files as $path) { + foreach (array_reverse($files) as $path) { $file = CompiledYamlFile::instance($path); $content = $file->content(); if (!empty($content['schemes'])) { diff --git a/system/src/Grav/Common/GPM/Remote/Grav.php b/system/src/Grav/Common/GPM/Remote/Grav.php index 152b8eda7..cbc3179fc 100644 --- a/system/src/Grav/Common/GPM/Remote/Grav.php +++ b/system/src/Grav/Common/GPM/Remote/Grav.php @@ -5,7 +5,7 @@ use \Doctrine\Common\Cache\FilesystemCache; class Grav extends AbstractPackageCollection { - protected $repository = 'http://getgrav.org/downloads/grav.json'; + protected $repository = 'https://getgrav.org/downloads/grav.json'; private $data; private $version; diff --git a/system/src/Grav/Common/GPM/Remote/Plugins.php b/system/src/Grav/Common/GPM/Remote/Plugins.php index df8668491..036fc0c2a 100644 --- a/system/src/Grav/Common/GPM/Remote/Plugins.php +++ b/system/src/Grav/Common/GPM/Remote/Plugins.php @@ -12,7 +12,7 @@ class Plugins extends AbstractPackageCollection */ protected $type = 'plugins'; - protected $repository = 'http://getgrav.org/downloads/plugins.json'; + protected $repository = 'https://getgrav.org/downloads/plugins.json'; /** * Local Plugins Constructor diff --git a/system/src/Grav/Common/GPM/Remote/Themes.php b/system/src/Grav/Common/GPM/Remote/Themes.php index 0af055b03..43a5af54d 100644 --- a/system/src/Grav/Common/GPM/Remote/Themes.php +++ b/system/src/Grav/Common/GPM/Remote/Themes.php @@ -12,7 +12,7 @@ class Themes extends AbstractPackageCollection */ protected $type = 'themes'; - protected $repository = 'http://getgrav.org/downloads/themes.json'; + protected $repository = 'https://getgrav.org/downloads/themes.json'; /** * Local Themes Constructor diff --git a/system/src/Grav/Common/Grav.php b/system/src/Grav/Common/Grav.php index 053b7fa1f..1642211d2 100644 --- a/system/src/Grav/Common/Grav.php +++ b/system/src/Grav/Common/Grav.php @@ -58,7 +58,7 @@ class Grav extends Container $container['grav'] = $container; $container['debugger'] = new Debugger(); - $container['debugger']->startTimer('_init', 'Initialize'); + $container['debugger']->startTimer('_services', 'Services'); $container->register(new LoggerServiceProvider); @@ -173,7 +173,7 @@ class Grav extends Container $container['inflector'] = new Inflector(); - $container['debugger']->stopTimer('_init'); + $container['debugger']->stopTimer('_services'); return $container; } @@ -183,17 +183,25 @@ class Grav extends Container /** @var Debugger $debugger */ $debugger = $this['debugger']; + // Load site setup and initializing streams. + $debugger->startTimer('_setup', 'Site Setup'); + $this['setup']->init(); + $this['streams']; + $debugger->stopTimer('_setup'); + // Initialize configuration. $debugger->startTimer('_config', 'Configuration'); $this['config']->init(); - $this['errors']->resetHandlers(); - $this['uri']->init(); - $this['session']->init(); - - $debugger->init(); - $this['config']->debug(); $debugger->stopTimer('_config'); + // Initialize error handlers. + $this['errors']->resetHandlers(); + + // Initialize debugger. + $debugger->init(); + $debugger->startTimer('init', 'Initialize'); + $this['config']->debug(); + // Use output buffering to prevent headers from being sent too early. ob_start(); if ($this['config']->get('system.cache.gzip')) { @@ -203,21 +211,23 @@ class Grav extends Container } } - // Initialize the timezone + // Initialize the timezone. if ($this['config']->get('system.timezone')) { date_default_timezone_set($this['config']->get('system.timezone')); } - // Initialize Locale if set and configured + // Initialize uri, session. + $this['uri']->init(); + $this['session']->init(); + + // Initialize Locale if set and configured. if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) { setlocale(LC_ALL, $this['language']->getLanguage()); } elseif ($this['config']->get('system.default_locale')) { setlocale(LC_ALL, $this['config']->get('system.default_locale')); } - $debugger->startTimer('streams', 'Streams'); - $this['streams']; - $debugger->stopTimer('streams'); + $debugger->stopTimer('init'); $debugger->startTimer('plugins', 'Plugins'); $this['plugins']->init(); diff --git a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php index 57bdcd93b..6617ab5a0 100644 --- a/system/src/Grav/Common/Markdown/ParsedownGravTrait.php +++ b/system/src/Grav/Common/Markdown/ParsedownGravTrait.php @@ -34,6 +34,7 @@ trait ParsedownGravTrait $this->page = $page; $this->pages = $grav['pages']; $this->BlockTypes['{'] [] = "TwigTag"; + $this->BlockTypes['['] [] = "ShortcodeTag"; $this->base_url = rtrim(self::getGrav()['base_url'] . self::getGrav()['pages']->base(), '/'); $this->pages_dir = self::getGrav()['locator']->findResource('page://'); $this->special_chars = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); @@ -145,6 +146,16 @@ trait ParsedownGravTrait } } + protected function blockShortcodeTag($Line) + { + if (preg_match('/^(?:\[)(.*)(?:\])$/', $Line['body'], $matches)) { + $Block = array( + 'markup' => $Line['body'], + ); + return $Block; + } + } + protected function inlineSpecialCharacter($Excerpt) { if ($Excerpt['text'][0] === '&' && ! preg_match('/^&#?\w+;/', $Excerpt['text'])) { diff --git a/system/src/Grav/Common/Page/Medium/ImageMedium.php b/system/src/Grav/Common/Page/Medium/ImageMedium.php index 3db039643..85de365d3 100644 --- a/system/src/Grav/Common/Page/Medium/ImageMedium.php +++ b/system/src/Grav/Common/Page/Medium/ImageMedium.php @@ -41,7 +41,8 @@ class ImageMedium extends Medium public static $magic_actions = [ 'resize', 'forceResize', 'cropResize', 'crop', 'zoomCrop', 'negate', 'brightness', 'contrast', 'grayscale', 'emboss', - 'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive' + 'smooth', 'sharp', 'edge', 'colorize', 'sepia', 'enableProgressive', + 'rotate', 'flip', 'fixOrientation' ]; /** diff --git a/system/src/Grav/Common/Page/Page.php b/system/src/Grav/Common/Page/Page.php index 4cf61791d..916d99aca 100644 --- a/system/src/Grav/Common/Page/Page.php +++ b/system/src/Grav/Common/Page/Page.php @@ -1152,11 +1152,12 @@ class Page $this->metadata = []; + $metadata = []; // Set the Generator tag - $this->metadata['generator'] = array('name'=>'generator', 'content'=>'GravCMS'); + $metadata['generator'] = 'GravCMS'; // Get initial metadata for the page - $metadata = self::getGrav()['config']->get('site.metadata'); + $metadata = array_merge($metadata, self::getGrav()['config']->get('site.metadata')); if (isset($this->header->metadata)) { // Merge any site.metadata settings in with page metadata @@ -1173,12 +1174,15 @@ class Page } // If it this is a standard meta data type } else { - if (in_array($key, $header_tag_http_equivs)) { - $this->metadata[$key] = array('http_equiv'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES)); - } else { - $this->metadata[$key] = array('name'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES)); + if ($value) { + if (in_array($key, $header_tag_http_equivs)) { + $this->metadata[$key] = array('http_equiv'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES)); + } else { + $this->metadata[$key] = array('name'=>$key, 'content'=>htmlspecialchars($value, ENT_QUOTES)); + } } } + } } @@ -1420,6 +1424,7 @@ class Page if ($var !== null) { $this->modified = $var; } + return $this->modified; } diff --git a/system/src/Grav/Common/Page/Pages.php b/system/src/Grav/Common/Page/Pages.php index 7193db791..2dd1bbadf 100644 --- a/system/src/Grav/Common/Page/Pages.php +++ b/system/src/Grav/Common/Page/Pages.php @@ -398,7 +398,7 @@ class Pages * @return array * @throws \RuntimeException */ - public function getList(Page $current = null, $level = 0) + public function getList(Page $current = null, $level = 0, $rawRoutes = false) { if (!$current) { if ($level) { @@ -411,11 +411,16 @@ class Pages $list = array(); if (!$current->root()) { - $list[$current->route()] = str_repeat('  ', ($level-1)*2) . $current->title(); + if ($rawRoutes) { + $route = $current->rawRoute(); + } else { + $route = $current->route(); + } + $list[$route] = str_repeat('  ', ($level-1)*2) . $current->title(); } foreach ($current->children() as $next) { - $list = array_merge($list, $this->getList($next, $level + 1)); + $list = array_merge($list, $this->getList($next, $level + 1, $rawRoutes)); } return $list; @@ -517,18 +522,42 @@ class Pages } /** - * Get available parents. + * Get available parents routes * * @return array */ public static function parents() + { + $rawRoutes = false; + return self::getParents($rawRoutes); + } + + /** + * Get available parents raw routes. + * + * @return array + */ + public static function parentsRawRoutes() + { + $rawRoutes = true; + return self::getParents($rawRoutes); + } + + /** + * Get available parents routes + * + * @param bool $rawRoutes get the raw route or the normal route + * + * @return array + */ + private static function getParents($rawRoutes) { $grav = Grav::instance(); /** @var Pages $pages */ $pages = $grav['pages']; - $parents = $pages->getList(); + $parents = $pages->getList(null, 0, $rawRoutes); /** @var Admin $admin */ $admin = $grav['admin']; @@ -544,6 +573,7 @@ class Pages } return $parents; + } /** @@ -790,6 +820,17 @@ class Pages $page->routable(false); } + // Override the modified time if modular + if ($page->template() == 'modular') { + foreach ($page->collection() as $child) { + $modified = $child->modified(); + + if ($modified > $last_modified) { + $last_modified = $modified; + } + } + } + // Override the modified and ID so that it takes the latest change into account $page->modified($last_modified); $page->id($last_modified.md5($page->filePath())); diff --git a/system/src/Grav/Common/Service/ConfigServiceProvider.php b/system/src/Grav/Common/Service/ConfigServiceProvider.php index ebb156b1a..79c99ed00 100644 --- a/system/src/Grav/Common/Service/ConfigServiceProvider.php +++ b/system/src/Grav/Common/Service/ConfigServiceProvider.php @@ -22,7 +22,7 @@ class ConfigServiceProvider implements ServiceProviderInterface public function register(Container $container) { $container['setup'] = function ($c) { - return static::setup($c)->init(); + return static::setup($c); }; $container['blueprints'] = function ($c) { diff --git a/system/src/Grav/Common/Twig/Twig.php b/system/src/Grav/Common/Twig/Twig.php index 0b3be023c..aa3119946 100644 --- a/system/src/Grav/Common/Twig/Twig.php +++ b/system/src/Grav/Common/Twig/Twig.php @@ -25,7 +25,7 @@ class Twig /** * @var array */ - public $twig_vars; + public $twig_vars = []; /** * @var array @@ -143,7 +143,7 @@ class Twig $this->grav->fireEvent('onTwigExtensions'); // Set some standard variables for twig - $this->twig_vars = array( + $this->twig_vars = $this->twig_vars + array( 'config' => $config, 'uri' => $this->grav['uri'], 'base_dir' => rtrim(ROOT_DIR, '/'), diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index ecac11645..9156d36ab 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -24,8 +24,10 @@ class Uri protected $params; protected $path; protected $paths; + protected $port; protected $query; protected $root; + protected $root_path; protected $uri; /** @@ -117,11 +119,6 @@ class Uri $base .= $this->name; $port = $this->port; - // add the port of needed - if ($port != '80' && $port != '443') { - $base .= ":" . $port; - } - return $base; } @@ -202,6 +199,11 @@ class Uri $root_path = $this->buildRootPath(); $this->root = $this->base . $root_path; $this->url = $this->base . $this->uri; + + $this->port = $port; + $this->base = $base; + $this->uri = $uri; + $this->root_path = $root_path; } /** @@ -214,6 +216,20 @@ class Uri $config = $grav['config']; $language = $grav['language']; + // resets + $this->paths = []; + $this->params = []; + $this->query = []; + + // add the port to the base for non-standard ports + if ($config->get('system.reverse_proxy_setup') == false && $this->port != '80' && $this->port != '443') { + $this->base .= ":".$this->port; + } + + // Set some defaults + $this->root = $this->base . $this->root_path; + $this->url = $this->base . $this->uri; + // get any params and remove them $uri = str_replace($this->root, '', $this->url); @@ -454,6 +470,16 @@ class Uri return $this->host; } + /** + * Return the port number + * + * @return int + */ + public function port() + { + return $this->port; + } + /** * Gets the environment name * diff --git a/system/src/Grav/Common/Utils.php b/system/src/Grav/Common/Utils.php index d8a3b814a..eb88c1937 100644 --- a/system/src/Grav/Common/Utils.php +++ b/system/src/Grav/Common/Utils.php @@ -473,6 +473,8 @@ abstract class Utils /** * Generates a nonce string to be hashed. Called by self::getNonce() + * We removed the IP portion in this version because it causes too many inconsistencies + * with reverse proxy setups. * * @param string $action * @param bool $plusOneTick if true, generates the token for the next tick (the next 12 hours) @@ -481,22 +483,12 @@ abstract class Utils */ private static function generateNonceString($action, $plusOneTick = false) { - if (!empty($_SERVER['HTTP_CLIENT_IP'])) { - $ip = $_SERVER['HTTP_CLIENT_IP']; - } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; - } else { - $ip = $_SERVER['REMOTE_ADDR']; - } - $username = ''; if (isset(self::getGrav()['user'])) { $user = self::getGrav()['user']; $username = $user->username; } - $username .= $ip; - $token = session_id(); $i = self::nonceTick(); @@ -587,6 +579,11 @@ abstract class Utils */ public static function verifyNonce($nonce, $action) { + //Safety check for multiple nonces + if (is_array($nonce)) { + $nonce = array_shift($nonce); + } + //Nonce generated 0-12 hours ago if ($nonce == self::getNonce($action)) { return true; diff --git a/system/src/Grav/Console/Gpm/InfoCommand.php b/system/src/Grav/Console/Gpm/InfoCommand.php index 0ff0850bb..99f8bd2e2 100644 --- a/system/src/Grav/Console/Gpm/InfoCommand.php +++ b/system/src/Grav/Console/Gpm/InfoCommand.php @@ -5,6 +5,7 @@ use Grav\Common\GPM\GPM; use Grav\Console\ConsoleCommand; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Question\ConfirmationQuestion; /** * Class InfoCommand @@ -34,6 +35,12 @@ class InfoCommand extends ConsoleCommand InputOption::VALUE_NONE, 'Force fetching the new data remotely' ) + ->addOption( + 'all-yes', + 'y', + InputOption::VALUE_NONE, + 'Assumes yes (or best approach) instead of prompting' + ) ->addArgument( 'package', InputArgument::REQUIRED, @@ -107,9 +114,62 @@ class InfoCommand extends ConsoleCommand } } + $type = rtrim($foundPackage->package_type, 's'); + $updatable = $this->gpm->{'is' . $type . 'Updatable'}($foundPackage->slug); + $installed = $this->gpm->{'is' . $type . 'Installed'}($foundPackage->slug); + + // display current version if installed and different + if ($installed && $updatable) { + $local = $this->gpm->{'getInstalled'. $type}($foundPackage->slug); + $this->output->writeln(''); + $this->output->writeln("Currently installed version: " . $local->version . ""); + $this->output->writeln(''); + } + + // display changelog information + $questionHelper = $this->getHelper('question'); + $skipPrompt = $this->input->getOption('all-yes'); + + if (!$skipPrompt) { + $question = new ConfirmationQuestion("Would you like to read the changelog? [y|N] ", + false); + $answer = $questionHelper->ask($this->input, $this->output, $question); + + if ($answer) { + $changelog = $foundPackage->changelog; + + $this->output->writeln(""); + foreach ($changelog as $version => $log) { + $title = $version . ' [' . $log['date'] . ']'; + $content = preg_replace_callback("/\d\.\s\[\]\(#(.*)\)/", function ($match) { + return "\n" . ucfirst($match[1]) . ":"; + }, $log['content']); + + $this->output->writeln(''.$title.''); + $this->output->writeln(str_repeat('-', strlen($title))); + $this->output->writeln($content); + $this->output->writeln(""); + + $question = new ConfirmationQuestion("Press [ENTER] to continue or [q] to quit ", true); + if (!$questionHelper->ask($this->input, $this->output, $question)) { + break; + } + $this->output->writeln(""); + } + } + } + + $this->output->writeln(''); - $this->output->writeln("You can install this package by typing:"); - $this->output->writeln(" " . $this->argv . " install " . $foundPackage->slug . ""); + + if ($installed && $updatable) { + $this->output->writeln("You can update this package by typing:"); + $this->output->writeln(" " . $this->argv . " update " . $foundPackage->slug . ""); + } else { + $this->output->writeln("You can install this package by typing:"); + $this->output->writeln(" " . $this->argv . " install " . $foundPackage->slug . ""); + } + $this->output->writeln(''); }