mini-projet

This commit is contained in:
2026-01-16 16:48:52 +01:00
parent 281aef65f8
commit a59d83df45
1074 changed files with 156012 additions and 0 deletions
@@ -0,0 +1,41 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\GPM;
use Grav\Common\Iterator;
/**
* Class AbstractCollection
* @package Grav\Common\GPM
*/
abstract class AbstractCollection extends Iterator
{
/**
* @return string
*/
public function toJson()
{
return json_encode($this->toArray(), JSON_THROW_ON_ERROR);
}
/**
* @return array
*/
public function toArray()
{
$items = [];
foreach ($this->items as $name => $package) {
$items[$name] = $package->toArray();
}
return $items;
}
}
@@ -0,0 +1,50 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\GPM\Common;
use Grav\Common\Iterator;
/**
* Class AbstractPackageCollection
* @package Grav\Common\GPM\Common
*/
abstract class AbstractPackageCollection extends Iterator
{
/** @var string */
protected $type;
/**
* @return string
*/
public function toJson()
{
$items = [];
foreach ($this->items as $name => $package) {
$items[$name] = $package->toArray();
}
return json_encode($items, JSON_THROW_ON_ERROR);
}
/**
* @return array
*/
public function toArray()
{
$items = [];
foreach ($this->items as $name => $package) {
$items[$name] = $package->toArray();
}
return $items;
}
}
@@ -0,0 +1,43 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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
* @package Grav\Common\GPM\Common
*/
class CachedCollection extends Iterator
{
/** @var array */
protected static $cache = [];
/**
* CachedCollection constructor.
*
* @param array $items
*/
public function __construct($items)
{
parent::__construct();
$method = static::class . __METHOD__;
// local cache to speed things up
if (!isset(self::$cache[$method])) {
self::$cache[$method] = $items;
}
foreach (self::$cache[$method] as $name => $item) {
$this->append([$name => $item]);
}
}
}
@@ -0,0 +1,99 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\GPM\Common;
use Grav\Common\Data\Data;
/**
* @property string $name
*/
class Package
{
/** @var Data */
protected $data;
/**
* Package constructor.
* @param Data $package
* @param string|null $type
*/
public function __construct(Data $package, $type = null)
{
$this->data = $package;
if ($type) {
$this->data->set('package_type', $type);
}
}
/**
* @return Data
*/
public function getData()
{
return $this->data;
}
/**
* @param string $key
* @return mixed
*/
#[\ReturnTypeWillChange]
public function __get($key)
{
return $this->data->get($key);
}
/**
* @param string $key
* @param mixed $value
* @return void
*/
#[\ReturnTypeWillChange]
public function __set($key, $value)
{
$this->data->set($key, $value);
}
/**
* @param string $key
* @return bool
*/
#[\ReturnTypeWillChange]
public function __isset($key)
{
return isset($this->data->{$key});
}
/**
* @return string
*/
#[\ReturnTypeWillChange]
public function __toString()
{
return $this->toJson();
}
/**
* @return string
*/
public function toJson()
{
return $this->data->toJson();
}
/**
* @return array
*/
public function toArray()
{
return $this->data->toArray();
}
}
File diff suppressed because it is too large Load Diff
+544
View File
@@ -0,0 +1,544 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\GPM;
use DirectoryIterator;
use Grav\Common\Filesystem\Folder;
use Grav\Common\Grav;
use Grav\Common\Utils;
use RuntimeException;
use ZipArchive;
use function count;
use function in_array;
use function is_string;
/**
* Class Installer
* @package Grav\Common\GPM
*/
class Installer
{
/** @const No error */
public const OK = 0;
/** @const Target already exists */
public const EXISTS = 1;
/** @const Target is a symbolic link */
public const IS_LINK = 2;
/** @const Target doesn't exist */
public const NOT_FOUND = 4;
/** @const Target is not a directory */
public const NOT_DIRECTORY = 8;
/** @const Target is not a Grav instance */
public const NOT_GRAV_ROOT = 16;
/** @const Error while trying to open the ZIP package */
public const ZIP_OPEN_ERROR = 32;
/** @const Error while trying to extract the ZIP package */
public const ZIP_EXTRACT_ERROR = 64;
/** @const Invalid source file */
public const INVALID_SOURCE = 128;
/** @var string Destination folder on which validation checks are applied */
protected static $target;
/** @var int|string Error code or string */
protected static $error = 0;
/** @var int Zip Error Code */
protected static $error_zip = 0;
/** @var string Post install message */
protected static $message = '';
/** @var array Default options for the install */
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|null $extracted The local path to the extacted ZIP package
* @param bool $keepExtracted True if you want to keep the original files
* @return bool True if everything went fine, False otherwise.
*/
public static function install($zip, $destination, $options = [], $extracted = null, $keepExtracted = false)
{
$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('', false);
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']) {
$isTheme = $options['theme'] ?? false;
// Make sure that themes are always being copied, even if option was not set!
$isTheme = $isTheme || preg_match('|/themes/[^/]+|ui', $install_path);
if ($isTheme) {
self::copyInstall($extracted, $install_path);
} else {
self::moveInstall($extracted, $install_path);
}
} else {
self::sophisticatedInstall($extracted, $install_path, $options['ignores'], $keepExtracted);
}
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 string $zip_file
* @param string $destination
* @return string|false
*/
public static function unZip($zip_file, $destination)
{
$zip = new ZipArchive();
$archive = $zip->open($zip_file);
if ($archive === true) {
Folder::create($destination);
$unzip = $zip->extractTo($destination);
if (!$unzip) {
self::$error = self::ZIP_EXTRACT_ERROR;
Folder::delete($destination);
$zip->close();
return false;
}
$package_folder_name = $zip->getNameIndex(0);
if ($package_folder_name === false) {
throw new \RuntimeException('Bad package file: ' . Utils::basename($zip_file));
}
$package_folder_name = preg_replace('#\./$#', '', $package_folder_name);
$zip->close();
return $destination . '/' . $package_folder_name;
}
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 string|null
*/
private static function loadInstaller($installer_file_folder, $is_install)
{
$installer_file_folder = rtrim($installer_file_folder, DS);
$install_file = $installer_file_folder . DS . 'install.php';
if (!file_exists($install_file)) {
return null;
}
require_once $install_file;
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) ?? $class_name;
if (class_exists($class_name_alphanumeric)) {
return $class_name_alphanumeric;
}
return null;
}
/**
* @param string $source_path
* @param string $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 string $source_path
* @param string $install_path
* @return bool
*/
public static function copyInstall($source_path, $install_path)
{
if (empty($source_path)) {
throw new RuntimeException("Directory $source_path is missing");
}
Folder::rcopy($source_path, $install_path);
return true;
}
/**
* @param string $source_path
* @param string $install_path
* @param array $ignores
* @param bool $keep_source
* @return bool
*/
public static function sophisticatedInstall($source_path, $install_path, $ignores = [], $keep_source = false)
{
foreach (new DirectoryIterator($source_path) as $file) {
if ($file->isLink() || $file->isDot() || in_array($file->getFilename(), $ignores, true)) {
continue;
}
$path = $install_path . DS . $file->getFilename();
if ($file->isDir()) {
Folder::delete($path);
if ($keep_source) {
Folder::copy($file->getPathname(), $path);
} else {
Folder::move($file->getPathname(), $path);
}
if ($file->getFilename() === 'bin') {
$glob = glob($path . DS . '*') ?: [];
foreach ($glob 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 bool 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 bool 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, true)) {
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 bool 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 .= 'Memory allocation 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;
case self::INVALID_SOURCE:
$msg = 'Invalid source file';
break;
default:
$msg = 'Unknown Error';
break;
}
return $msg;
}
/**
* Returns the last error code of the occurred error
*
* @return int|string 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
* @return void
*/
public static function setError($error)
{
self::$error = $error;
}
}
+116
View File
@@ -0,0 +1,116 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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;
use RocketTheme\Toolbox\File\FileInterface;
use function is_string;
/**
* Class Licenses
*
* @package Grav\Common\GPM
*/
class Licenses
{
/** @var string Regex to validate the format of a License */
protected static $regex = '^(?:[A-F0-9]{8}-){3}(?:[A-F0-9]{8}){1}$';
/** @var FileInterface */
protected static $file;
/**
* Returns the license for a Premium package
*
* @param string $slug
* @param string $license
* @return bool
*/
public static function set($slug, $license)
{
$licenses = self::getLicenseFile();
$data = (array)$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 string|null $slug
* @return string[]|string
*/
public static function get($slug = null)
{
$licenses = self::getLicenseFile();
$data = (array)$licenses->content();
$licenses->free();
if (null === $slug) {
return $data['licenses'] ?? [];
}
$slug = strtolower($slug);
return $data['licenses'][$slug] ?? '';
}
/**
* Validates the License format
*
* @param string|null $license
* @return bool
*/
public static function validate($license = null)
{
if (!is_string($license)) {
return false;
}
return (bool)preg_match('#' . self::$regex. '#', $license);
}
/**
* Get the License File object
*
* @return 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,34 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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;
/**
* Class AbstractPackageCollection
* @package Grav\Common\GPM\Local
*/
abstract class AbstractPackageCollection extends BaseCollection
{
/**
* AbstractPackageCollection constructor.
*
* @param array $items
*/
public function __construct($items)
{
parent::__construct();
foreach ($items as $name => $data) {
$data->set('slug', $name);
$this->items[$name] = new Package($data, $this->type);
}
}
}
@@ -0,0 +1,51 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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;
use Parsedown;
/**
* Class Package
* @package Grav\Common\GPM\Local
*/
class Package extends BasePackage
{
/** @var array */
protected $settings;
/**
* Package constructor.
* @param Data $package
* @param string|null $package_type
*/
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->__get('description'));
$this->data->set('slug', $package->__get('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->__get('slug')));
}
/**
* @return bool
*/
public function isEnabled()
{
return (bool)$this->settings['enabled'];
}
}
@@ -0,0 +1,29 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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
* @package Grav\Common\GPM\Local
*/
class Packages extends CachedCollection
{
public function __construct()
{
$items = [
'plugins' => new Plugins(),
'themes' => new Themes()
];
parent::__construct($items);
}
}
@@ -0,0 +1,33 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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
* @package Grav\Common\GPM\Local
*/
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());
}
}
@@ -0,0 +1,33 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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
* @package Grav\Common\GPM\Local
*/
class Themes extends AbstractPackageCollection
{
/** @var string */
protected $type = 'themes';
/**
* Local Themes Constructor
*/
public function __construct()
{
/** @var \Grav\Common\Themes $themes */
$themes = Grav::instance()['themes'];
parent::__construct($themes->all());
}
}
@@ -0,0 +1,81 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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\HTTP\Response;
use Grav\Common\GPM\Common\AbstractPackageCollection as BaseCollection;
use \Doctrine\Common\Cache\FilesystemCache;
use RuntimeException;
/**
* Class AbstractPackageCollection
* @package Grav\Common\GPM\Remote
*/
class AbstractPackageCollection extends BaseCollection
{
/** @var string The cached data previously fetched */
protected $raw;
/** @var string */
protected $repository;
/** @var FilesystemCache */
protected $cache;
/** @var int The lifetime to store the entry in seconds */
private $lifetime = 86400;
/**
* AbstractPackageCollection constructor.
*
* @param string|null $repository
* @param bool $refresh
* @param callable|null $callback
*/
public function __construct($repository = null, $refresh = false, $callback = null)
{
parent::__construct();
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 multi-sites
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);
}
}
/**
* @param bool $refresh
* @param callable|null $callback
* @return string
*/
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;
}
}
@@ -0,0 +1,151 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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;
use InvalidArgumentException;
/**
* Class GravCore
* @package Grav\Common\GPM\Remote
*/
class GravCore extends AbstractPackageCollection
{
/** @var string */
protected $repository = 'https://getgrav.org/downloads/grav.json';
/** @var array */
private $data;
/** @var string */
private $version;
/** @var string */
private $date;
/** @var string|null */
private $min_php;
/**
* @param bool $refresh
* @param callable|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 = $this->data['version'] ?? '-';
$this->date = $this->data['date'] ?? '-';
$this->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|null $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 string
*/
public function getMinPHPVersion()
{
// If non min set, assume current PHP version
if (null === $this->min_php) {
$this->min_php = PHP_VERSION;
}
return $this->min_php;
}
/**
* Is this installation symlinked?
*
* @return bool
*/
public function isSymlink()
{
return is_link(GRAV_ROOT . DS . 'index.php');
}
}
@@ -0,0 +1,66 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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
* @package Grav\Common\GPM\Remote
*/
class Package extends BasePackage implements \JsonSerializable
{
/**
* Package constructor.
* @param array $package
* @param string|null $package_type
*/
public function __construct($package, $package_type = null)
{
$data = new Data($package);
parent::__construct($data, $package_type);
}
/**
* @return array
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->data->toArray();
}
/**
* Returns the changelog list for each version of a package
*
* @param string|null $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;
}
}
@@ -0,0 +1,34 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 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
* @package Grav\Common\GPM\Remote
*/
class Packages extends CachedCollection
{
/**
* Packages constructor.
* @param bool $refresh
* @param callable|null $callback
*/
public function __construct($refresh = false, $callback = null)
{
$items = [
'plugins' => new Plugins($refresh, $callback),
'themes' => new Themes($refresh, $callback)
];
parent::__construct($items);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\GPM\Remote;
/**
* Class Plugins
* @package Grav\Common\GPM\Remote
*/
class Plugins extends AbstractPackageCollection
{
/** @var string */
protected $type = 'plugins';
/** @var string */
protected $repository = 'https://getgrav.org/downloads/plugins.json';
/**
* Local Plugins Constructor
* @param bool $refresh
* @param callable|null $callback Either a function or callback in array notation
*/
public function __construct($refresh = false, $callback = null)
{
parent::__construct($this->repository, $refresh, $callback);
}
}
@@ -0,0 +1,32 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\GPM\Remote;
/**
* Class Themes
* @package Grav\Common\GPM\Remote
*/
class Themes extends AbstractPackageCollection
{
/** @var string */
protected $type = 'themes';
/** @var string */
protected $repository = 'https://getgrav.org/downloads/themes.json';
/**
* Local Themes Constructor
* @param bool $refresh
* @param callable|null $callback Either a function or callback in array notation
*/
public function __construct($refresh = false, $callback = null)
{
parent::__construct($this->repository, $refresh, $callback);
}
}
+3
View File
@@ -0,0 +1,3 @@
<?php
// Create alias for the deprecated class.
class_alias(\Grav\Common\HTTP\Response::class, \Grav\Common\GPM\Response::class);
+138
View File
@@ -0,0 +1,138 @@
<?php
/**
* @package Grav\Common\GPM
*
* @copyright Copyright (c) 2015 - 2025 Trilby Media, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Common\GPM;
use Grav\Common\GPM\Remote\GravCore;
use InvalidArgumentException;
/**
* Class Upgrader
*
* @package Grav\Common\GPM
*/
class Upgrader
{
/** @var GravCore Remote details about latest Grav version */
private $remote;
/** @var string|null */
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|null $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|null $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()
{
if (version_compare(PHP_VERSION, $this->minPHPVersion(), '<')) {
return false;
}
return true;
}
/**
* Get minimum PHP version from remote
*
* @return string
*/
public function minPHPVersion()
{
if (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 bool 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 bool True if Grav is symlinked, False otherwise.
*/
public function isSymlink()
{
return $this->remote->isSymlink();
}
}