first commit
This commit is contained in:
36
system/src/Grav/Common/GPM/AbstractCollection.php
Normal file
36
system/src/Grav/Common/GPM/AbstractCollection.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
abstract class AbstractCollection extends Iterator
|
||||
{
|
||||
public function toJson()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $package) {
|
||||
$items[$name] = $package->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $package) {
|
||||
$items[$name] = $package->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
abstract class AbstractPackageCollection extends Iterator
|
||||
{
|
||||
protected $type;
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $package) {
|
||||
$items[$name] = $package->toArray();
|
||||
}
|
||||
|
||||
return json_encode($items);
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $name => $package) {
|
||||
$items[$name] = $package->toArray();
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
28
system/src/Grav/Common/GPM/Common/CachedCollection.php
Normal file
28
system/src/Grav/Common/GPM/Common/CachedCollection.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Iterator;
|
||||
|
||||
class CachedCollection extends Iterator {
|
||||
|
||||
protected static $cache;
|
||||
|
||||
public function __construct($items)
|
||||
{
|
||||
// local cache to speed things up
|
||||
if (!isset(self::$cache[get_called_class().__METHOD__])) {
|
||||
self::$cache[get_called_class().__METHOD__] = $items;
|
||||
}
|
||||
|
||||
foreach (self::$cache[get_called_class().__METHOD__] as $name => $item) {
|
||||
$this->append([$name => $item]);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
system/src/Grav/Common/GPM/Common/Package.php
Normal file
49
system/src/Grav/Common/GPM/Common/Package.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Common;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
|
||||
class Package {
|
||||
|
||||
protected $data;
|
||||
|
||||
public function __construct(Data $package, $type = null) {
|
||||
$this->data = $package;
|
||||
|
||||
if ($type) {
|
||||
$this->data->set('package_type', $type);
|
||||
}
|
||||
}
|
||||
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
return $this->data->get($key);
|
||||
}
|
||||
|
||||
public function __isset($key) {
|
||||
return isset($this->data->$key);
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->toJson();
|
||||
}
|
||||
|
||||
public function toJson() {
|
||||
return $this->data->toJson();
|
||||
}
|
||||
|
||||
public function toArray() {
|
||||
return $this->data->toArray();
|
||||
}
|
||||
|
||||
}
|
||||
1153
system/src/Grav/Common/GPM/GPM.php
Normal file
1153
system/src/Grav/Common/GPM/GPM.php
Normal file
File diff suppressed because it is too large
Load Diff
533
system/src/Grav/Common/GPM/Installer.php
Normal file
533
system/src/Grav/Common/GPM/Installer.php
Normal file
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Filesystem\Folder;
|
||||
use Grav\Common\Grav;
|
||||
|
||||
class Installer
|
||||
{
|
||||
/** @const No error */
|
||||
const OK = 0;
|
||||
/** @const Target already exists */
|
||||
const EXISTS = 1;
|
||||
/** @const Target is a symbolic link */
|
||||
const IS_LINK = 2;
|
||||
/** @const Target doesn't exist */
|
||||
const NOT_FOUND = 4;
|
||||
/** @const Target is not a directory */
|
||||
const NOT_DIRECTORY = 8;
|
||||
/** @const Target is not a Grav instance */
|
||||
const NOT_GRAV_ROOT = 16;
|
||||
/** @const Error while trying to open the ZIP package */
|
||||
const ZIP_OPEN_ERROR = 32;
|
||||
/** @const Error while trying to extract the ZIP package */
|
||||
const ZIP_EXTRACT_ERROR = 64;
|
||||
/** @const Invalid source file */
|
||||
const INVALID_SOURCE = 128;
|
||||
|
||||
/**
|
||||
* Destination folder on which validation checks are applied
|
||||
* @var string
|
||||
*/
|
||||
protected static $target;
|
||||
|
||||
/**
|
||||
* @var integer Error Code
|
||||
*/
|
||||
protected static $error = 0;
|
||||
|
||||
/**
|
||||
* @var integer Zip Error Code
|
||||
*/
|
||||
protected static $error_zip = 0;
|
||||
|
||||
/**
|
||||
* @var string Post install message
|
||||
*/
|
||||
protected static $message = '';
|
||||
|
||||
/**
|
||||
* Default options for the install
|
||||
* @var array
|
||||
*/
|
||||
protected static $options = [
|
||||
'overwrite' => true,
|
||||
'ignore_symlinks' => true,
|
||||
'sophisticated' => false,
|
||||
'theme' => false,
|
||||
'install_path' => '',
|
||||
'ignores' => [],
|
||||
'exclude_checks' => [self::EXISTS, self::NOT_FOUND, self::IS_LINK]
|
||||
];
|
||||
|
||||
/**
|
||||
* Installs a given package to a given destination.
|
||||
*
|
||||
* @param string $zip the local path to ZIP package
|
||||
* @param string $destination The local path to the Grav Instance
|
||||
* @param array $options Options to use for installing. ie, ['install_path' => 'user/themes/antimatter']
|
||||
* @param string $extracted The local path to the extacted ZIP package
|
||||
* @return bool True if everything went fine, False otherwise.
|
||||
*/
|
||||
public static function install($zip, $destination, $options = [], $extracted = null)
|
||||
{
|
||||
$destination = rtrim($destination, DS);
|
||||
$options = array_merge(self::$options, $options);
|
||||
$install_path = rtrim($destination . DS . ltrim($options['install_path'], DS), DS);
|
||||
|
||||
if (!self::isGravInstance($destination) || !self::isValidDestination($install_path,
|
||||
$options['exclude_checks'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self::lastErrorCode() == self::IS_LINK && $options['ignore_symlinks'] ||
|
||||
self::lastErrorCode() == self::EXISTS && !$options['overwrite']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a tmp location
|
||||
$tmp_dir = Grav::instance()['locator']->findResource('tmp://', true, true);
|
||||
$tmp = $tmp_dir . '/Grav-' . uniqid();
|
||||
|
||||
if (!$extracted) {
|
||||
$extracted = self::unZip($zip, $tmp);
|
||||
if (!$extracted) {
|
||||
Folder::delete($tmp);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists($extracted)) {
|
||||
self::$error = self::INVALID_SOURCE;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
$is_install = true;
|
||||
$installer = self::loadInstaller($extracted, $is_install);
|
||||
|
||||
if (isset($options['is_update']) && $options['is_update'] === true) {
|
||||
$method = 'preUpdate';
|
||||
} else {
|
||||
$method = 'preInstall';
|
||||
}
|
||||
|
||||
if ($installer && method_exists($installer, $method)) {
|
||||
$method_result = $installer::$method();
|
||||
if ($method_result !== true) {
|
||||
self::$error = 'An error occurred';
|
||||
if (is_string($method_result)) {
|
||||
self::$error = $method_result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$options['sophisticated']) {
|
||||
if ($options['theme']) {
|
||||
self::copyInstall($extracted, $install_path);
|
||||
} else {
|
||||
self::moveInstall($extracted, $install_path);
|
||||
}
|
||||
} else {
|
||||
self::sophisticatedInstall($extracted, $install_path, $options['ignores']);
|
||||
}
|
||||
|
||||
Folder::delete($tmp);
|
||||
|
||||
if (isset($options['is_update']) && $options['is_update'] === true) {
|
||||
$method = 'postUpdate';
|
||||
} else {
|
||||
$method = 'postInstall';
|
||||
}
|
||||
|
||||
self::$message = '';
|
||||
if ($installer && method_exists($installer, $method)) {
|
||||
self::$message = $installer::$method();
|
||||
}
|
||||
|
||||
self::$error = self::OK;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzip a file to somewhere
|
||||
*
|
||||
* @param $zip_file
|
||||
* @param $destination
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function unZip($zip_file, $destination)
|
||||
{
|
||||
$zip = new \ZipArchive();
|
||||
$archive = $zip->open($zip_file);
|
||||
|
||||
if ($archive === true) {
|
||||
Folder::mkdir($destination);
|
||||
|
||||
$unzip = $zip->extractTo($destination);
|
||||
|
||||
|
||||
if (!$unzip) {
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
Folder::delete($destination);
|
||||
$zip->close();
|
||||
return false;
|
||||
}
|
||||
|
||||
$package_folder_name = preg_replace('#\./$#', '', $zip->getNameIndex(0));
|
||||
$zip->close();
|
||||
$extracted_folder = $destination . '/' . $package_folder_name;
|
||||
|
||||
return $extracted_folder;
|
||||
}
|
||||
|
||||
self::$error = self::ZIP_EXTRACT_ERROR;
|
||||
self::$error_zip = $archive;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates and returns the package installer class
|
||||
*
|
||||
* @param string $installer_file_folder The folder path that contains install.php
|
||||
* @param bool $is_install True if install, false if removal
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
private static function loadInstaller($installer_file_folder, $is_install)
|
||||
{
|
||||
$installer = null;
|
||||
|
||||
$installer_file_folder = rtrim($installer_file_folder, DS);
|
||||
|
||||
$install_file = $installer_file_folder . DS . 'install.php';
|
||||
|
||||
if (file_exists($install_file)) {
|
||||
require_once($install_file);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($is_install) {
|
||||
$slug = '';
|
||||
if (($pos = strpos($installer_file_folder, 'grav-plugin-')) !== false) {
|
||||
$slug = substr($installer_file_folder, $pos + strlen('grav-plugin-'));
|
||||
} elseif (($pos = strpos($installer_file_folder, 'grav-theme-')) !== false) {
|
||||
$slug = substr($installer_file_folder, $pos + strlen('grav-theme-'));
|
||||
}
|
||||
} else {
|
||||
$path_elements = explode('/', $installer_file_folder);
|
||||
$slug = end($path_elements);
|
||||
}
|
||||
|
||||
if (!$slug) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$class_name = ucfirst($slug) . 'Install';
|
||||
|
||||
if (class_exists($class_name)) {
|
||||
return $class_name;
|
||||
}
|
||||
|
||||
$class_name_alphanumeric = preg_replace('/[^a-zA-Z0-9]+/', '', $class_name);
|
||||
|
||||
if (class_exists($class_name_alphanumeric)) {
|
||||
return $class_name_alphanumeric;
|
||||
}
|
||||
|
||||
return $installer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $source_path
|
||||
* @param $install_path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function moveInstall($source_path, $install_path)
|
||||
{
|
||||
if (file_exists($install_path)) {
|
||||
Folder::delete($install_path);
|
||||
}
|
||||
|
||||
Folder::move($source_path, $install_path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $source_path
|
||||
* @param $install_path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function copyInstall($source_path, $install_path)
|
||||
{
|
||||
if (empty($source_path)) {
|
||||
throw new \RuntimeException("Directory $source_path is missing");
|
||||
} else {
|
||||
Folder::rcopy($source_path, $install_path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $source_path
|
||||
* @param $install_path
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function sophisticatedInstall($source_path, $install_path, $ignores = [])
|
||||
{
|
||||
foreach (new \DirectoryIterator($source_path) as $file) {
|
||||
|
||||
if ($file->isLink() || $file->isDot() || in_array($file->getFilename(), $ignores)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $install_path . DS . $file->getFilename();
|
||||
|
||||
if ($file->isDir()) {
|
||||
Folder::delete($path);
|
||||
Folder::move($file->getPathname(), $path);
|
||||
|
||||
if ($file->getFilename() === 'bin') {
|
||||
foreach (glob($path . DS . '*') as $bin_file) {
|
||||
@chmod($bin_file, 0755);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@unlink($path);
|
||||
@copy($file->getPathname(), $path);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls one or more given package
|
||||
*
|
||||
* @param string $path The slug of the package(s)
|
||||
* @param array $options Options to use for uninstalling
|
||||
*
|
||||
* @return boolean True if everything went fine, False otherwise.
|
||||
*/
|
||||
public static function uninstall($path, $options = [])
|
||||
{
|
||||
$options = array_merge(self::$options, $options);
|
||||
if (!self::isValidDestination($path, $options['exclude_checks'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$installer_file_folder = $path;
|
||||
$is_install = false;
|
||||
$installer = self::loadInstaller($installer_file_folder, $is_install);
|
||||
|
||||
if ($installer && method_exists($installer, 'preUninstall')) {
|
||||
$method_result = $installer::preUninstall();
|
||||
if ($method_result !== true) {
|
||||
self::$error = 'An error occurred';
|
||||
if (is_string($method_result)) {
|
||||
self::$error = $method_result;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$result = Folder::delete($path);
|
||||
|
||||
self::$message = '';
|
||||
if ($result && $installer && method_exists($installer, 'postUninstall')) {
|
||||
self::$message = $installer::postUninstall();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a set of checks on the destination and sets the Error if any
|
||||
*
|
||||
* @param string $destination The directory to run validations at
|
||||
* @param array $exclude An array of constants to exclude from the validation
|
||||
*
|
||||
* @return boolean True if validation passed. False otherwise
|
||||
*/
|
||||
public static function isValidDestination($destination, $exclude = [])
|
||||
{
|
||||
self::$error = 0;
|
||||
self::$target = $destination;
|
||||
|
||||
if (is_link($destination)) {
|
||||
self::$error = self::IS_LINK;
|
||||
} elseif (file_exists($destination)) {
|
||||
self::$error = self::EXISTS;
|
||||
} elseif (!file_exists($destination)) {
|
||||
self::$error = self::NOT_FOUND;
|
||||
} elseif (!is_dir($destination)) {
|
||||
self::$error = self::NOT_DIRECTORY;
|
||||
}
|
||||
|
||||
if (count($exclude) && in_array(self::$error, $exclude)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !(self::$error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the given path is a Grav Instance
|
||||
*
|
||||
* @param string $target The local path to the Grav Instance
|
||||
*
|
||||
* @return boolean True if is a Grav Instance. False otherwise
|
||||
*/
|
||||
public static function isGravInstance($target)
|
||||
{
|
||||
self::$error = 0;
|
||||
self::$target = $target;
|
||||
|
||||
if (
|
||||
!file_exists($target . DS . 'index.php') ||
|
||||
!file_exists($target . DS . 'bin') ||
|
||||
!file_exists($target . DS . 'user') ||
|
||||
!file_exists($target . DS . 'system' . DS . 'config' . DS . 'system.yaml')
|
||||
) {
|
||||
self::$error = self::NOT_GRAV_ROOT;
|
||||
}
|
||||
|
||||
return !self::$error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last message added by the installer
|
||||
* @return string The message
|
||||
*/
|
||||
public static function getMessage()
|
||||
{
|
||||
return self::$message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error occurred in a string message format
|
||||
* @return string The message of the last error
|
||||
*/
|
||||
public static function lastErrorMsg()
|
||||
{
|
||||
if (is_string(self::$error)) {
|
||||
return self::$error;
|
||||
}
|
||||
|
||||
switch (self::$error) {
|
||||
case 0:
|
||||
$msg = 'No Error';
|
||||
break;
|
||||
|
||||
case self::EXISTS:
|
||||
$msg = 'The target path "' . self::$target . '" already exists';
|
||||
break;
|
||||
|
||||
case self::IS_LINK:
|
||||
$msg = 'The target path "' . self::$target . '" is a symbolic link';
|
||||
break;
|
||||
|
||||
case self::NOT_FOUND:
|
||||
$msg = 'The target path "' . self::$target . '" does not appear to exist';
|
||||
break;
|
||||
|
||||
case self::NOT_DIRECTORY:
|
||||
$msg = 'The target path "' . self::$target . '" does not appear to be a folder';
|
||||
break;
|
||||
|
||||
case self::NOT_GRAV_ROOT:
|
||||
$msg = 'The target path "' . self::$target . '" does not appear to be a Grav instance';
|
||||
break;
|
||||
|
||||
case self::ZIP_OPEN_ERROR:
|
||||
$msg = 'Unable to open the package file';
|
||||
break;
|
||||
|
||||
case self::ZIP_EXTRACT_ERROR:
|
||||
$msg = 'Unable to extract the package. ';
|
||||
if (self::$error_zip) {
|
||||
switch(self::$error_zip) {
|
||||
case \ZipArchive::ER_EXISTS:
|
||||
$msg .= "File already exists.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_INCONS:
|
||||
$msg .= "Zip archive inconsistent.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_MEMORY:
|
||||
$msg .= "Malloc failure.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_NOENT:
|
||||
$msg .= "No such file.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_NOZIP:
|
||||
$msg .= "Not a zip archive.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_OPEN:
|
||||
$msg .= "Can't open file.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_READ:
|
||||
$msg .= "Read error.";
|
||||
break;
|
||||
|
||||
case \ZipArchive::ER_SEEK:
|
||||
$msg .= "Seek error.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$msg = 'Unknown Error';
|
||||
break;
|
||||
}
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last error code of the occurred error
|
||||
* @return integer The code of the last error
|
||||
*/
|
||||
public static function lastErrorCode()
|
||||
{
|
||||
return self::$error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to manually set an error
|
||||
*
|
||||
* @param int|string $error the Error code
|
||||
*/
|
||||
|
||||
public static function setError($error)
|
||||
{
|
||||
self::$error = $error;
|
||||
}
|
||||
}
|
||||
126
system/src/Grav/Common/GPM/Licenses.php
Normal file
126
system/src/Grav/Common/GPM/Licenses.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\File\CompiledYamlFile;
|
||||
use Grav\Common\Grav;
|
||||
|
||||
/**
|
||||
* Class Licenses
|
||||
*
|
||||
* @package Grav\Common\GPM
|
||||
*/
|
||||
class Licenses
|
||||
{
|
||||
|
||||
/**
|
||||
* Regex to validate the format of a License
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $regex = '^(?:[A-F0-9]{8}-){3}(?:[A-F0-9]{8}){1}$';
|
||||
|
||||
protected static $file;
|
||||
|
||||
|
||||
/**
|
||||
* Returns the license for a Premium package
|
||||
*
|
||||
* @param $slug
|
||||
* @param $license
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function set($slug, $license)
|
||||
{
|
||||
$licenses = self::getLicenseFile();
|
||||
$data = $licenses->content();
|
||||
$slug = strtolower($slug);
|
||||
|
||||
if ($license && !self::validate($license)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_string($license)) {
|
||||
if (isset($data['licenses'][$slug])) {
|
||||
unset($data['licenses'][$slug]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
$data['licenses'][$slug] = $license;
|
||||
}
|
||||
|
||||
$licenses->save($data);
|
||||
$licenses->free();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the license for a Premium package
|
||||
*
|
||||
* @param $slug
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get($slug = null)
|
||||
{
|
||||
$licenses = self::getLicenseFile();
|
||||
$data = $licenses->content();
|
||||
$licenses->free();
|
||||
$slug = strtolower($slug);
|
||||
|
||||
if (!$slug) {
|
||||
return isset($data['licenses']) ? $data['licenses'] : [];
|
||||
}
|
||||
|
||||
if (!isset($data['licenses']) || !isset($data['licenses'][$slug])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $data['licenses'][$slug];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the License format
|
||||
*
|
||||
* @param $license
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function validate($license = null)
|
||||
{
|
||||
if (!is_string($license)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return preg_match('#' . self::$regex. '#', $license);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the License File object
|
||||
*
|
||||
* @return \RocketTheme\Toolbox\File\FileInterface
|
||||
*/
|
||||
public static function getLicenseFile()
|
||||
|
||||
{
|
||||
if (!isset(self::$file)) {
|
||||
$path = Grav::instance()['locator']->findResource('user://data') . '/licenses.yaml';
|
||||
if (!file_exists($path)) {
|
||||
touch($path);
|
||||
}
|
||||
self::$file = CompiledYamlFile::instance($path);
|
||||
}
|
||||
|
||||
return self::$file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
|
||||
|
||||
abstract class AbstractPackageCollection extends BaseCollection
|
||||
{
|
||||
public function __construct($items)
|
||||
{
|
||||
foreach ($items as $name => $data) {
|
||||
$data->set('slug', $name);
|
||||
$this->items[$name] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
system/src/Grav/Common/GPM/Local/Package.php
Normal file
39
system/src/Grav/Common/GPM/Local/Package.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\GPM\Common\Package as BasePackage;
|
||||
|
||||
class Package extends BasePackage
|
||||
{
|
||||
protected $settings;
|
||||
|
||||
public function __construct(Data $package, $package_type = null)
|
||||
{
|
||||
$data = new Data($package->blueprints()->toArray());
|
||||
parent::__construct($data, $package_type);
|
||||
|
||||
$this->settings = $package->toArray();
|
||||
|
||||
$html_description = \Parsedown::instance()->line($this->description);
|
||||
$this->data->set('slug', $package->slug);
|
||||
$this->data->set('description_html', $html_description);
|
||||
$this->data->set('description_plain', strip_tags($html_description));
|
||||
$this->data->set('symlink', is_link(USER_DIR . $package_type . DS . $this->slug));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->settings['enabled'];
|
||||
}
|
||||
}
|
||||
24
system/src/Grav/Common/GPM/Local/Packages.php
Normal file
24
system/src/Grav/Common/GPM/Local/Packages.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\GPM\Common\CachedCollection;
|
||||
|
||||
class Packages extends CachedCollection
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$items = [
|
||||
'plugins' => new Plugins(),
|
||||
'themes' => new Themes()
|
||||
];
|
||||
|
||||
parent::__construct($items);
|
||||
}
|
||||
}
|
||||
29
system/src/Grav/Common/GPM/Local/Plugins.php
Normal file
29
system/src/Grav/Common/GPM/Local/Plugins.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
|
||||
class Plugins extends AbstractPackageCollection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'plugins';
|
||||
|
||||
/**
|
||||
* Local Plugins Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
/** @var \Grav\Common\Plugins $plugins */
|
||||
$plugins = Grav::instance()['plugins'];
|
||||
parent::__construct($plugins->all());
|
||||
}
|
||||
}
|
||||
27
system/src/Grav/Common/GPM/Local/Themes.php
Normal file
27
system/src/Grav/Common/GPM/Local/Themes.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Local;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
|
||||
class Themes extends AbstractPackageCollection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'themes';
|
||||
|
||||
/**
|
||||
* Local Themes Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct(Grav::instance()['themes']->all());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
|
||||
use Grav\Common\GPM\Response;
|
||||
use \Doctrine\Common\Cache\FilesystemCache;
|
||||
|
||||
class AbstractPackageCollection extends BaseCollection
|
||||
{
|
||||
/**
|
||||
* The cached data previously fetched
|
||||
* @var string
|
||||
*/
|
||||
protected $raw;
|
||||
|
||||
/**
|
||||
* The lifetime to store the entry in seconds
|
||||
* @var integer
|
||||
*/
|
||||
private $lifetime = 86400;
|
||||
|
||||
protected $repository;
|
||||
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* AbstractPackageCollection constructor.
|
||||
*
|
||||
* @param null $repository
|
||||
* @param bool $refresh
|
||||
* @param null $callback
|
||||
*/
|
||||
public function __construct($repository = null, $refresh = false, $callback = null)
|
||||
{
|
||||
if ($repository === null) {
|
||||
throw new \RuntimeException("A repository is required to indicate the origin of the remote collection");
|
||||
}
|
||||
|
||||
$channel = Grav::instance()['config']->get('system.gpm.releases', 'stable');
|
||||
$cache_dir = Grav::instance()['locator']->findResource('cache://gpm', true, true);
|
||||
$this->cache = new FilesystemCache($cache_dir);
|
||||
|
||||
$this->repository = $repository . '?v=' . GRAV_VERSION . '&' . $channel . '=1';
|
||||
$this->raw = $this->cache->fetch(md5($this->repository));
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
foreach (json_decode($this->raw, true) as $slug => $data) {
|
||||
// Temporarily fix for using multisites
|
||||
if (isset($data['install_path'])) {
|
||||
$path = preg_replace('~^user/~i', 'user://', $data['install_path']);
|
||||
$data['install_path'] = Grav::instance()['locator']->findResource($path, false, true);
|
||||
}
|
||||
$this->items[$slug] = new Package($data, $this->type);
|
||||
}
|
||||
}
|
||||
|
||||
public function fetch($refresh = false, $callback = null)
|
||||
{
|
||||
if (!$this->raw || $refresh) {
|
||||
$response = Response::get($this->repository, [], $callback);
|
||||
$this->raw = $response;
|
||||
$this->cache->save(md5($this->repository), $this->raw, $this->lifetime);
|
||||
}
|
||||
|
||||
return $this->raw;
|
||||
}
|
||||
}
|
||||
140
system/src/Grav/Common/GPM/Remote/GravCore.php
Normal file
140
system/src/Grav/Common/GPM/Remote/GravCore.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use \Doctrine\Common\Cache\FilesystemCache;
|
||||
|
||||
class GravCore extends AbstractPackageCollection
|
||||
{
|
||||
protected $repository = 'https://getgrav.org/downloads/grav.json';
|
||||
private $data;
|
||||
|
||||
private $version;
|
||||
private $date;
|
||||
private $min_php;
|
||||
|
||||
/**
|
||||
* @param bool $refresh
|
||||
* @param null $callback
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
$channel = Grav::instance()['config']->get('system.gpm.releases', 'stable');
|
||||
$cache_dir = Grav::instance()['locator']->findResource('cache://gpm', true, true);
|
||||
$this->cache = new FilesystemCache($cache_dir);
|
||||
$this->repository .= '?v=' . GRAV_VERSION . '&' . $channel . '=1';
|
||||
$this->raw = $this->cache->fetch(md5($this->repository));
|
||||
|
||||
$this->fetch($refresh, $callback);
|
||||
|
||||
$this->data = json_decode($this->raw, true);
|
||||
$this->version = isset($this->data['version']) ? $this->data['version'] : '-';
|
||||
$this->date = isset($this->data['date']) ? $this->data['date'] : '-';
|
||||
$this->min_php = isset($this->data['min_php']) ? $this->data['min_php'] : null;
|
||||
|
||||
if (isset($this->data['assets'])) {
|
||||
foreach ((array)$this->data['assets'] as $slug => $data) {
|
||||
$this->items[$slug] = new Package($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of assets associated to the latest version of Grav
|
||||
*
|
||||
* @return array list of assets
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return $this->data['assets'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the changelog list for each version of Grav
|
||||
*
|
||||
* @param string $diff the version number to start the diff from
|
||||
*
|
||||
* @return array changelog list for each version
|
||||
*/
|
||||
public function getChangelog($diff = null)
|
||||
{
|
||||
if (!$diff) {
|
||||
return $this->data['changelog'];
|
||||
}
|
||||
|
||||
$diffLog = [];
|
||||
foreach ((array)$this->data['changelog'] as $version => $changelog) {
|
||||
preg_match("/[\w-\.]+/", $version, $cleanVersion);
|
||||
|
||||
if (!$cleanVersion || version_compare($diff, $cleanVersion[0], '>=')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diffLog[$version] = $changelog;
|
||||
}
|
||||
|
||||
return $diffLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the release date of the latest Grav
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDate()
|
||||
{
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this version of Grav is eligible to be updated
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function isUpdatable()
|
||||
{
|
||||
return version_compare(GRAV_VERSION, $this->getVersion(), '<');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest version of Grav available remotely
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getVersion()
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum PHP version
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getMinPHPVersion()
|
||||
{
|
||||
// If non min set, assume current PHP version
|
||||
if (is_null($this->min_php)) {
|
||||
$this->min_php = phpversion();
|
||||
}
|
||||
return $this->min_php;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this installation symlinked?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSymlink()
|
||||
{
|
||||
return is_link(GRAV_ROOT . DS . 'index.php');
|
||||
}
|
||||
}
|
||||
19
system/src/Grav/Common/GPM/Remote/Package.php
Normal file
19
system/src/Grav/Common/GPM/Remote/Package.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\Data\Data;
|
||||
use Grav\Common\GPM\Common\Package as BasePackage;
|
||||
|
||||
class Package extends BasePackage {
|
||||
public function __construct($package, $package_type = null) {
|
||||
$data = new Data($package);
|
||||
parent::__construct($data, $package_type);
|
||||
}
|
||||
}
|
||||
24
system/src/Grav/Common/GPM/Remote/Packages.php
Normal file
24
system/src/Grav/Common/GPM/Remote/Packages.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
use Grav\Common\GPM\Common\CachedCollection;
|
||||
|
||||
class Packages extends CachedCollection
|
||||
{
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
$items = [
|
||||
'plugins' => new Plugins($refresh, $callback),
|
||||
'themes' => new Themes($refresh, $callback)
|
||||
];
|
||||
|
||||
parent::__construct($items);
|
||||
}
|
||||
}
|
||||
29
system/src/Grav/Common/GPM/Remote/Plugins.php
Normal file
29
system/src/Grav/Common/GPM/Remote/Plugins.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Plugins extends AbstractPackageCollection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'plugins';
|
||||
|
||||
protected $repository = 'https://getgrav.org/downloads/plugins.json';
|
||||
|
||||
/**
|
||||
* Local Plugins Constructor
|
||||
* @param bool $refresh
|
||||
* @param callable $callback Either a function or callback in array notation
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository, $refresh, $callback);
|
||||
}
|
||||
}
|
||||
29
system/src/Grav/Common/GPM/Remote/Themes.php
Normal file
29
system/src/Grav/Common/GPM/Remote/Themes.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM\Remote;
|
||||
|
||||
class Themes extends AbstractPackageCollection
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'themes';
|
||||
|
||||
protected $repository = 'https://getgrav.org/downloads/themes.json';
|
||||
|
||||
/**
|
||||
* Local Themes Constructor
|
||||
* @param bool $refresh
|
||||
* @param callable $callback Either a function or callback in array notation
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
parent::__construct($this->repository, $refresh, $callback);
|
||||
}
|
||||
}
|
||||
432
system/src/Grav/Common/GPM/Response.php
Normal file
432
system/src/Grav/Common/GPM/Response.php
Normal file
@@ -0,0 +1,432 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\Utils;
|
||||
use Grav\Common\Grav;
|
||||
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* The callback for the progress
|
||||
*
|
||||
* @var callable Either a function or callback in array notation
|
||||
*/
|
||||
public static $callback = null;
|
||||
|
||||
/**
|
||||
* Which method to use for HTTP calls, can be 'curl', 'fopen' or 'auto'. Auto is default and fopen is the preferred method
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static $method = 'auto';
|
||||
|
||||
/**
|
||||
* Default parameters for `curl` and `fopen`
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $defaults = [
|
||||
|
||||
'curl' => [
|
||||
CURLOPT_REFERER => 'Grav GPM',
|
||||
CURLOPT_USERAGENT => 'Grav GPM',
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_HEADER => false,
|
||||
//CURLOPT_SSL_VERIFYPEER => true, // this is set in the constructor since it's a setting
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
//CURLOPT_NOPROGRESS => false,
|
||||
//CURLOPT_PROGRESSFUNCTION => [$this, 'progress']
|
||||
],
|
||||
'fopen' => [
|
||||
'method' => 'GET',
|
||||
'user_agent' => 'Grav GPM',
|
||||
'max_redirects' => 5,
|
||||
'follow_location' => 1,
|
||||
'timeout' => 15,
|
||||
/* // this is set in the constructor since it's a setting
|
||||
'ssl' => [
|
||||
'verify_peer' => true,
|
||||
'verify_peer_name' => true,
|
||||
],
|
||||
*/
|
||||
/**
|
||||
* Example of callback parameters from within your own class
|
||||
*/
|
||||
//'notification' => [$this, 'progress']
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* Sets the preferred method to use for making HTTP calls.
|
||||
*
|
||||
* @param string $method Default is `auto`
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public static function setMethod($method = 'auto')
|
||||
{
|
||||
if (!in_array($method, ['auto', 'curl', 'fopen'])) {
|
||||
$method = 'auto';
|
||||
}
|
||||
|
||||
self::$method = $method;
|
||||
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request to the URL by using the preferred method
|
||||
*
|
||||
* @param string $uri URL to call
|
||||
* @param array $options An array of parameters for both `curl` and `fopen`
|
||||
* @param callable $callback Either a function or callback in array notation
|
||||
*
|
||||
* @return string The response of the request
|
||||
*/
|
||||
public static function get($uri = '', $options = [], $callback = null)
|
||||
{
|
||||
if (!self::isCurlAvailable() && !self::isFopenAvailable()) {
|
||||
throw new \RuntimeException('Could not start an HTTP request. `allow_url_open` is disabled and `cURL` is not available');
|
||||
}
|
||||
|
||||
// check if this function is available, if so use it to stop any timeouts
|
||||
try {
|
||||
if (!Utils::isFunctionDisabled('set_time_limit') && !ini_get('safe_mode') && function_exists('set_time_limit')) {
|
||||
set_time_limit(0);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
$config = Grav::instance()['config'];
|
||||
$overrides = [];
|
||||
|
||||
// Override CA Bundle
|
||||
$caPathOrFile = \Composer\CaBundle\CaBundle::getSystemCaRootBundlePath();
|
||||
if (is_dir($caPathOrFile) || (is_link($caPathOrFile) && is_dir(readlink($caPathOrFile)))) {
|
||||
$overrides['curl'][CURLOPT_CAPATH] = $caPathOrFile;
|
||||
$overrides['fopen']['ssl']['capath'] = $caPathOrFile;
|
||||
} else {
|
||||
$overrides['curl'][CURLOPT_CAINFO] = $caPathOrFile;
|
||||
$overrides['fopen']['ssl']['cafile'] = $caPathOrFile;
|
||||
}
|
||||
|
||||
// SSL Verify Peer and Proxy Setting
|
||||
$settings = [
|
||||
'method' => $config->get('system.gpm.method', self::$method),
|
||||
'verify_peer' => $config->get('system.gpm.verify_peer', true),
|
||||
// `system.proxy_url` is for fallback
|
||||
// introduced with 1.1.0-beta.1 probably safe to remove at some point
|
||||
'proxy_url' => $config->get('system.gpm.proxy_url', $config->get('system.proxy_url', false)),
|
||||
];
|
||||
|
||||
if (!$settings['verify_peer']) {
|
||||
$overrides = array_replace_recursive([], $overrides, [
|
||||
'curl' => [
|
||||
CURLOPT_SSL_VERIFYPEER => $settings['verify_peer']
|
||||
],
|
||||
'fopen' => [
|
||||
'ssl' => [
|
||||
'verify_peer' => $settings['verify_peer'],
|
||||
'verify_peer_name' => $settings['verify_peer'],
|
||||
]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// Proxy Setting
|
||||
if ($settings['proxy_url']) {
|
||||
$proxy = parse_url($settings['proxy_url']);
|
||||
$fopen_proxy = ($proxy['scheme'] ?: 'http') . '://' . $proxy['host'] . (isset($proxy['port']) ? ':' . $proxy['port'] : '');
|
||||
|
||||
$overrides = array_replace_recursive([], $overrides, [
|
||||
'curl' => [
|
||||
CURLOPT_PROXY => $proxy['host'],
|
||||
CURLOPT_PROXYTYPE => 'HTTP'
|
||||
],
|
||||
'fopen' => [
|
||||
'proxy' => $fopen_proxy,
|
||||
'request_fulluri' => true
|
||||
]
|
||||
]);
|
||||
|
||||
if (isset($proxy['port'])) {
|
||||
$overrides['curl'][CURLOPT_PROXYPORT] = $proxy['port'];
|
||||
}
|
||||
|
||||
if (isset($proxy['user']) && isset($proxy['pass'])) {
|
||||
$fopen_auth = $auth = base64_encode($proxy['user'] . ':' . $proxy['pass']);
|
||||
$overrides['curl'][CURLOPT_PROXYUSERPWD] = $proxy['user'] . ':' . $proxy['pass'];
|
||||
$overrides['fopen']['header'] = "Proxy-Authorization: Basic $fopen_auth";
|
||||
}
|
||||
}
|
||||
|
||||
$options = array_replace_recursive(self::$defaults, $options, $overrides);
|
||||
$method = 'get' . ucfirst(strtolower($settings['method']));
|
||||
|
||||
self::$callback = $callback;
|
||||
return static::$method($uri, $options, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cURL is available
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isCurlAvailable()
|
||||
{
|
||||
return function_exists('curl_version');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the remote fopen request is enabled in PHP
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isFopenAvailable()
|
||||
{
|
||||
return preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a remote file or not
|
||||
*
|
||||
* @param $file
|
||||
* @return bool
|
||||
*/
|
||||
public static function isRemote($file)
|
||||
{
|
||||
return (bool) filter_var($file, FILTER_VALIDATE_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress normalized for cURL and Fopen
|
||||
* Accepts a variable length of arguments passed in by stream method
|
||||
*/
|
||||
public static function progress()
|
||||
{
|
||||
static $filesize = null;
|
||||
|
||||
$args = func_get_args();
|
||||
$isCurlResource = is_resource($args[0]) && get_resource_type($args[0]) == 'curl';
|
||||
|
||||
$notification_code = !$isCurlResource ? $args[0] : false;
|
||||
$bytes_transferred = $isCurlResource ? $args[2] : $args[4];
|
||||
|
||||
if ($isCurlResource) {
|
||||
$filesize = $args[1];
|
||||
} elseif ($notification_code == STREAM_NOTIFY_FILE_SIZE_IS) {
|
||||
$filesize = $args[5];
|
||||
}
|
||||
|
||||
if ($bytes_transferred > 0) {
|
||||
if ($notification_code == STREAM_NOTIFY_PROGRESS | STREAM_NOTIFY_COMPLETED || $isCurlResource) {
|
||||
|
||||
$progress = [
|
||||
'code' => $notification_code,
|
||||
'filesize' => $filesize,
|
||||
'transferred' => $bytes_transferred,
|
||||
'percent' => $filesize <= 0 ? '-' : round(($bytes_transferred * 100) / $filesize, 1)
|
||||
];
|
||||
|
||||
if (self::$callback !== null) {
|
||||
call_user_func_array(self::$callback, [$progress]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically picks the preferred method
|
||||
*
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getAuto()
|
||||
{
|
||||
if (!ini_get('open_basedir') && self::isFopenAvailable()) {
|
||||
return self::getFopen(func_get_args());
|
||||
}
|
||||
|
||||
if (self::isCurlAvailable()) {
|
||||
return self::getCurl(func_get_args());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a HTTP request via fopen
|
||||
*
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getFopen()
|
||||
{
|
||||
if (count($args = func_get_args()) == 1) {
|
||||
$args = $args[0];
|
||||
}
|
||||
|
||||
$uri = $args[0];
|
||||
$options = $args[1];
|
||||
$callback = $args[2];
|
||||
|
||||
if ($callback) {
|
||||
$options['fopen']['notification'] = ['self', 'progress'];
|
||||
}
|
||||
|
||||
if (isset($options['fopen']['ssl'])) {
|
||||
$ssl = $options['fopen']['ssl'];
|
||||
unset($options['fopen']['ssl']);
|
||||
|
||||
$stream = stream_context_create([
|
||||
'http' => $options['fopen'],
|
||||
'ssl' => $ssl
|
||||
], $options['fopen']);
|
||||
} else {
|
||||
$stream = stream_context_create(['http' => $options['fopen']], $options['fopen']);
|
||||
}
|
||||
|
||||
|
||||
$content = @file_get_contents($uri, false, $stream);
|
||||
|
||||
if ($content === false) {
|
||||
$code = null;
|
||||
if (isset($http_response_header)) {
|
||||
$code = explode(' ', $http_response_header[0])[1];
|
||||
}
|
||||
|
||||
switch ($code) {
|
||||
case '404':
|
||||
throw new \RuntimeException("Page not found");
|
||||
case '401':
|
||||
throw new \RuntimeException("Invalid LICENSE");
|
||||
default:
|
||||
throw new \RuntimeException("Error while trying to download (code: $code): $uri \n");
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a HTTP request via cURL
|
||||
*
|
||||
* @return string The response of the request
|
||||
*/
|
||||
private static function getCurl()
|
||||
{
|
||||
$args = func_get_args();
|
||||
$args = count($args) > 1 ? $args : array_shift($args);
|
||||
|
||||
$uri = $args[0];
|
||||
$options = $args[1];
|
||||
$callback = $args[2];
|
||||
|
||||
$ch = curl_init($uri);
|
||||
|
||||
$response = static::curlExecFollow($ch, $options, $callback);
|
||||
$errno = curl_errno($ch);
|
||||
|
||||
if ($errno) {
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error_message = curl_strerror($errno) . "\n" . curl_error($ch);
|
||||
|
||||
switch ($code) {
|
||||
case '404':
|
||||
throw new \RuntimeException("Page not found");
|
||||
case '401':
|
||||
throw new \RuntimeException("Invalid LICENSE");
|
||||
default:
|
||||
throw new \RuntimeException("Error while trying to download (code: $code): $uri \nMessage: $error_message");
|
||||
}
|
||||
}
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $ch
|
||||
* @param $options
|
||||
* @param $callback
|
||||
*
|
||||
* @return bool|mixed
|
||||
*/
|
||||
private static function curlExecFollow($ch, $options, $callback)
|
||||
{
|
||||
if ($callback) {
|
||||
curl_setopt_array(
|
||||
$ch,
|
||||
[
|
||||
CURLOPT_NOPROGRESS => false,
|
||||
CURLOPT_PROGRESSFUNCTION => ['self', 'progress']
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// no open_basedir set, we can proceed normally
|
||||
if (!ini_get('open_basedir')) {
|
||||
curl_setopt_array($ch, $options['curl']);
|
||||
return curl_exec($ch);
|
||||
}
|
||||
|
||||
$max_redirects = isset($options['curl'][CURLOPT_MAXREDIRS]) ? $options['curl'][CURLOPT_MAXREDIRS] : 5;
|
||||
$options['curl'][CURLOPT_FOLLOWLOCATION] = false;
|
||||
|
||||
// open_basedir set but no redirects to follow, we can disable followlocation and proceed normally
|
||||
curl_setopt_array($ch, $options['curl']);
|
||||
if ($max_redirects <= 0) {
|
||||
return curl_exec($ch);
|
||||
}
|
||||
|
||||
$uri = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
|
||||
$rch = curl_copy_handle($ch);
|
||||
|
||||
curl_setopt($rch, CURLOPT_HEADER, true);
|
||||
curl_setopt($rch, CURLOPT_NOBODY, true);
|
||||
curl_setopt($rch, CURLOPT_FORBID_REUSE, false);
|
||||
curl_setopt($rch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
do {
|
||||
curl_setopt($rch, CURLOPT_URL, $uri);
|
||||
$header = curl_exec($rch);
|
||||
|
||||
if (curl_errno($rch)) {
|
||||
$code = 0;
|
||||
} else {
|
||||
$code = curl_getinfo($rch, CURLINFO_HTTP_CODE);
|
||||
if ($code == 301 || $code == 302 || $code == 303) {
|
||||
preg_match('/Location:(.*?)\n/', $header, $matches);
|
||||
$uri = trim(array_pop($matches));
|
||||
} else {
|
||||
$code = 0;
|
||||
}
|
||||
}
|
||||
} while ($code && --$max_redirects);
|
||||
|
||||
curl_close($rch);
|
||||
|
||||
if (!$max_redirects) {
|
||||
if ($max_redirects === null) {
|
||||
trigger_error('Too many redirects. When following redirects, libcurl hit the maximum amount.', E_USER_WARNING);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $uri);
|
||||
|
||||
return curl_exec($ch);
|
||||
}
|
||||
}
|
||||
141
system/src/Grav/Common/GPM/Upgrader.php
Normal file
141
system/src/Grav/Common/GPM/Upgrader.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Grav.Common.GPM
|
||||
*
|
||||
* @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
|
||||
* @license MIT License; see LICENSE file for details.
|
||||
*/
|
||||
|
||||
namespace Grav\Common\GPM;
|
||||
|
||||
use Grav\Common\GPM\Remote\GravCore;
|
||||
|
||||
/**
|
||||
* Class Upgrader
|
||||
*
|
||||
* @package Grav\Common\GPM
|
||||
*/
|
||||
class Upgrader
|
||||
{
|
||||
/**
|
||||
* Remote details about latest Grav version
|
||||
*
|
||||
* @var GravCore
|
||||
*/
|
||||
private $remote;
|
||||
|
||||
private $min_php;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param callable $callback Either a function or callback in array notation
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function __construct($refresh = false, $callback = null)
|
||||
{
|
||||
$this->remote = new Remote\GravCore($refresh, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the release date of the latest version of Grav
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getReleaseDate()
|
||||
{
|
||||
return $this->remote->getDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the installed Grav
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalVersion()
|
||||
{
|
||||
return GRAV_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version of the remotely available Grav
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRemoteVersion()
|
||||
{
|
||||
return $this->remote->getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of assets available to download remotely
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getAssets()
|
||||
{
|
||||
return $this->remote->getAssets();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the changelog list for each version of Grav
|
||||
*
|
||||
* @param string $diff the version number to start the diff from
|
||||
*
|
||||
* @return array return the changelog list for each version
|
||||
*/
|
||||
public function getChangelog($diff = null)
|
||||
{
|
||||
return $this->remote->getChangelog($diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure this meets minimum PHP requirements
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function meetsRequirements()
|
||||
{
|
||||
$current_php_version = phpversion();
|
||||
if (version_compare($current_php_version, $this->minPHPVersion(), '<')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minimum PHP version from remote
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function minPHPVersion()
|
||||
{
|
||||
if (is_null($this->min_php)) {
|
||||
$this->min_php = $this->remote->getMinPHPVersion();
|
||||
}
|
||||
return $this->min_php;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the currently installed Grav is upgradable to a newer version
|
||||
*
|
||||
* @return boolean True if it's upgradable, False otherwise.
|
||||
*/
|
||||
public function isUpgradable()
|
||||
{
|
||||
return version_compare($this->getLocalVersion(), $this->getRemoteVersion(), "<");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Grav is currently symbolically linked
|
||||
*
|
||||
* @return boolean True if Grav is symlinked, False otherwise.
|
||||
*/
|
||||
|
||||
public function isSymlink()
|
||||
{
|
||||
return $this->remote->isSymlink();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user