initial commit

This commit is contained in:
2025-09-30 11:14:10 +02:00
commit 0f8250e702
68 changed files with 9365 additions and 0 deletions

935
src/Application.php Normal file
View File

@@ -0,0 +1,935 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file heavily inspired by Roots\Acorn and Illuminate\Foundation.
* Original author: Roots/Laravel
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/
*/
namespace Webshr\Core;
use BadMethodCallException;
use Webshr\Core\Support\Arriable;
use Webshr\Core\Utility\Encryption;
use Webshr\Core\Utility\Hash;
use Webshr\Core\Assets\Manager as Assets_Manager;
use Webshr\Core\Modules\Manager as Modules_Manager;
use Webshr\Core\Support\Traits\Aliases as Aliases_Trait;
use Webshr\Core\Support\Traits\Application as App_Trait;
use Webshr\Core\Contracts\Application as Application_Interface;
use function Webshr\Core\Filesystem\join_paths;
/**
* Main communication channel with the theme.
*/
class Application implements Application_Interface
{
use Aliases_Trait;
use App_Trait;
/**
* The core framework version.
*
* @var string
*/
public const VERSION = '1.1.0';
/**
* The base path for the application.
*
* @var string
*/
protected $base_path;
/**
* The custom language file path defined by the developer.
*
* @var string
*/
protected $lang_path;
/**
* The custom environment file path defined by the developer.
*
* @var string
*/
protected $env_path;
/**
* The custom manifests defined by the developer.
*
* @var array
*/
protected $manifests;
/**
* The custom default manifest file defined by the developer.
*
* @var string
*/
protected $default_manifest;
/**
* The custom language file path defined by the developer.
*
* @var string
*/
protected $config_path;
/**
* Paths to be used by the theme.
*
* @var array
*/
protected $paths = [];
/**
* Associative array of all core theme configs.
*
* @var array
*/
protected $config = [];
/**
* App instance.
*
* @var Application
*/
protected static $instance;
/**
* Assets_Manager.
*
* @var Assets_Manager
*/
protected $assets_manager;
/**
* The Module Manager instance.
*
* @var Modules_Manager
*/
public $module_manager;
/**
* Indicates if the application has "booted".
*
* @var bool
*/
protected $booted = false;
/**
* Indicates if the application has been bootstrapped before.
*
* @var bool
*/
protected $has_been_bootstrapped = false;
/**
* The array of booting callbacks.
*
* @var callable[]
*/
protected $booting_callbacks = [];
/**
* The array of booted callbacks.
*
* @var callable[]
*/
protected $booted_callbacks = [];
/**
* Associative array of all core theme providers.
*
* @var array
*/
protected $managers = [];
/**
* Associative array of all core theme modules
*
* @var array
*/
protected $modules = [];
/**
* Associative array of all core encryption cipher and keys
*
* @var array
*/
protected $encryption = [];
/**
* The names of the loaded modules.
*
* @var array
*/
protected $registered_modules = [];
/**
* The names of the loaded modules.
*
* @var array
*/
protected $loaded_modules = [];
/**
* The deferred services and their modules.
*
* @var array
*/
protected $deferred_services = [];
/**
* The application bindings.
*
* @var array
*/
protected $bindings = [];
/**
* Constructor to initialize the app instance.
*
* @param Application $instance
*/
public function __construct($encryption = null, $base_path = null, $paths = null, $default_manifest = null, $manifests = null, $modules = null)
{
if ($encryption) {
$this->use_encryption((array) $encryption);
}
if ($base_path) {
$this->base_path = rtrim($base_path, '\/');
}
if ($paths) {
$this->use_paths((array) $paths);
}
if ($default_manifest) {
$this->use_default_manifest((string) $default_manifest);
}
if ($manifests) {
$this->use_manifests((array) $manifests);
}
if ($modules) {
$this->use_modules((array) $modules);
}
$this->register_core_bindings();
$this->register_core_aliases();
$this->register_core_encryption();
$this->register_core_managers();
$this->register_core_helpers();
}
/**
* Make a new theme instance.
*
* @codeCoverageIgnore
* @return static
*/
public function make($abstract, array $parameters = [])
{
return $this->resolve($abstract, $parameters);
}
/**
* Set the Application instance.
*
* @param Application $instance
*/
public static function set_instance(Application $instance): void
{
static::$instance = $instance;
}
/**
* Get the globally available instance of the theme.
*
* @return static
*/
public static function get_instance()
{
if (is_null(static::$instance)) {
static::$instance = new static();
}
return static::$instance;
}
/**
* Boot the application's modules.
*
* @return void
*/
public function boot()
{
if ($this->is_booted()) {
return;
}
// Once the application has booted we will also fire some "booted" callbacks
// for any listeners that need to do work after this initial booting gets
// finished. This is useful when ordering the boot-up processes we run.
$this->fire_app_callbacks($this->booting_callbacks);
// Register assets
$this->register_assets_config();
$this->register_modules();
$this->boot_modules();
$this->fire_app_callbacks($this->booted_callbacks);
$this->booted = true;
// Set the bootstrapped flag
$this->has_been_bootstrapped = true;
}
/**
* Set the encryption configuration.
*
* @param array $encryption
* @return $this
*/
public function use_encryption(array $encryption)
{
if (isset($encryption['cipher'])) {
$this->instance('cipher', $encryption['cipher']);
}
if (isset($encryption['key'])) {
$this->instance('key', $encryption['key']);
}
if (isset($encryption['previous_keys'])) {
$this->instance('previous_keys', $encryption['previous_keys']);
}
return $this;
}
/**
* Set paths that are configurable by the developer.
*
* Supported path types:
* - app
* - bootstrap
* - config
* - lang
* - resources
* - storage
* - env
*
* @param array $path
* @return $this
*/
public function use_paths(array $paths)
{
$supported_paths = [
'base',
'app',
'config',
'resources',
'storage',
'bootstrap',
'env',
];
foreach ($paths as $path_type => $path) {
$path = rtrim($path, '\\/');
if (! in_array($path_type, $supported_paths)) {
throw new \Exception("The {$path_type} path type is not supported.");
}
$this->paths[$path_type] = $path;
}
$this->bind_paths();
return $this;
}
/**
* Bind all of the application paths.
*
* @return void
*/
protected function bind_paths()
{
foreach ($this->paths as $key => $path) {
$this->instance("path.{$key}", $path);
}
$this->use_lang_path(is_dir($directory = $this->paths['resources'] . '/lang')
? $directory
: $this->paths['base'] . '/lang');
}
/**
* Set the default manifest
*
* @param string $manifest
* @return $this
*/
public function use_default_manifest(string $manifest)
{
if (! is_string($manifest)) {
throw new \Exception("The default manifest '{$manifest}' does not exist in the manifests array.");
}
$this->default_manifest = $manifest;
return $this;
}
/**
* Set manifests types that are configurable by the developer.
*
* Supported manifest types:
* - path
* - url
* - assets
* - bundles
*
* @param array $manifests
* @return $this
*/
public function use_manifests(array $manifests)
{
$supported_manifest_keys = [
'path',
'url',
'assets',
'bundles',
];
foreach ($manifests as $key => $manifest) {
foreach ($manifest as $manifest => $value) {
if (! in_array($manifest, $supported_manifest_keys)) {
throw new \Exception("The {$manifest} path type is not supported.");
}
$this->manifests[$key][$manifest] = rtrim($value, '\\/');
}
}
return $this;
}
/**
* Register assets
*
* @return void
*/
protected function register_assets_config()
{
if (! isset($this->assets_manager)) {
throw new \Exception("Assets manager is not initialized.");
}
$this->register_default_manifest();
$this->register_manifests();
}
/**
* Register the default manifest.
*
* @return void
*/
protected function register_default_manifest()
{
if (! isset($this->default_manifest)) {
throw new \Exception("Default manifest key '{$this->default_manifest}' is not set.");
}
$key = $this->default_manifest;
$manifest = $this->manifests[$key];
// Check if the default manifest is already bound
if (! isset($this->bindings['assets.manifest'])) {
// Set the default manifest instance
$manifest_instace = $this->assets_manager->manifest($key, $manifest);
// Bind the Manifest instance
$this->instance('assets.manifest', $manifest_instace);
}
}
/**
* Register all manifests.
*
* @return void
*/
protected function register_manifests()
{
foreach ($this->manifests as $key => $manifest) {
$this->assets_manager->manifest($key, $manifest);
}
$this->instance('assets', $this->assets_manager);
}
/**
* Set the directory for the environment file.
*
* @param string $path
* @return $this
*/
public function use_environment_path($path)
{
$this->env_path = $path;
$this->instance('path.env', $path);
return $this;
}
/**
* Set the language file directory.
*
* @param string $path
* @return $this
*/
public function use_lang_path($path)
{
$this->lang_path = $path;
$this->instance('path.lang', $path);
return $this;
}
/**
* @inheritDoc
*/
public function assets_path($path = '')
{
return $this->join_paths($this->config_path, $path);
}
/**
* @inheritDoc
*/
public function base_path($path = '')
{
return $this->join_paths($this->base_path, $path);
}
/**
* @inheritDoc
*/
public function config_path($path = '')
{
return $this->join_paths($this->config_path, $path);
}
/**
* @inheritDoc
*/
public function lang_path($path = '')
{
return $this->join_paths($this->config_path, $path);
}
/**
* @inheritDoc
*/
public function manifest_path($path = '')
{
return $this->join_paths($this->config_path, $path);
}
/**
* Join the given paths together.
*
* @param string $base_path
* @param string $path
* @return string
*/
public function join_paths($base_path, $path = '')
{
return join_paths($base_path, $path);
}
protected function register_core_encryption()
{
$this->instance('hash', new Hash());
$this->instance('encryption', new Encryption($this));
}
/**
* Load global helper functions.
*
* @return void
*/
protected function register_core_helpers()
{
require_once __DIR__ . '/globals.php';
require_once __DIR__ . '/helpers.php';
}
/**
* Register core bindings.
*
* @return void
*/
protected function register_core_bindings()
{
static::set_instance($this);
$this->instance('app', $this);
}
/**
* Register core managers.
*
* @return void
*/
protected function register_core_managers()
{
$this->assets_manager = new Assets_Manager();
$this->module_manager = new Modules_Manager();
}
/**
* Set the modules that are configurable by the developer.
*
* @param array $modules
* @return $this
*/
public function use_modules(array $modules)
{
foreach ($modules as $module_name => $module_class) {
if (! class_exists($module_class)) {
throw new \Exception("The module class {$module_class} does not exist.");
}
$this->modules[$module_name] = $module_class;
}
return $this;
}
/**
* Register modules
*
* @return void
*/
protected function register_modules()
{
if (! isset($this->module_manager)) {
throw new \Exception("Modules manager is not initialized.");
}
foreach ($this->modules as $key => $module) {
$this->module_manager->register($key, new $module());
$this->registered_modules[$key] = $module;
}
}
/**
* Register a module with the application.
*
* @param \Webshr\Core\Module|string $module
* @param bool $force
*/
public function boot_modules()
{
foreach ($this->module_manager->modules() as $key => $module) {
// Check if the module is already loaded
if (isset($this->loaded_modules[$key])) {
continue;
// Skip already loaded modules
}
// Boot the module
$this->boot_module($module);
$this->loaded_modules[$key] = $module;
// Register the module if it can be registered
if ($module->can_register()) {
$module->register();
}
// If the application has already booted, call the boot method on the module
if ($this->is_booted()) {
$this->boot_module($module);
}
}
}
/**
* Boot the given module.
*
* @param \Webshr\Core\Module $module
* @return void
*/
protected function boot_module(Module $module)
{
$module->call_booting_callbacks();
if (method_exists($module, 'boot')) {
$this->call([$module, 'boot']);
}
$module->call_booted_callbacks();
}
/**
* Get the registered service provider instance if it exists.
*
* @param \Webshr\Core\Module|string $module
* @return \Webshr\Core\Module|null
*/
public function get_module($module)
{
return array_values($this->get_modules($module))[0] ?? null;
}
/**
* Get the registered service provider instances if any exist.
*
* @param \Webshr\Core\Module|string $module
* @return array
*/
public function get_modules($module)
{
$name = is_string($module) ? $module : get_class($module);
return Arriable::where($this->modules, fn($value) => $value instanceof $name);
}
/**
* Resolve a module instance from the class name.
*
* @param string $module
* @return \Webshr\Core\Modules\Contracts\Module
*/
public function resolve_module($module)
{
return $this->module_manager->module($module);
}
/**
* Mark the given module as registered.
*
* @param \Webshr\Core\Module $module
* @return void
*/
protected function mark_as_registered($module)
{
$this->modules[] = $module;
$this->loaded_modules[get_class($module)] = true;
}
/**
* Register the core class aliases in the application.
*
* @return void
*/
protected function register_core_aliases()
{
$this->alias('app', self::class);
}
/**
* Register a new boot listener.
*
* @param callable $callback
* @return void
*/
public function booting($callback)
{
$this->booting_callbacks[] = $callback;
}
/**
* Register a new "booted" listener.
*
* @param callable $callback
* @return void
*/
public function booted($callback)
{
$this->booted_callbacks[] = $callback;
if ($this->is_booted()) {
$callback($this);
}
}
/**
* Check if the application has been booted.
*
* This method returns the status of the application's boot process.
*
* @return bool True if the application is booted, false otherwise.
*/
public function is_booted()
{
return $this->booted;
}
/**
* Call the booting callbacks for the application.
*
* @param callable[] $callbacks
* @return void
*/
protected function fire_app_callbacks(array &$callbacks)
{
$index = 0;
while ($index < count($callbacks)) {
$callbacks[$index]($this);
$index++;
}
}
/**
* Determine if the application has been bootstrapped before.
*
* @return bool
*/
public function has_been_bootstrapped()
{
return $this->has_been_bootstrapped;
}
/**
* Get an object or value from the application container.
*
* @param string $key
* @return mixed
*/
public function get(string $key)
{
return $this->bindings[$key] ?? null;
}
/**
* Get all bindings.
*
* @return array
*/
public function bindings(): array
{
return $this->bindings;
}
/**
* @inheritDoc
*/
public function version()
{
return self::VERSION;
}
/**
* Bind a value or object to a key in the application container.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function instance(string $key, $value)
{
$this->bindings[$key] = $value;
}
/**
* Register a shared binding in the application.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null)
{
// If no concrete implementation is provided, use the abstract as the concrete
if (is_null($concrete)) {
$concrete = $abstract;
}
// Register a closure that resolves the singleton instance
$this->bindings[$abstract] = function () use ($abstract, $concrete) {
// Check if the instance already exists
if (! isset($this->bindings["instances"][$abstract])) {
// Resolve the instance and store it
$this->bindings["instances"][$abstract] = $this->resolve($concrete);
}
// Return the stored instance
return $this->bindings["instances"][$abstract];
};
}
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param callable|string $callback
* @param array $parameters
* @param string|null $default_method
* @return mixed
*/
public function call($callback, array $parameters = [], $default_method = null)
{
if (is_string($callback)) {
// Handle the case where the callback is a string in the format 'Class@method'
if (strpos($callback, '@') !== false) {
list($class, $method) = explode('@', $callback);
$callback = [new $class(), $method];
} elseif ($default_method) {
$callback = [new $callback(), $default_method];
} else {
$callback = new $callback();
}
}
return call_user_func_array($callback, $parameters);
}
/**
* Resolve a value or object from the application
*
* @param string $key The key to resolve.
* @param array $args The arguments to pass to the resolved service.
* @return mixed|null The resolved service or null if not found.
*/
public function resolve($abstract, $parameters = []): mixed
{
// Get the alias
if (! is_string($abstract)) {
return null;
}
// Check if the binding exists
if (isset($this->bindings[$abstract])) {
$binding = $this->bindings[$abstract];
// If the module is a class name (string), instantiate it
if (is_string($binding) && class_exists($binding)) {
return new $binding();
// or resolve via a DI container if needed
}
// If the module is a callable or has a callback, invoke it
if (is_callable($binding)) {
return call_user_func($binding);
}
if (is_array($binding) && isset($binding['callback']) && is_callable($binding['callback'])) {
return call_user_func($binding['callback']);
}
// Otherwise, return the module directly
return $binding;
}
return null;
// Return null if the service is not found
}
/**
* Magic call method.
*
* Will proxy to the theme method $method, unless it is not available, in which case an exception will be thrown.
*
* @param string $method Template tag name.
* @param array $args Template tag arguments.
* @return mixed Template tag result, or null if theme method only outputs markup.
*
* @throws BadMethodCallException Thrown if the theme method does not exist.
*/
public function __call(string $method, array $args): mixed
{
$resolved = $this->resolve($method);
if (! $resolved) {
throw new BadMethodCallException(sprintf(__('The method %s does not exist.', 'webshr'), 'app()->' . $method . '()'),);
}
// If the resolved service is an invokable object, call it
if (is_object($resolved) && is_callable($resolved)) {
return call_user_func_array($resolved, $args);
}
// If the resolved service is an object (but not invokable), return the object itself
if (is_object($resolved)) {
return $resolved;
}
// Otherwise, call the resolved service as a function
return call_user_func_array($resolved, $args);
}
}

226
src/Assets/Asset/Asset.php Normal file
View File

@@ -0,0 +1,226 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets\Asset;
use Webshr\Core\Assets\Contracts\Asset as Asset_Interface;
use Webshr\Core\Assets\Contracts\Asset_Meta as Meta_Interface;
use Webshr\Core\Filesystem\Filesystem;
use SplFileInfo;
class Asset implements Asset_Interface, Meta_Interface
{
/**
* The local asset path.
*
* @var string
*/
protected $path;
/**
* The remote asset URI.
*
* @var string
*/
protected $uri;
/**
* The asset MIME content type.
*
* @var string
*/
protected $type;
/**
* The asset base64-encoded contents.
*
* @var string
*/
protected $base64;
/**
* The asset data URL.
*
* @var string
*/
protected $data_url;
/**
* Get asset from manifest
*
* @param string $path Local path
* @param string $uri Remote URI
*/
public function __construct(string $path, string $uri)
{
$this->path = $path;
$this->uri = $uri;
}
/** {@inheritdoc} */
public function uri(): string
{
return $this->uri;
}
/** {@inheritdoc} */
public function path(): string
{
return $this->path;
}
/** {@inheritdoc} */
public function exists(): bool
{
return file_exists($this->path());
}
/** {@inheritdoc} */
public function contents(): string
{
if (! $this->exists()) {
return '';
}
return file_get_contents($this->path());
}
/**
* Get the relative path to the asset.
*
* @param string $basePath Base path to use for relative path.
*/
public function relative_path(string $basePath): string
{
$basePath = rtrim($basePath, '/\\') . '/';
return (new Filesystem())->get_relative_path($basePath, $this->path());
}
/**
* Get the base64-encoded contents of the asset.
*
* @return string
*/
public function base64()
{
if ($this->base64) {
return $this->base64;
}
return $this->base64 = base64_encode($this->contents());
}
/**
* Get data URL of asset.
*
* @param string $mediatype MIME content type
*/
public function data_url(?string $mediatype = null): string
{
if ($this->data_url) {
return $this->data_url;
}
if (! $mediatype) {
$mediatype = $this->content_type();
}
return $this->data_url = "data:{$mediatype};base64,{$this->base64()}";
}
/**
* Get data URL of asset.
*
* @param string $mediatype MIME content type
*/
public function data_uri(?string $mediatype = null): string
{
return $this->data_url($mediatype);
}
/**
* Get the MIME content type.
*
* @return string|false
*/
public function content_type()
{
if ($this->type) {
return $this->type;
}
return $this->type = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $this->path());
}
/**
* Get the MIME content type.
*
* @return string|false
*/
public function mime_type()
{
return $this->content_type();
}
/**
* Get SplFileInfo instance of asset.
*
* @return SplFileInfo
*/
public function file()
{
return new SplFileInfo($this->path());
}
/** {@inheritdoc} */
public function raw(): mixed
{
if (!$this->exists()) {
return '';
}
// Start output buffering
ob_start();
// Include the PHP file
include $this->path();
// Get the contents of the buffer and clean it
$content = ob_get_clean();
return $content;
}
/**
* {@inheritDoc}
*/
public function version()
{
//
}
/**
* {@inheritDoc}
*/
public function dependencies()
{
//
}
/**
* {@inheritDoc}
*/
public function include()
{
if (!$this->exists()) {
return '';
}
return include $this->path();
}
/** {@inheritdoc} */
public function __toString()
{
return $this->uri();
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets\Asset;
use Webshr\Core\Support\Contracts\Arrayable;
use Webshr\Core\Support\Contracts\Jsonable;
class Json_Asset extends Text_Asset implements Arrayable, Jsonable
{
/**
* {@inheritdoc}
*/
public function to_json($options = \JSON_UNESCAPED_SLASHES)
{
return json_encode($this->to_array(), $options);
}
/**
* {@inheritdoc}
*/
public function to_array(): array
{
return (array) $this->decode(JSON_OBJECT_AS_ARRAY);
}
/**
* Decode JSON data.
*
* @param int $options
* @param int $depth
* @return array|null
*/
public function decode($options = 0, $depth = 512)
{
return json_decode($this->contents(), null, $depth, $options);
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets\Asset;
use Webshr\Core\Assets\Contracts\Asset_Meta;
class Meta_Asset extends Php_Asset implements Asset_Meta
{
/**
* The asset content
*/
protected $contents;
/**
* The asset version
*
* @var string
*/
protected $version;
/**
* The asset dependencies
*
* @var array
*/
protected $dependencies;
/**
* Constructor to initialize the asset.
*
* @param string $path
* @param string $uri
*/
public function __construct(string $path, string $uri)
{
parent::__construct($path, $uri);
$this->contents = $this->include_once();
$this->version = $this->contents['version'] ?? '';
$this->dependencies = $this->contents['dependencies'] ?? [];
}
/**
* Get the asset version.
*
* @return string
*/
public function version()
{
return $this->version;
}
/**
* Get the asset dependencies.
*
* @return string
*/
public function dependencies(): string
{
return json_encode($this->dependencies);
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets\Asset;
use Webshr\Core\Filesystem\Exceptions\File_Not_Found_Exception;
class Php_Asset extends Asset
{
/**
* Get the returned value of the asset
*
* @return mixed
*/
public function require_once()
{
$this->assert_exists();
return require_once $this->path();
}
/**
* Get the returned value of the asset
*
* @return mixed
*/
public function require()
{
$this->assert_exists();
return require $this->path();
}
/**
* Get the returned value of the asset
*
* @return mixed
*/
public function include_once()
{
$this->assert_exists();
return include_once $this->path();
}
/**
* Get the returned value of the asset
*
* @return mixed
*/
public function include()
{
$this->assert_exists();
return include $this->path();
}
/**
* Assert that the asset exists.
*
* @throws \Webshr\Core\Filesystem\Exceptions\File_Not_Found_Exception
*/
protected function assert_exists()
{
if (! $this->exists()) {
throw new File_Not_Found_Exception("Asset [{$this->path()}] not found.");
}
}
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets\Asset;
class Svg_Asset extends Text_Asset
{
public function content_type()
{
return 'image/svg+xml';
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets\Asset;
class Text_Asset extends Asset
{
/**
* Character encoding
*
* @var string
*/
protected $charset;
/**
* Get character encoding.
*
* @param string $fallback Fallback if charset cannot be determined
*/
public function charset($fallback = 'UTF-8'): string
{
if ($this->charset) {
return $this->charset;
}
if (preg_match('//u', $this->contents())) {
return $this->charset = 'UTF-8';
}
if (function_exists('mb_detect_encoding')) {
return $this->charset = mb_detect_encoding($this->contents()) ?: $fallback;
}
return $this->charset = $fallback;
}
/**
* Get data URL of asset.
*
* @param string $mediatype MIME content type
* @param string $charset Character encoding
* @param string $urlencode List of characters to be percent-encoded
*/
public function data_url(?string $mediatype = null, ?string $charset = null, string $urlencode = '%\'"'): string
{
if ($this->data_url) {
return $this->data_url;
}
if (! $mediatype) {
$mediatype = $this->content_type();
}
if (! strstr($mediatype, 'charset')) {
$mediatype .= ';charset=' . ($charset ?: $this->charset());
}
$percents = [];
foreach (preg_split('//u', $urlencode, -1, PREG_SPLIT_NO_EMPTY) as $char) {
$percents[$char] = rawurlencode($char);
}
$data = strtr($this->contents(), $percents);
return $this->data_url = "data:{$mediatype},{$data}";
}
/**
* Get data URL of asset.
*
* @param string $mediatype MIME content type
* @param string $charset Character encoding
* @param string $urlencode List of characters to be percent-encoded
*/
public function data_uri(?string $mediatype = null, ?string $charset = null, string $urlencode = '%\'"'): string
{
return $this->data_url($mediatype, $charset, $urlencode);
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file was copied from Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/
*/
namespace Webshr\Core\Assets;
use Webshr\Core\Assets\Asset\Asset;
use Webshr\Core\Assets\Asset\Json_Asset;
use Webshr\Core\Assets\Asset\PHP_Asset;
use Webshr\Core\Assets\Asset\Meta_Asset;
use Webshr\Core\Assets\Asset\Svg_Asset;
use Webshr\Core\Assets\Contracts\Asset as Asset_Interface;
class Asset_Factory
{
/**
* Create Asset instance.
*
* @param string $path Local path
* @param string $uri Remote URI
* @param string $type Asset type
*/
public static function create(string $path, string $uri, ?string $type = null): Asset_Interface
{
if (! $type) {
$type = pathinfo($path, PATHINFO_EXTENSION);
}
if ($type === 'meta') {
$path .= '.asset.php';
}
$method = 'create_' . strtolower($type) . '_asset';
if (method_exists(self::class, $method)) {
return self::{$method}($path, $uri);
}
return self::create_asset($path, $uri);
}
/**
* Convert an asset to another asset type.
*/
public static function convert(Asset_Interface $asset, string $type): Asset_Interface
{
return self::create($asset->path(), $asset->uri(), $type);
}
/**
* Create Asset instance.
*/
protected static function create_asset(string $path, string $uri): Asset
{
return new Asset($path, $uri);
}
/**
* Create Json_Asset instance.
*/
protected static function create_json_asset(string $path, string $uri): Json_Asset
{
return new Json_Asset($path, $uri);
}
/**
* Create PHP_Asset instance.
*/
protected static function create_php_asset(string $path, string $uri): PHP_Asset
{
return new PHP_Asset($path, $uri);
}
/**
* Create Meta_Asset instance.
*/
protected static function create_meta_asset(string $path, string $uri): Meta_Asset
{
return new Meta_Asset($path, $uri);
}
/**
* Create Svg_Asset instance.
*/
protected static function create_svg_asset(string $path, string $uri): Svg_Asset
{
return new Svg_Asset($path, $uri);
}
}

216
src/Assets/Bundle.php Normal file
View File

@@ -0,0 +1,216 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file was copied from Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/
*/
namespace Webshr\Core\Assets;
use Webshr\Core\Support\Arriable;
use Webshr\Core\Assets\Concerns\Conditional;
use Webshr\Core\Assets\Concerns\Enqueuable;
use Webshr\Core\Assets\Contracts\Bundle as Bundle_Interface;
class Bundle implements Bundle_Interface
{
use Conditional;
use Enqueuable;
/**
* The bundle ID.
*
* @var string
*/
protected $id;
/**
* The bundle path.
*
* @var string
*/
protected $path;
/**
* The bundle URI.
*
* @var string
*/
protected $uri;
/**
* The bundle runtime.
*
* @var string|null
*/
protected $runtime;
/**
* The bundle contents.
*
* @var array
*/
protected $bundle;
/**
* The bundle runtimes.
*
* @var array
*/
protected static $runtimes = [];
/**
* Create a new bundle.
*/
public function __construct(string $id, array $bundle, string $path, string $uri = '/')
{
$this->id = $id;
$this->path = $path;
$this->uri = $uri;
$this->bundle = $bundle + ['js' => [], 'mjs' => [], 'css' => []];
$this->set_runtime();
}
/**
* Get CSS files in bundle.
*
* Optionally pass a function to execute on each CSS file.
*
* @return array|$this
*/
public function css(?callable $callable = null)
{
$styles = $this->conditional ? $this->bundle['css'] : [];
if (! $callable) {
return $styles;
}
foreach ($styles as $handle => $src) {
$callable("{$this->id}/{$handle}", $this->get_url($src));
}
return $this;
}
/**
* Get JS files in bundle.
*
* Optionally pass a function to execute on each JS file.
*
* @return array|$this
*/
public function js(?callable $callable = null)
{
$scripts = $this->conditional ? array_merge($this->bundle['js'], $this->bundle['mjs']) : [];
if (! $callable) {
return $scripts;
}
foreach ($scripts as $handle => $src) {
if ($handle === 'runtime') {
continue;
}
$callable("{$this->id}/{$handle}", $this->get_url($src), $this->dependencies());
}
return $this;
}
/**
* Get the bundle dependencies.
*
* @return array
*/
public function dependencies()
{
return $this->bundle['dependencies'] ?? [];
}
/**
* Get the bundle runtime.
*
* @return string|null
*/
public function runtime()
{
return $this->runtime;
}
/**
* Get bundle runtime contents.
*
* @return string|null
*/
public function runtime_source()
{
if (($runtime = $this->runtime()) === null) {
return null;
}
if ($sauce = self::$runtimes[$runtime] ?? null) {
return $sauce;
}
return self::$runtimes[$runtime] = file_get_contents("{$this->path}/{$runtime}");
}
/**
* Get the bundle URL.
*
* @return string
*/
protected function get_url(string $path)
{
if (parse_url($path, PHP_URL_HOST)) {
return $path;
}
$path = ltrim($path, '/');
$uri = rtrim($this->uri, '/');
return "{$uri}/{$path}";
}
/**
* Set the bundle runtime.
*
* @return void
*/
protected function set_runtime()
{
if (Arriable::is_assoc($this->bundle['js'])) {
$this->runtime = $this->bundle['js']['runtime']
?? $this->bundle['js']["runtime~{$this->id}"]
?? null;
unset($this->bundle['js']['runtime'], $this->bundle['js']["runtime~{$this->id}"]);
return;
}
$this->runtime = $this->get_bundle_runtime() ?? $this->get_bundle_runtime('mjs');
}
/**
* Retrieve the runtime in a bundle.
*
* @return string|null
*/
protected function get_bundle_runtime(string $type = 'js')
{
if (! $this->bundle[$type]) {
return null;
}
foreach ($this->bundle[$type] as $key => $value) {
if (! str_contains($value, 'runtime')) {
continue;
}
unset($this->bundle[$type][$key]);
return $value;
}
return null;
}
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file was copied from Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/
*/
namespace Webshr\Core\Assets\Concerns;
trait Conditional
{
/**
* Conditionally load assets.
*
* @var bool
*/
protected $conditional = true;
/**
* Set conditional loading.
*
* @param bool|callable $conditional
* @return $this
*/
public function when($conditional, ...$args)
{
$this->conditional = is_callable($conditional)
? call_user_func($conditional, $args)
: $conditional;
return $this;
}
}

View File

@@ -0,0 +1,252 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file was copied from Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/
*/
namespace Webshr\Core\Assets\Concerns;
use Webshr\Core\Support\Str;
use Webshr\Core\Filesystem\Filesystem;
trait Enqueuable
{
/**
* Resolved inline sources.
*
* @var array
*/
protected static $inlined = [];
/**
* Get JS files in bundle.
*
* Optionally pass a function to execute on each JS file.
*
* @return array|$this
*/
abstract public function js(?callable $callable = null);
/**
* Get CSS files in bundle.
*
* Optionally pass a function to execute on each CSS file.
*
* @return array|$this
*/
abstract public function css(?callable $callable = null);
abstract public function runtime();
abstract public function runtime_source();
/**
* Enqueue CSS files in WordPress.
*
* @return $this
*/
public function enqueue_css(string $media = 'all', array $dependencies = [])
{
$this->css(function ($handle, $src) use (&$dependencies, $media) {
wp_enqueue_style($handle, $src, $dependencies, null, $media);
$this->merge_dependencies($dependencies, [$handle]);
});
return $this;
}
/**
* Enqueue JS files in WordPress.
*
* @return $this
*/
public function enqueue_js(bool|array $args = true, array $dependencies = [])
{
$this->js(function ($handle, $src, $bundleDependencies) use (&$dependencies, $args) {
$this->merge_dependencies($dependencies, $bundleDependencies);
wp_enqueue_script($handle, $src, $dependencies, null, $args);
$this->inline_runtime();
$this->merge_dependencies($dependencies, [$handle]);
});
return $this;
}
/**
* Enqueue JS and CSS files in WordPress.
*
* @return $this
*/
public function enqueue()
{
return $this->enqueue_css()->enqueue_js();
}
/**
* Add CSS files as editor styles in WordPress.
*
* @return $this
*/
public function editor_styles()
{
$relative_path = (new Filesystem())->get_relative_path(Str::finish(get_theme_file_path(), '/'), $this->path);
$this->css(function ($handle, $src) use ($relative_path) {
if (! Str::starts_with($src, $this->uri)) {
return add_editor_style($src);
}
$style = Str::of($src)
->after($this->uri)
->ltrim('/')
->start("{$relative_path}/")
->to_string();
add_editor_style($style);
});
return $this;
}
/**
* Dequeue CSS files in WordPress.
*
* @return $this
*/
public function dequeue_css()
{
$this->css(function ($handle) {
wp_dequeue_style($handle);
});
return $this;
}
/**
* Dequeue JS files in WordPress.
*
* @return $this
*/
public function dequeue_js()
{
$this->js(function ($handle) {
wp_dequeue_script($handle);
});
return $this;
}
/**
* Dequeue JS and CSS files in WordPress.
*
* @return $this
*/
public function dequeue()
{
return $this->dequeue_css()->dequeue_js();
}
/**
* Inline runtime.js in WordPress.
*
* @return $this
*/
public function inline_runtime()
{
if (! $runtime = $this->runtime()) {
return $this;
}
if (isset(self::$inlined[$runtime])) {
return $this;
}
if ($contents = $this->runtime_source()) {
$this->inline($contents, 'before');
}
self::$inlined[$runtime] = $contents;
return $this;
}
/**
* Add an inline script before or after the bundle loads
*
* @param string $contents
* @param string $position
* @return $this
*/
public function inline($contents, $position = 'after')
{
if (! $handles = array_keys($this->js()->keys()->toArray())) {
return $this;
}
$handle = "{$this->id}/" . (
$position === 'after'
? array_pop($handles)
: array_shift($handles)
);
wp_add_inline_script($handle, $contents, $position);
return $this;
}
/**
* Add localization data to be used by the bundle
*
* @param string $name
* @param array $object
* @return $this
*/
public function localize($name, $object)
{
if (! $handles = $this->js()->keys()->toArray()) {
return $this;
}
$handle = "{$this->id}/{$handles[0]}";
wp_localize_script($handle, $name, $object);
return $this;
}
/**
* Add script translations to be used by the bundle
*
* @param string $domain
* @param string $path
* @return $this
*/
public function translate($domain = null, $path = null)
{
$domain ??= wp_get_theme()->get('TextDomain');
$path ??= lang_path();
$this->js()->keys()->each(function ($handle) use ($domain, $path) {
wp_set_script_translations("{$this->id}/{$handle}", $domain, $path);
});
return $this;
}
/**
* Merge two or more arrays.
*
* @return void
*/
protected function merge_dependencies(array &$dependencies, array ...$moreDependencies)
{
$dependencies = array_unique(array_merge($dependencies, ...$moreDependencies));
}
/**
* Reset inlined sources.
*
* @internal
*
* @return void
*/
public static function reset_inlined_sources()
{
self::$inlined = [];
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file was copied from Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/Assets/Contracts/Asset.php
*/
namespace Webshr\Core\Assets\Contracts;
interface Asset
{
/**
* Get the asset's remote URI
*
* Example: https://example.com/app/themes/sage/dist/styles/a1b2c3.min.css
*/
public function uri(): string;
/**
* Get the asset's local path
*
* Example: /srv/www/example.com/current/web/app/themes/sage/dist/styles/a1b2c3.min.css
*/
public function path(): string;
/**
* Check whether the asset exists on the file system
*/
public function exists(): bool;
/**
* Get the contents of the asset
*
* @return mixed
*/
public function contents();
/**
* Get the relative path to the asset.
*
* @param string $base_path Base path to use for relative path.
*/
public function relative_path(string $base_path): string;
/**
* Get data URL of asset.
*
* @return string
*/
public function data_url();
/**
* Get asset file.
*
* @return string
*/
public function file();
/**
* Include asset.
*
* @return mixed
*/
public function include();
/**
* Get raw content of asset.
*
* @return string
*/
//public function php ();
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets\Contracts;
interface Asset_Meta
{
/**
* Get the asset version.
*
* @return string
*/
public function version();
/**
* Get the asset dependencies.
*
* @return array
*/
public function dependencies();
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file was copied from Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/Assets/Contracts/Bundle.php
*/
namespace Webshr\Core\Assets\Contracts;
interface Bundle
{
public function css();
public function js();
public function runtime();
}

View File

@@ -0,0 +1,38 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file was copied from Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/Assets/Contracts/Manifest.php
*/
namespace Webshr\Core\Assets\Contracts;
interface Manifest
{
/**
* Get an asset object from the Manifest
*
* @param string $key
*/
public function asset($key): Asset;
/**
* Get an asset bundle from the Manifest
*
* @param string $key
*/
public function bundle($key): Bundle;
/**
* Get an php file from the Manifest
*
* @param string $key
*/
//public function php($key): Php;
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets\Contracts;
interface Meta
{
/**
* Get an asset meta object from the Store
*
* @param string $key
*/
public function meta($key): Asset_Meta;
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets\Contracts;
interface Php
{
/**
* Get an php file from the Manifest
*
* @param string $key
*/
public function php();
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Webshr\Core\Exceptions;
use Exception;
use Psr\Container\ContainerExceptionInterface;
class Binding_Resolution_Exception extends Exception implements ContainerExceptionInterface
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Webshr\Core\Assets\Exceptions;
use Exception;
class Bundle_Not_Found_Exception extends Exception
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Webshr\Core\Assets\Exceptions;
use Exception;
class Manifest_Not_Found_Exception extends Exception
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Webshr\Core\Assets\Exceptions;
use Exception;
class Meta_Not_Found_Exception extends Exception
{
//
}

View File

@@ -0,0 +1,60 @@
<?php
namespace Webshr\Core\Assets\Exceptions;
use InvalidArgumentException;
use Throwable;
class Skip_Module_Exception extends InvalidArgumentException
{
/**
* Create a new exception.
*
* @return void
*/
public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null, string $package = '')
{
parent::__construct($message, $code, $previous);
$this->package = $package;
}
/**
* Name of the module's package.
*
* @var string
*/
protected $package;
/**
* Set the name of the module's package.
*
* @return void
*/
public function set_package(string $package)
{
$this->package = $package;
}
/**
* Get the module's package.
*
* @return string
*/
public function package()
{
return $this->package;
}
/**
* Report the exception.
*
* @return array
*/
public function context()
{
return [
'package' => $this->package(),
];
}
}

114
src/Assets/Manager.php Normal file
View File

@@ -0,0 +1,114 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file was copied from Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/
*/
namespace Webshr\Core\Assets;
use InvalidArgumentException;
use Webshr\Core\Assets\Contracts\Manifest as Manifest_Interface;
use Webshr\Core\Assets\Exceptions\Manifest_Not_Found_Exception;
/**
* Manage assets manifests
*
* @see \Illuminate\Support\Manager
* @link https://github.com/illuminate/support/blob/8.x/Manager.php
*/
class Manager
{
/**
* Resolved manifests
*
* @var Manifest_Interface[]
*/
protected $manifests;
/**
* Assets Config
*
* @var array
*/
protected $config;
/**
* Initialize the Asset Manager instance.
*
* @param array $config
*/
public function __construct($config = [])
{
$this->config = $config;
}
/**
* Register the given manifest
*
* @param Manifest $manifest
* @return static
*/
public function register(string $name, Manifest_Interface $manifest): self
{
$this->manifests[$name] = $manifest;
return $this;
}
/**
* Get a Manifest
*/
public function manifest(string $name, ?array $config = null): Manifest_Interface
{
$manifest = $this->manifests[$name] ?? $this->resolve($name, $config);
return $this->manifests[$name] = $manifest;
}
/**
* Resolve the given manifest.
*
*
* @throws InvalidArgumentException
*/
protected function resolve(string $name, ?array $config): Manifest_Interface
{
$config = $config ?? $this->get_config($name);
if (isset($config['handler'])) {
return new $config['handler']($config);
}
$path = $config['path'];
$url = $config['url'];
$assets = isset($config['assets']) ? $this->get_json_manifest($config['assets']) : [];
$bundles = isset($config['bundles']) ? $this->get_json_manifest($config['bundles']) : [];
return new Manifest($path, $url, $assets, $bundles);
}
/**
* Opens a JSON manifest file from the local file system
*
* @param string $json_manifest Path to .json file
*/
protected function get_json_manifest(string $json_manifest): array
{
if (! file_exists($json_manifest)) {
throw new Manifest_Not_Found_Exception("The asset manifest [{$json_manifest}] cannot be found.");
}
return json_decode(file_get_contents($json_manifest), true) ?? [];
}
/**
* Get the assets manifest configuration.
*/
protected function get_config(string $name): array
{
return $this->config['manifests'][$name];
}
}

150
src/Assets/Manifest.php Normal file
View File

@@ -0,0 +1,150 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file was copied from Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/
*/
namespace Webshr\Core\Assets;
use Webshr\Core\Support\Str;
use Webshr\Core\Assets\Exceptions\Bundle_Not_Found_Exception;
use Webshr\Core\Assets\Contracts\Asset as Asset_Interface;
use Webshr\Core\Assets\Contracts\Asset_Meta as Asset_Meta_Interface;
use Webshr\Core\Assets\Contracts\Bundle as Bundle_Interface;
use Webshr\Core\Assets\Contracts\Manifest as Manifest_Interface;
class Manifest implements Manifest_Interface
{
/**
* The manifest assets.
*
* @var array
*/
protected $assets;
/**
* The manifest meta assets.
*
* @var array
*/
protected $metas;
/**
* The manifest bundles.
*
* @var array
*/
protected $bundles;
/**
* The manifest path.
*
* @var string
*/
protected $path;
/**
* The manifest URI.
*
* @var string
*/
protected $uri;
/**
* Create a new manifest instance.
*/
public function __construct(string $path, string $uri, array $assets = [], ?array $bundles = null, ?array $metas = null)
{
$this->path = $path;
$this->uri = $uri;
$this->bundles = $bundles;
$this->metas = $metas;
foreach ($assets as $original => $revved) {
$this->assets[$this->normalize_relative_path($original)] = $this->normalize_relative_path($revved);
}
}
/**
* Get specified asset.
*
* @param string $key
*/
public function asset($key): Asset_Interface
{
$key = $this->normalize_relative_path($key);
$relativePath = $this->assets[$key] ?? $key;
$path = Str::before("{$this->path}/{$relativePath}", '?');
$uri = "{$this->uri}/{$relativePath}";
return Asset_Factory::create($path, $uri);
}
/**
* Get specified asset.
*
* @param string $key
*/
public function php($key)
{
$key = $this->normalize_relative_path($key);
$relativePath = $this->assets[$key] ?? $key;
$path = Str::before("{$this->path}/{$relativePath}", '?');
$uri = "{$this->uri}/{$relativePath}";
return Asset_Factory::create($path, $uri, 'php');
}
/**
* Get specified meta asset.
*
* @param string $key
*/
public function meta($key): Asset_Meta_Interface
{
$key = $this->normalize_relative_path($key);
// Check if the meta asset is already cached
if (isset($this->metas[$key])) {
return $this->metas[$key];
}
$relativePath = $this->assets[$key] ?? $key;
$path = Str::before("{$this->path}/{$relativePath}", '?');
$uri = "{$this->uri}/{$relativePath}";
if (! isset($this->metas[$key])) {
$meta = Asset_Factory::create($path, $uri, 'meta');
} else {
$meta = new Meta($key, $this->metas[$key], $this->path, $this->uri);
}
// Cache the created meta asset
$this->metas[$key] = $meta;
return $meta;
}
/**
* Get specified bundles.
*
* @param string $key
*
* @throws \Webshr\Core\Assets\Exceptions\Bundle_Not_Found_Exception
*/
public function bundle($key): Bundle_Interface
{
if (! isset($this->bundles[$key])) {
throw new Bundle_Not_Found_Exception("Bundle [{$key}] not found in manifest.");
}
return new Bundle($key, $this->bundles[$key], $this->path, $this->uri);
}
/**
* Normalizes to forward slashes and removes leading slash.
*/
protected function normalize_relative_path(string $path): string
{
$path = str_replace('\\', '/', $path);
$path = preg_replace('%//+%', '/', $path);
return ltrim($path, './');
}
}

75
src/Assets/Meta.php Normal file
View File

@@ -0,0 +1,75 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Assets;
use Webshr\Core\Assets\Contracts\Asset_Meta as Meta_Interface;
class Meta implements Meta_Interface
{
//use Conditional, Enqueuable;
/**
* The meta ID.
*
* @var string
*/
protected $id;
/**
* The meta path.
*
* @var string
*/
protected $path;
/**
* The meta URI.
*
* @var string
*/
protected $uri;
/**
* The meta contents.
*
* @var array
*/
protected $meta;
/**
* Create a new meta.
*/
public function __construct(string $id, array $meta, string $path, string $uri = '/')
{
$this->id = $id;
$this->path = $path;
$this->uri = $uri;
$this->meta = $meta;
$this->dependencies();
$this->version();
}
/**
* Get the meta dependencies.
*
* @return array
*/
public function version()
{
return $this->meta['version'] ?? '';
}
/**
* Get the meta dependencies.
*
* @return array
*/
public function dependencies()
{
return $this->meta['dependencies'] ?? [];
}
}

242
src/Bootloader.php Normal file
View File

@@ -0,0 +1,242 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file heavily inspired by Roots\Acorn.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/
*/
namespace Webshr\Core;
use Webshr\Core\Filesystem\Filesystem;
use Webshr\Core\Config\Repository;
class Bootloader
{
/**
* The Bootloader instance.
*/
protected static $instance;
/**
* The Application instance.
*
* @var \Webshr\Core\Application|null
*/
protected ?Application $app;
/**
* The Filesystem instance.
*
* @var \Webshr\Core\Filesystem\Filesystem
*/
protected Filesystem $files;
/**
* The configuration repository.
*
* @var \Webshr\Core\Config\Repository
*/
protected Repository $config;
/**
* The application's base path.
*
* @var string
*/
protected string $base_path = '';
/**
* The configuration settings.
*
* @var array
*/
protected array $configss;
/**
* Create a new bootloader instance.
*
* @param \Webshr\Core\Application|null $app
* @param \Webshr\Core\Filesystem\Filesystem|null $files
*/
public function __construct(?Application $app = null, ?Filesystem $files = null)
{
$this->app = $app;
$this->files = $files ?? new Filesystem();
// Load all configuration files into the repository
$this->config = new Repository($this->load_configuration_files());
static::$instance ??= $this;
}
/**
* Boot the Application.
*
* @return void
*/
public function __invoke(): void
{
$this->boot();
}
/**
* Set the Bootloader instance.
*
* @param \Webshr\Core\Bootloader|null $bootloader
*
* @return void
*/
public static function set_instance(?self $bootloader): void
{
static::$instance = $bootloader;
}
/**
* Get the Bootloader instance.
*
* @param \Webshr\Core\Application|null $app
*
* @return \Webshr\Core\Bootloader
*/
public static function get_instance(?Application $app = null): static
{
return static::$instance ??= new static($app);
}
/**
* Boot the Application.
*
* @param callable|null $callback
*
* @return void
*/
public function boot(?callable $callback = null): void
{
$this->get_application();
if ($callback) {
$callback($this->app);
}
if ($this->app->has_been_bootstrapped()) {
return;
}
$this->app->boot();
}
/**
* Initialize and retrieve the Application instance.
*
* @return \Webshr\Core\Application
*/
public function get_application(): Application
{
$this->app ??= new Application($this->encryption(), $this->base_path(), $this->use_path());
$this->app->use_environment_path($this->environment_path());
$this->app->use_default_manifest($this->default_manifest());
$this->app->use_manifests($this->manifests());
$this->app->use_modules($this->modules());
return $this->app;
}
/**
* Load all configuration files.
*
* @return array
*/
protected function load_configuration_files(): array
{
$config_path = get_template_directory() . '/config';
$config_files = $this->files->files($config_path);
$config = [];
foreach ($config_files as $file) {
$key = basename($file, '.php');
$config[$key] = require $file;
}
return $config;
}
/**
* Get the application's encryption values.
*
* @return array
*/
protected function encryption(): array
{
$encryption = [];
$encryption['cipher'] = $this->config->get('app.cipher');
$encryption['key'] = $this->config->get('app.key');
$encryption['previous_keys'] = $this->config->get('app.previous_keys');
return $encryption;
}
/**
* Get the application's base path.
*
* @return string
*/
protected function base_path(): string
{
return $this->config->get('app.paths.base');
}
/**
* Get the environment file path.
*
* @return string
*/
protected function environment_path(): string
{
return $this->config->get('app.paths.env');
}
/**
* Use paths that are configurable by the developer.
*
* @return array
*/
protected function use_path(): array
{
return $this->config->get('app.paths');
}
/**
* Get the default manifest name.
*
* @return string
*/
protected function default_manifest(): string
{
return $this->config->get('assets.default');
}
/**
* Get the application's manifests.
*
* @return array
*/
protected function manifests(): array
{
return $this->config->get('assets.manifests');
}
/**
* Get the application's modules
*
* @return array
*/
protected function modules(): array
{
return $this->config->get('app.modules');
}
/**
* Get the application's aliases.
*
* @return array
*/
protected function aliases(): array
{
return $this->config->get('app.aliases');
}
}

109
src/Config/Environment.php Normal file
View File

@@ -0,0 +1,109 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Laravel\Illuminate package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Config;
use RuntimeException;
class Environment
{
/**
* Gets the value of an environment variable.
*
* @param string $key
* @param mixed $default
* @return mixed
*/
public static function get($key, $default = null)
{
$value = getenv($key);
if ($value === false) {
return \Webshr\Core\value($default);
}
switch (strtolower($value)) {
case 'true':
case '(true)':
return true;
case 'false':
case '(false)':
return false;
case 'empty':
case '(empty)':
return '';
case 'null':
case '(null)':
return null;
}
if (($valueLength = strlen($value)) > 1 && $value[0] === '"' && $value[($valueLength - 1)] === '"') {
return substr($value, 1, -1);
}
return $value;
}
/**
* Get the value of a required environment variable.
*
* @param string $key
* @return mixed
*
* @throws \RuntimeException
*/
public static function get_or_fail($key)
{
$value = static::get($key);
if (is_null($value)) {
throw new RuntimeException("Environment variable [{$key}] has no value.");
}
return $value;
}
/**
* Check if an environment variable exists.
*
* @param string $key
* @return bool
*/
public static function has($key)
{
return getenv($key) !== false;
}
/**
* Set an environment variable.
*
* @param string $key
* @param mixed $value
* @return bool
*/
public static function set($key, $value)
{
return putenv("{$key}={$value}");
}
/**
* Get all environment variables.
*
* @return array
*/
public static function all()
{
return getenv();
}
}

189
src/Config/Repository.php Normal file
View File

@@ -0,0 +1,189 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Laravel\Illuminate package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Config;
use ArrayAccess;
use Webshr\Core\Contracts\Repository as Config_Interface;
use Webshr\Core\Support\Arriable;
use Webshr\Core\Support\Traits\Macroable;
class Repository implements ArrayAccess, Config_Interface
{
use Macroable;
/**
* All of the configuration items.
*
* @var array
*/
protected $items = [];
/**
* Create a new configuration repository.
*
* @param array $items
* @return void
*/
public function __construct(array $items = [])
{
$this->items = $items;
}
/**
* Determine if the given configuration value exists.
*
* @param string $key
* @return bool
*/
public function has($key)
{
return Arriable::has($this->items, $key);
}
/**
* Get the specified configuration value.
*
* @param array|string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null)
{
if (is_array($key)) {
return $this->get_many($key);
}
return Arriable::get($this->items, $key, $default);
}
/**
* Get many configuration values.
*
* @param array $keys
* @return array
*/
public function get_many($keys)
{
$config = [];
foreach ($keys as $key => $default) {
if (is_numeric($key)) {
[$key, $default] = [$default, null];
}
$config[$key] = Arriable::get($this->items, $key, $default);
}
return $config;
}
/**
* Set a given configuration value.
*
* @param array|string $key
* @param mixed $value
* @return void
*/
public function set($key, $value = null)
{
$keys = is_array($key) ? $key : [$key => $value];
foreach ($keys as $key => $value) {
Arriable::set($this->items, $key, $value);
}
}
/**
* Prepend a value onto an array configuration value.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function prepend($key, $value)
{
$array = $this->get($key, []);
array_unshift($array, $value);
$this->set($key, $array);
}
/**
* Push a value onto an array configuration value.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function push($key, $value)
{
$array = $this->get($key, []);
$array[] = $value;
$this->set($key, $array);
}
/**
* Get all of the configuration items for the application.
*
* @return array
*/
public function all()
{
return $this->items;
}
/**
* Determine if the given configuration option exists.
*
* @param string $key
* @return bool
*/
public function offsetExists($key): bool
{
return $this->has($key);
}
/**
* Get a configuration option.
*
* @param string $key
* @return mixed
*/
public function offsetGet($key): mixed
{
return $this->get($key);
}
/**
* Set a configuration option.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function offsetSet($key, $value): void
{
$this->set($key, $value);
}
/**
* Unset a configuration option.
*
* @param string $key
* @return void
*/
public function offsetUnset($key): void
{
$this->set($key, null);
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Illuminate\Foundation package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Contracts;
interface Application
{
/**
* Get the version number of the application.
*
* @return string
*/
public function version();
/**
* Get the base path of the installation.
*
* @param string $path
* @return string
*/
public function base_path($path = '');
/**
* Get the path to the application configuration files.
*
* @param string $path
* @return string
*/
public function config_path($path = '');
/**
* Get the path to the language files.
*
* @param string $path
* @return string
*/
public function lang_path($path = '');
/**
* Register a shared binding in the application.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null);
/**
* Resolve the given type from the application.
*
* @param string $abstract
* @param array $parameters
* @return mixed
*
* @throws \Webshr\Core\Exceptions\Binding_Resolution_Exception
*/
public function make($abstract, array $parameters = []);
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param callable|string $callback
* @param array $parameters
* @param string|null $default_method
* @return mixed
*/
public function call($callback, array $parameters = [], $default_method = null);
/**
* Get the value of a configuration key.
*
* @param string $key
* @return mixed
*/
public function get(string $key);
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Illuminate\Foundation package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Contracts;
interface Deferrable_Module
{
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides();
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Illuminate\Foundation package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Contracts;
interface Repository
{
/**
* Determine if the given configuration value exists.
*
* @param string $key
* @return bool
*/
public function has($key);
/**
* Get the specified configuration value.
*
* @param array|string $key
* @param mixed $default
* @return mixed
*/
public function get($key, $default = null);
/**
* Get all of the configuration items for the application.
*
* @return array
*/
public function all();
/**
* Set a given configuration value.
*
* @param array|string $key
* @param mixed $value
* @return void
*/
public function set($key, $value = null);
/**
* Prepend a value onto an array configuration value.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function prepend($key, $value);
/**
* Push a value onto an array configuration value.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function push($key, $value);
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Webshr\Core\Filesystem\Exceptions;
use Exception;
class File_Not_Found_Exception extends Exception
{
//
}

View File

@@ -0,0 +1,184 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Roots\Acorn package.
* Original author: Roots
* Original source: https://github.com/roots/acorn/blob/main/src/Roots/Acorn/
*/
namespace Webshr\Core\Filesystem;
class Filesystem
{
/**
* Normalizes file path separators
*
* @param mixed $path
* @param string $separator
* @return mixed
*/
public function normalize_path($path, $separator = '/')
{
return preg_replace('#/+#', $separator, strtr($path, '\\', '/'));
}
/**
* Find the closest file up the directory tree.
*
* @param string $path
* @param string $file
* @return string|null
*/
public function closest($path, $file)
{
$currentDirectory = $path;
while ($this->is_readable($currentDirectory)) {
if ($this->is_file($filePath = $currentDirectory . DIRECTORY_SEPARATOR . $file)) {
return $filePath;
}
$parentDirectory = $this->dirname($currentDirectory);
if (empty($parentDirectory) || $parentDirectory === $currentDirectory) {
break;
}
$currentDirectory = $parentDirectory;
}
return null;
}
/**
* Get relative path of target from specified base
*
* @param string $base_path
* @param string $target_path
* @return string
*
* @copyright Fabien Potencier
* @license GPL-3.0-or-later
*
* @link https://github.com/symfony/routing/blob/v4.1.1/Generator/UrlGenerator.php#L280-L329
*/
public function get_relative_path($base_path, $target_path)
{
$base_path = $this->normalize_path($base_path);
$target_path = $this->normalize_path($target_path);
if ($base_path === $target_path) {
return '';
}
$source_dirs = explode('/', ltrim($base_path, '/'));
$target_dirs = explode('/', ltrim($target_path, '/'));
array_pop($source_dirs);
$target_file = array_pop($target_dirs);
foreach ($source_dirs as $i => $dir) {
if (isset($target_dirs[$i]) && $dir === $target_dirs[$i]) {
unset($source_dirs[$i], $target_dirs[$i]);
} else {
break;
}
}
$target_dirs[] = $target_file;
$path = str_repeat('../', count($source_dirs)) . implode('/', $target_dirs);
return $path === '' || $path[0] === '/'
|| ($colon_pos = strpos($path, ':')) !== false && ($colon_pos < ($slash_pos = strpos($path, '/'))
|| $slash_pos === false)
? "./$path" : $path;
}
/**
* Ensure a directory exists.
*
* @param string $path
* @param int $mode
* @param bool $recursive
* @return void
*/
public function ensure_directory_exists($path, $mode = 0755, $recursive = true)
{
if (! $this->is_directory($path)) {
$this->make_directory($path, $mode, $recursive);
}
}
/**
* Create a directory.
*
* @param string $path
* @param int $mode
* @param bool $recursive
* @param bool $force
* @return bool
*/
public function make_directory($path, $mode = 0755, $recursive = false, $force = false)
{
if ($force) {
return @mkdir($path, $mode, $recursive);
}
return mkdir($path, $mode, $recursive);
}
/**
* Extract the parent directory from a file path.
*
* @param string $path
* @return string
*/
public function dirname($path)
{
return pathinfo($path, PATHINFO_DIRNAME);
}
/**
* Determine if the given path is a directory.
*
* @param string $directory
* @return bool
*/
public function is_directory($directory)
{
return is_dir($directory);
}
/**
* Get all files in a directory.
*
* @param string $directory
* @return array
*/
public function files(string $directory): array
{
return glob($directory . '/*.php');
}
/**
* Determine if the given path is a file.
*
* @param string $file
* @return bool
*/
public function is_file($file)
{
return is_file($file);
}
/**
* Determine if the given path is readable.
*
* @param string $path
* @return bool
*/
public function is_readable($path)
{
return is_readable($path);
}
}

97
src/Filesystem/Loader.php Normal file
View File

@@ -0,0 +1,97 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @version 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Filesystem;
use DirectoryIterator;
/**
* Provides a set of methods that are used to load files.
*/
class Loader
{
/**
* Iterates through a directory and executes the provided callback function
* on each file or folder in the directory (excluding dot files).
*
* @since 0.1.0
*
* @param string $dir Absolute path to the directory.
* @param callable $callback The callback function.
*
* @return array An array of the callback results.
*/
public function iterate_dir(string $dir, callable $callback): array
{
$output = [];
if (! is_dir($dir)) {
return $output;
}
$directory_iterator = new DirectoryIterator($dir);
foreach ($directory_iterator as $file) {
if ($file->isDot()) {
continue;
}
$callback_result = call_user_func($callback, $file);
$output[] = $callback_result;
}
return $output;
}
/**
* Recursively require all files in a specific directory.
*
* By default, requires all php files in a specific directory once.
* Optionally able to specify the files in an array to load in a certain order.
* Starting and trailing slashes will be stripped for the directory and all files provided.
*
* @since 0.1.0
*
* @param string $dir Directory to search through.
* @param array $files Optional array of files to include. If this is set, only the files specified will be loaded.
*/
public function load(string $dir, array $files = []): void
{
$dir = trim($dir, '/');
if ($files === []) {
$dir = get_template_directory() . '/' . $dir;
$php_files = [];
$this->iterate_dir($dir, function ($file) use (&$php_files): void {
if ($file->isDir()) {
$dir_path = trim(str_replace(get_template_directory(), '', $file->getPathname()), '/');
$this->load($dir_path);
} elseif ($file->isFile() && $file->getExtension() === 'php') {
$file_path = $file->getPathname();
$php_files[] = $file_path;
}
});
// Sort files alphabetically.
sort($php_files);
foreach ($php_files as $php_file) {
require_once $php_file;
}
} else {
sort($files);
foreach ($files as $file) {
$file = $file ?? '';
// Fix Passing null to parameter #1 ($string) of type string is deprecated error.
$file_path = $dir . '/' . ltrim($file, '/');
if (locate_template($file_path, true, true) === '' || locate_template($file_path, true, true) === '0') {
trigger_error(sprintf(__('Error locating %s for inclusion', 'webshr'), $file_path), E_USER_ERROR,);
}
}
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Webshr\Core\Filesystem;
if (! function_exists('Webshr\Core\Filesystem\join_paths')) {
/**
* Join the given paths together.
*
* @param string|null $base_path
* @param string ...$paths
* @return string
*/
function join_paths($base_path, ...$paths)
{
foreach ($paths as $index => $path) {
if (empty($path)) {
unset($paths[$index]);
} else {
$paths[$index] = DIRECTORY_SEPARATOR . ltrim($path, DIRECTORY_SEPARATOR);
}
}
return $base_path . implode('', $paths);
}
}

60
src/Module.php Normal file
View File

@@ -0,0 +1,60 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core;
use Webshr\Core\Modules\Contracts\Module as Module_Interface;
use Webshr\Core\Modules\Module\Module as Base_Module;
/**
* Module class.
*/
class Module extends Base_Module implements Module_Interface
{
/**
* Determine if the module can be registered.
*
* @return bool
*/
public function can_register(): bool
{
return true;
}
/**
* Register the module in WordPress.
*
* @return void
*/
public function register(): void
{
//
}
/**
* Register the action hooks for the module.
*
* @return void
*/
public function register_actions(): void
{
// Default implementation can be overridden by child classes
}
/**
* Register the filter hooks for the module.
*
* @return void
*/
public function register_filters(): void
{
// Default implementation can be overridden by child classes
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* @package Webshr\Theme
* @since 1.0.0
* @version 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Modules\Contracts;
/**
* Deferrable module interface.
*/
interface Deferrable_Module
{
/**
* Get the services provided by the module.
*
* @return array
*/
public function provides();
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* @package Webshr\Theme
* @since 1.0.0
* @version 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Modules\Contracts;
/**
* Theme module interface.
* Defines the contract that every module must follow.
*
*/
interface Module
{
/**
* Determine whether the module can be registered in the current context.
*
* @return bool
*/
public function can_register(): bool;
/**
* Register the module in WordPress.
*
* @return void
*/
public function register(): void;
}

104
src/Modules/Manager.php Normal file
View File

@@ -0,0 +1,104 @@
<?php
/**
* @package Webshr\Theme
* @since 1.0.0
* @version 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Modules;
use InvalidArgumentException;
use Webshr\Core\Modules\Contracts\Module as Module_Interface;
/**
* Theme module manager.
* Provides a set of methods to manage theme modules.
*/
class Manager
{
/**
* Resolved modules.
*
* @var Module_Interface[]
*/
protected $modules = [];
/**
* Assets Config
*
* @var array
*/
protected $config;
/**
* Constructor.
*
* @param array $modules Associative array of modules.
*/
public function __construct($config = [])
{
$this->config = $config;
}
/**
* Register the given module
*
* @param Module_Interface $module
* @return static
*/
public function register(string $name, Module_Interface $module): self
{
$this->modules[$name] = $module;
return $this;
}
/**
* Get a Module
*
*/
public function module(string $name, ?array $config = null): Module_Interface
{
$module = $this->modules[$name] ?? $this->resolve($name, $config);
return $this->modules[$name] = $module;
}
/**
* Get all modules.
*/
public function modules(): array
{
return $this->modules;
}
/**
* Resolve the given module.
*
* @throws InvalidArgumentException
*/
protected function resolve(string $name, ?array $config): Module_Interface
{
$config = $config ?? $this->get_config($name);
if (isset($config['handler'])) {
return new $config['handler']($config);
}
// If there's no handler in the config, use the module class directly from the modules array
if (isset($this->config['modules'][$name])) {
$moduleClass = $this->config['modules'][$name];
return new $moduleClass($this->app ?? null);
}
throw new InvalidArgumentException("Module '{$name}' has no handler defined and could not be resolved.");
}
/**
* Get the modules configuration.
*/
protected function get_config(string $name): array
{
return $this->config['modules'][$name];
}
}

View File

@@ -0,0 +1,155 @@
<?php
/**
* @package Webshr\Theme
* @since 1.0.0
* @version 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*/
namespace Webshr\Core\Modules\Module;
use Closure;
use Webshr\Core\Modules\Contracts\Module as Module_Interface;
use Webshr\Core\Modules\Contracts\Deferrable_Module as Deferrable_Module_Interface;
/**
* Theme module class.
* Defines the basic structure for a theme module.
*
*/
abstract class Module implements Module_Interface
{
/**
* The application instance.
*
* @var \Webshr\Core\Contracts\Application
*/
protected $app;
/**
* All of the registered booting callbacks.
*
* @var array
*/
protected $booting_callbacks = [];
/**
* All of the registered booted callbacks.
*
* @var array
*/
protected $booted_callbacks = [];
/**
* Whether the module will be a deferred one or not.
*
* @var bool
*/
protected $deferred = false;
/**
* Create a new module instance.
*
* @param \Webshr\Core\Application $app
* @return void
*/
public function __construct($app)
{
$this->app = $app;
}
/**
* Checks whether the module should be registered in the current context.
*
* Must be implemented by the child module.
*
* @return bool
*/
abstract public function can_register(): bool;
/**
* Register the module in WordPress.
*
* Must be implemented by the child module.
*
* @return void
*/
abstract public function register(): void;
/**
* Register a booting callback to be run before the "boot" method is called.
*
* @param \Closure $callback
* @return void
*/
public function booting(Closure $callback)
{
$this->booting_callbacks[] = $callback;
}
/**
* Register a booted callback to be run after the "boot" method is called.
*
* @param \Closure $callback
* @return void
*/
public function booted(Closure $callback)
{
$this->booted_callbacks[] = $callback;
}
/**
* Call the registered booting callbacks.
*
* @return void
*/
public function call_booting_callbacks()
{
$index = 0;
while ($index < count($this->booting_callbacks)) {
$this->app->call($this->booting_callbacks[$index]);
$index++;
}
}
/**
* Call the registered booted callbacks.
*
* @return void
*/
public function call_booted_callbacks()
{
$index = 0;
while ($index < count($this->booted_callbacks)) {
$this->app->call($this->booted_callbacks[$index]);
$index++;
}
}
/**
* Get the services provided by the module.
*
* @return array
*/
public function provides()
{
return [];
}
/**
* Get the events that trigger this module to register.
*
* @return array
*/
public function when()
{
return [];
}
/**
* Determine if the module is deferred.
*
* @return bool
*/
public function is_deferred()
{
return $this instanceof Deferrable_Module_Interface;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Module Factory class.
*
* @package Webshr\Theme
* @since 1.0.0
*/
namespace Webshr\Core\Modules;
use Webshr\Core\Modules\Contracts\Module as Module_interface;
class Module_Factory
{
/**
* @var Module_Interface The module instance.
*/
protected $module;
/**
* Create Module instance.
*
* @param string $class Module class name
* @return Module_Interface
* @throws \Exception
*/
public function create(string $class): Module_Interface
{
if (! class_exists($class) || ! is_subclass_of($class, Module_Interface::class)) {
throw new \Exception("Module class {$class} must exist and implement Module_Interface.");
}
return new $class();
}
}

536
src/Support/Arriable.php Normal file
View File

@@ -0,0 +1,536 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Laravel\Illuminate\Foundation package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Support;
use ArrayAccess;
use Closure;
/**
* A collection of tools dealing with arrays
*
* @credit (limited version of) illuminate/support
* @codeCoverageIgnore
*/
class Arriable
{
/**
* Converts the given value to an array.
*
* If the provided value is already an array, it will be returned as-is.
* Otherwise, the value will be wrapped in an array.
*
* @param mixed $value The value to be converted to an array.
* @return array The resulting array.
*/
public static function to_array($value): array
{
return is_array($value) ? $value : [$value];
}
/**
* Determine whether the given value is array accessible.
*
* @param mixed $value
* @return bool
*/
public static function accessible($value)
{
return \is_array($value) || $value instanceof ArrayAccess;
}
/**
* Add an element to an array using "dot" notation if it doesn't exist.
*
* @param array $array
* @param string $key
* @param mixed $value
* @return array
*/
public static function add($array, $key, $value)
{
if (\is_null(static::get($array, $key))) {
static::set($array, $key, $value);
}
return $array;
}
/**
* Collapse an array of arrays into a single array.
*
* @param array $array
* @return array
*/
public static function collapse($array)
{
$results = [];
foreach ($array as $values) {
if (! \is_array($values)) {
continue;
}
$results = \array_merge($results, $values);
}
return $results;
}
/**
* Divide an array into two arrays. One with keys and the other with values.
*
* @param array $array
* @return array
*/
public static function divide($array)
{
return [\array_keys($array), \array_values($array)];
}
/**
* Flatten a multi-dimensional associative array with dots.
*
* @param array $array
* @param string $prepend
* @return array
*/
public static function dot($array, $prepend = '')
{
$results = [];
foreach ($array as $key => $value) {
if (\is_array($value) && ! empty($value)) {
$results = \array_merge($results, static::dot($value, $prepend . $key . '.'));
} else {
$results[$prepend . $key] = $value;
}
}
return $results;
}
/**
* Get all of the given array except for a specified array of items.
*
* @param array $array
* @param array|string $keys
* @return array
*/
public static function except($array, $keys)
{
static::forget($array, $keys);
return $array;
}
/**
* Determine if the given key exists in the provided array.
*
* @param array|ArrayAccess $array
* @param string|int $key
* @return bool
*/
public static function exists($array, $key)
{
if ($array instanceof ArrayAccess) {
return $array->offsetExists($key);
}
return \array_key_exists($key, $array);
}
/**
* Get the first element in an array passing a given truth test.
*
* @param array $array
* @param callable|null $callback
* @param mixed $default
* @return mixed
*/
public static function first($array, $callback = null, $default = null)
{
if (\is_null($callback)) {
if (empty($array)) {
return $default;
}
foreach ($array as $item) {
return $item;
}
}
foreach ($array as $key => $value) {
if (\call_user_func($callback, $value, $key)) {
return $value;
}
}
return $default;
}
/**
* Get the last element in an array passing a given truth test.
*
* @param array $array
* @param callable|null $callback
* @param mixed $default
* @return mixed
*/
public static function last($array, $callback = null, $default = null)
{
if (\is_null($callback)) {
return empty($array) ? $default : \end($array);
}
return static::first(\array_reverse($array, true), $callback, $default);
}
/**
* Remove one or many array items from a given array using "dot" notation.
*
* @param array $array
* @param array|string $keys
* @return void
*/
public static function forget(&$array, $keys)
{
$original = &$array;
$keys = (array) $keys;
if (\count($keys) === 0) {
return;
}
foreach ($keys as $key) {
// if the exact key exists in the top-level, remove it
if (static::exists($array, $key)) {
unset($array[$key]);
continue;
}
$parts = \explode('.', $key);
// clean up before each pass
$array = &$original;
while (\count($parts) > 1) {
$part = \array_shift($parts);
if (isset($array[$part]) && \is_array($array[$part])) {
$array = &$array[$part];
} else {
continue 2;
}
}
unset($array[\array_shift($parts)]);
}
}
/**
* Get an item from an array using "dot" notation.
*
* @param array|ArrayAccess $array
* @param int|string $key
* @param mixed $default
* @return mixed
*/
public static function get($array, $key, $default = null)
{
if (! static::accessible($array)) {
return self::return_default($default);
}
if (static::exists($array, $key)) {
return $array[$key];
}
$key = (string) $key;
if (false === strpos($key, '.')) {
return $array[$key] ?? self::return_default($default);
}
foreach (\explode('.', $key) as $segment) {
if (static::accessible($array) && static::exists($array, $segment)) {
$array = $array[$segment];
} else {
return self::return_default($default);
}
}
return $array;
}
/**
* Check if an item or items exist in an array using "dot" notation.
*
* @param array|ArrayAccess $array
* @param string|array $keys
* @return bool
*/
public static function has($array, $keys)
{
if (\is_null($keys)) {
return false;
}
$keys = (array) $keys;
if (! $array) {
return false;
}
if ($keys === []) {
return false;
}
foreach ($keys as $key) {
$subKeyArray = $array;
if (static::exists($array, $key)) {
continue;
}
foreach (\explode('.', $key) as $segment) {
if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
$subKeyArray = $subKeyArray[$segment];
} else {
return false;
}
}
}
return true;
}
/**
* Determines if an array is associative.
*
* An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
*
* @param array $array
* @return bool
*/
public static function is_assoc(array $array)
{
$keys = \array_keys($array);
return \array_keys($keys) !== $keys;
}
/**
* Get a subset of the items from the given array.
*
* @param array $array
* @param array|string $keys
* @return array
*/
public static function only($array, $keys)
{
return \array_intersect_key($array, \array_flip((array) $keys));
}
/**
* Pluck an array of values from an array.
*
* @param array $array
* @param string|array $value
* @param string|array|null $key
* @return array
*/
public static function pluck($array, $value, $key = null)
{
$results = [];
list($value, $key) = static::exploded_pluck_parameters($value, $key);
foreach ($array as $item) {
$itemValue = static::data_get($item, $value);
// If the key is "null", we will just append the value to the array and keep
// looping. Otherwise we will key the array using the value of the key we
// received from the developer. Then we'll return the final array form.
if (\is_null($key)) {
$results[] = $itemValue;
} else {
$itemKey = static::data_get($item, $key);
$results[$itemKey] = $itemValue;
}
}
return $results;
}
/**
* Explode the "value" and "key" arguments passed to "pluck".
*
* @param string|array $value
* @param string|array|null $key
* @return array
*/
protected static function exploded_pluck_parameters($value, $key)
{
$value = \is_string($value) ? \explode('.', $value) : $value;
$key = \is_null($key) || \is_array($key) ? $key : \explode('.', $key);
return [$value, $key];
}
/**
* Push an item onto the beginning of an array.
*
* @param array $array
* @param mixed $value
* @param mixed $key
* @return array
*/
public static function prepend($array, $value, $key = null)
{
if (\is_null($key)) {
\array_unshift($array, $value);
} else {
$array = [$key => $value] + $array;
}
return $array;
}
/**
* Get a value from the array, and remove it.
*
* @param array $array
* @param string $key
* @param mixed $default
* @return mixed
*/
public static function pull(&$array, $key, $default = null)
{
$value = static::get($array, $key, $default);
static::forget($array, $key);
return $value;
}
/**
* Set an array item to a given value using "dot" notation.
*
* If no key is given to the method, the entire array will be replaced.
*
* @param array $array
* @param string $key
* @param mixed $value
* @return array
*/
public static function set(&$array, $key, $value)
{
if (\is_null($key)) {
return $array = $value;
}
$keys = \explode('.', $key);
while (\count($keys) > 1) {
$key = \array_shift($keys);
// If the key doesn't exist at this depth, we will just create an empty array
// to hold the next value, allowing us to create the arrays to hold final
// values at the correct depth. Then we'll keep digging into the array.
if (! isset($array[$key]) || ! \is_array($array[$key])) {
$array[$key] = [];
}
$array = &$array[$key];
}
$array[\array_shift($keys)] = $value;
return $array;
}
/**
* Shuffle the given array and return the result.
*
* @param array $array
* @return array
*/
public static function shuffle($array)
{
\shuffle($array);
return $array;
}
/**
* Recursively sort an array by keys and values.
*
* @param array $array
* @return array
*/
public static function sort_recursive($array)
{
foreach ($array as &$value) {
if (\is_array($value)) {
$value = static::sort_recursive($value);
}
}
if (static::is_assoc($array)) {
\ksort($array);
} else {
\sort($array);
}
return $array;
}
/**
* Get an item from an array or object using "dot" notation.
*
* @param mixed $target
* @param string|array $key
* @param mixed $default
* @return mixed
*/
public static function data_get($target, $key, $default = null)
{
if (\is_null($key)) {
return $target;
}
$key = \is_array($key) ? $key : \explode('.', $key);
while (! \is_null($segment = \array_shift($key))) {
if ($segment === '*') {
if (! \is_array($target)) {
return $default;
}
$result = static::pluck($target, $key);
return \in_array('*', $key) ? static::collapse($result) : $result;
}
if (static::accessible($target) && static::exists($target, $segment)) {
$target = $target[$segment];
} elseif (\is_object($target) && isset($target->{$segment})) {
$target = $target->{$segment};
} else {
return $default;
}
}
return $target;
}
/**
* Filter the array using the given callback.
*
* @param array $array
* @param callable $callback
* @return array
*/
public static function where($array, callable $callback)
{
return array_filter($array, $callback);
}
/**
* Returns the default value. If the default value is a Closure, it executes the Closure and returns its result.
*
* @param mixed $default The default value or a Closure that returns the default value.
* @return mixed The default value or the result of the Closure execution.
*/
private static function return_default($default)
{
if ($default instanceof Closure) {
$default = $default();
}
return $default;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Laravel\Illuminate package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Support\Contracts;
/**
* @template TKey of array-key
* @template TValue
*/
interface Arrayable
{
/**
* Get the instance as an array.
*
* @return array<TKey, TValue>
*/
public function to_array();
}

View File

@@ -0,0 +1,30 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Laravel\Illuminate package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Support\Contracts;
/**
* @template TKey of array-key
* @template TValue
*/
interface Jsonable
{
/**
* Convert the object to its JSON representation.
*
* @param int $options
* @return string
*/
public function to_json();
}

393
src/Support/Str.php Normal file
View File

@@ -0,0 +1,393 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Laravel\Illuminate\Foundation package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Support;
use RuntimeException;
class Str
{
/**
* Check if a given substring is present within a subject string.
*
* @param string $string The string to search within.
* @param string $substring The substring to search for.
* @return bool True if the substring is found within the subject, false otherwise.
*/
public static function contains(string $string, string $substring): bool
{
if ('' === $substring) {
return false;
}
if ('' === $string) {
return false;
}
return false !== mb_strpos($string, $substring);
}
/**
* Get the portion of a string before the first occurrence of a given value.
*
* @param string $string
* @param string $substring
* @return string
*/
public static function before(string $string, string $substring): bool|string
{
if ($substring === '') {
return $string;
}
$result = strstr($string, (string) $substring, true);
return $result === false ? $string : $result;
}
/**
* Check if the given string contains any of the specified substrings.
*
* @param string $string The string to search within.
* @param array $substrings An array of substrings to search for.
* @return bool True if any of the substrings are found in the subject string, false otherwise.
*/
public static function contains_any(string $string, array $substrings): bool
{
foreach ($substrings as $substring) {
if (self::contains($string, $substring)) {
return true;
}
}
return false;
}
/**
* Begin a string with a single instance of a given value.
*
* @param string $value
* @param string $prefix
* @return string
*/
public static function start($value, $prefix)
{
$quoted = preg_quote($prefix, '/');
return $prefix . preg_replace('/^(?:' . $quoted . ')+/u', '', $value);
}
/**
* Determine if a given string starts with a given substring.
*
* @param string $string
* @param string|iterable<string> $substrings
* @return bool
*/
public static function starts_with(string $string, string $substrings): bool
{
if (! is_iterable($substrings)) {
$substrings = [$substrings];
}
foreach ($substrings as $substring) {
if ((string) $substring !== '' && str_starts_with($string, $substring)) {
return true;
}
}
return false;
}
/**
* Determine if a given string ends with a given substring.
*
* @param string $string The string to search in.
* @param string $substring The substring to search for.
* @return bool True if the subject ends with the substring, false otherwise.
*/
public static function ends_with(string $string, string $substring): bool
{
if ('' === $substring) {
return false;
}
return substr($string, -strlen($substring)) === $substring;
}
/**
* Determine if a given string does not end with a given substring.
*
* @param string $string The string to check.
* @param string $substring The substring to search for at the end of the subject string.
* @return bool True if the subject string does not end with the given substring, false otherwise.
*/
public static function does_not_end_with(string $string, string $substring): bool
{
return ! self::ends_with($string, $substring);
}
/**
* Return the remainder of a string after the first occurrence of a given value.
*
* @param string $subject
* @param string $search
* @return string
*/
public static function after(string $subject, string $search): string
{
return $search === '' ? $subject : array_reverse(explode($search, $subject, 2))[0];
}
/**
* Returns the portion of the string after the first occurrence of the given search string.
*
* @param string $string The input string.
* @param string $substring The string to search for.
* @return string The portion of the string after the first occurrence of the search string.
*/
public static function after_first(string $string, string $substring): string
{
return '' === $substring ? $string : array_reverse(explode($substring, $string, 2))[0];
}
/**
* Returns the portion of the string after the last occurrence of a given substring.
*
* @param string $string The string to search in.
* @param string $substring The substring to search for.
* @return string The portion of the string after the last occurrence of the substring.
* @throws RuntimeException If the substr function returns false.
*/
public static function after_last(string $string, string $substring): string
{
if ('' === $substring) {
return $string;
}
$position = strrpos($string, $substring);
if (false === $position) {
return $string;
}
$res = substr($string, $position + strlen($substring));
if (false === $res) {
// @codeCoverageIgnoreStart
throw new RuntimeException(sprintf('substr returned false for subject [%s].', $string));
// @codeCoverageIgnoreEnd
}
return $res;
}
/**
* Extracts the substring between the first occurrence of the given "from" and "to" strings.
*
* Usage: Str::between_first('xayyy', 'x', 'y') => 'a'.
*
* @param string $string The string to extract the substring from.
* @param string $from The starting delimiter string.
* @param string $to The ending delimiter string.
* @return string The extracted substring. If either "from" or "to" is an empty string, the original subject is returned.
*/
public static function between_first(string $string, string $from, string $to): string
{
if ('' === $from) {
return $string;
}
if ('' === $to) {
return $string;
}
return self::before_first(self::after_first($string, $from), $to);
}
/**
* Extracts the substring between the last occurrence of the given "from" and "to" strings.
*
* Usage: Str::between_last('xayyy', 'x', 'y') => 'a'.
*
* @param string $string The input string from which to extract the substring.
* @param string $from The starting delimiter string.
* @param string $to The ending delimiter string.
* @return string The extracted substring, or the entire `$string` string if
* either `$from` or `$to` is empty.
*/
public static function between_last(string $string, string $from, string $to): string
{
if ('' === $from) {
return $string;
}
if ('' === $to) {
return $string;
}
return self::before_last(self::after_first($string, $from), $to);
}
/**
* Returns the portion of the string before the first occurrence of the given search string.
*
* @param string $string The input string to search within.
* @param string $substring The string to search for.
* @return string The portion of the input string before the first occurrence of the search string,
* or the entire input string if the search string is not found or is empty.
*/
public static function before_first(string $string, string $substring): string
{
if ('' === $substring) {
return $string;
}
$result = strstr($string, $substring, true);
return false === $result ? $string : $result;
}
/**
* Returns the portion of the string before the last occurrence of the given substring.
*
* @param string $string The input string.
* @param string $substring The substring to search for.
* @return string The portion of the string before the last occurrence of the substring.
*/
public static function before_last(string $string, string $substring): string
{
if ('' === $substring) {
return $string;
}
$pos = mb_strrpos($string, $substring);
if (false === $pos) {
return $string;
}
return self::substr($string, 0, $pos);
}
/**
* Returns the portion of the string specified by the start and length parameters.
*
* @param string $string The input string.
* @param int $start The starting position.
* @param int|null $length The length of the substring. If omitted, the substring will extend to the end of the string.
* @return string The extracted substring.
*/
public static function substr(string $string, int $start, int $length = null): string
{
return mb_substr($string, $start, $length, 'UTF-8');
}
/**
* Cap a string with a single instance of a given value.
*
* @param string $value
* @param string $cap
* @return string
*/
public static function finish(string $value, string $cap): string
{
$quoted = preg_quote($cap, '/');
return preg_replace('/(?:' . $quoted . ')+$/u', '', $value) . $cap;
}
/**
* Get a new stringable object from the given string.
*
* @param string $string
* @return \Webshr\Core\Support\Stringable
*/
public static function of(string $string): Stringable
{
return new Stringable($string);
}
/**
* Determine if a given string matches a given pattern.
*
* This method checks if the given subject string matches the specified pattern.
* If the pattern is an exact match to the subject, it returns true immediately.
* Otherwise, it translates asterisks in the pattern into regular expression
* wildcards and performs a pattern match against the subject string.
*
* @param string $string The string to be checked.
* @param string $pattern The pattern to match against the subject.
* @return bool True if the subject matches the pattern, false otherwise.
*/
public static function is(string $string, string $pattern): bool
{
// If the given value is an exact match we can of course return true right
// from the beginning. Otherwise, we will translate asterisks and do an
// actual pattern match against the two strings to see if they match.
if ($pattern === $string) {
return true;
}
$pattern = preg_quote($pattern, '#');
// Asterisks are translated into zero-or-more regular expression wildcards
// to make it convenient to check if the strings starts with the given
// pattern such as "library/*", making any string check convenient.
$pattern = str_replace('\*', '.*', $pattern);
return 1 === preg_match('#^' . $pattern . '\z#u', $string);
}
/**
* Replace all occurrences of a substring within a string with another string.
*
* @param string $string The string to search and replace within.
* @param string $substring The substring to search for.
* @param string $replace The string to replace the substring with.
* @return string The resulting string after the replacements.
*/
public static function replace_all(string $string, string $substring, string $replace): string
{
return str_replace($substring, $replace, $string);
}
/**
* Remove all whitespace from both ends of a string.
*
* @param string $value
* @param string|null $charlist
* @return string
*/
public static function trim($value, $charlist = null)
{
if ($charlist === null) {
$trim_default_characters = " \n\r\t\v\0";
return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}' . $trim_default_characters . ']+|[\s\x{FEFF}\x{200B}\x{200E}' . $trim_default_characters . ']+$~u', '', $value) ?? trim($value);
}
return trim($value, $charlist);
}
/**
* Remove all whitespace from the beginning of a string.
*
* @param string $value
* @param string|null $charlist
* @return string
*/
public static function ltrim($value, $charlist = null)
{
if ($charlist === null) {
$trim_default_characters = " \n\r\t\v\0";
return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}' . $trim_default_characters . ']+~u', '', $value) ?? ltrim($value);
}
return ltrim($value, $charlist);
}
}

110
src/Support/Stringable.php Normal file
View File

@@ -0,0 +1,110 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Laravel\Illuminate\Foundation package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Support;
class Stringable
{
/**
* The underlying string value.
*
* @var string
*/
protected $value;
/**
* Create a new instance of the class.
*
* @param string $value
* @return void
*/
public function __construct($value = '')
{
$this->value = (string) $value;
}
/**
* Return the remainder of a string after the first occurrence of a given value.
*
* @param string $search
* @return static
*/
public function after($search)
{
return new static(Str::after($this->value, $search));
}
/**
* Return the remainder of a string after the last occurrence of a given value.
*
* @param string $search
* @return static
*/
public function after_last($search)
{
return new static(Str::after_last($this->value, $search));
}
/**
* Trim the string of the given characters.
*
* @param string $characters
* @return static
*/
public function trim($characters = null)
{
return new static(Str::trim(...array_merge([$this->value], func_get_args())));
}
/**
* Left trim the string of the given characters.
*
* @param string $characters
* @return static
*/
public function ltrim($characters = null)
{
return new static(Str::ltrim(...array_merge([$this->value], func_get_args())));
}
/**
* Begin a string with a single instance of a given value.
*
* @param string $prefix
* @return static
*/
public function start($prefix)
{
return new static(Str::start($this->value, $prefix));
}
/**
* Get the string value.
*
* @return string
*/
public function to_string()
{
return $this->value;
}
/**
* Get the string value.
*
* @return string
*/
public function __toString()
{
return $this->value;
}
}

View File

@@ -0,0 +1,123 @@
<?php
namespace Webshr\Core\Support\Traits;
use Closure;
use BadMethodCallException;
use Webshr\Core\Support\Arriable;
/**
* Add methods to classes at runtime.
* Loosely based on spatie/macroable.
*
* @codeCoverageIgnore
*/
trait Aliases
{
/**
* Registered aliases.
*
* @var array<string, array>
*/
protected $aliases = [];
/**
* Get whether an alias is registered.
*
* @param string $alias
* @return boolean
*/
public function has_alias($alias)
{
return isset($this->aliases[$alias]);
}
/**
* Get a registered alias.
*
* @param string $alias
* @return array|null
*/
public function get_alias($alias)
{
if (! $this->has_alias($alias)) {
return null;
}
return $this->aliases[$alias];
}
/**
* Register an alias.
* Useful when passed the return value of getAlias() to restore
* a previously registered alias, for example.
*
* @param array<string, mixed> $alias
* @return void
*/
public function set_alias($alias)
{
$name = Arriable::get($alias, 'name');
$this->aliases[$name] = [
'name' => $name,
'target' => Arriable::get($alias, 'target'),
'method' => Arriable::get($alias, 'method', ''),
];
}
/**
* Register an alias.
* Identical to setAlias but with a more user-friendly interface.
*
* @codeCoverageIgnore
* @param string $alias
* @param string|Closure $target
* @param string $method
* @return void
*/
public function alias($alias, $target, $method = '')
{
$this->set_alias([
'name' => $alias,
'target' => $target,
'method' => $method,
]);
}
/**
* Call alias if registered.
*
* @param string $method
* @param array $parameters
* @return mixed
*/
public function __call($method, $parameters)
{
if (! $this->has_alias($method)) {
throw new BadMethodCallException("Method {$method} does not exist.");
}
$alias = $this->aliases[$method];
if ($alias['target'] instanceof Closure) {
return call_user_func_array($alias['target']->bindTo($this, static::class), $parameters);
}
$target = $this->resolve($alias['target']);
if (! empty($alias['method'])) {
return call_user_func_array([$target, $alias['method']], $parameters);
}
return $target;
}
/**
* Resolve a dependency from the IoC container.
*
* @param string $key
* @return mixed|null
*/
abstract public function resolve($key);
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Webshr\Core\Support\Traits;
trait Application
{
}

View File

@@ -0,0 +1,127 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
* This file contains a customized adaptation of the Laravel\Illuminate package.
* Original author: Laravel
* Original source: https://laravel.com/api/master/Illuminate/Foundation.html
*/
namespace Webshr\Core\Support\Traits;
use BadMethodCallException;
use Closure;
use ReflectionClass;
use ReflectionMethod;
trait Macroable
{
/**
* The registered string macros.
*
* @var array
*/
protected static $macros = [];
/**
* Register a custom macro.
*
* @param string $name
* @param object|callable $macro
* @return void
*/
public static function macro($name, $macro)
{
static::$macros[$name] = $macro;
}
/**
* Mix another object into the class.
*
* @param object $mixin
* @param bool $replace
* @return void
*
* @throws \ReflectionException
*/
public static function mixin($mixin, $replace = true)
{
$methods = (new ReflectionClass($mixin))->getMethods(ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED);
foreach ($methods as $method) {
if ($replace || ! static::has_macro($method->name)) {
static::macro($method->name, $method->invoke($mixin));
}
}
}
/**
* Checks if macro is registered.
*
* @param string $name
* @return bool
*/
public static function has_macro($name)
{
return isset(static::$macros[$name]);
}
/**
* Flush the existing macros.
*
* @return void
*/
public static function flush_macros()
{
static::$macros = [];
}
/**
* Dynamically handle calls to the class.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public static function __callStatic($method, $parameters)
{
if (! static::has_macro($method)) {
throw new BadMethodCallException(sprintf('Method %s::%s does not exist.', static::class, $method,));
}
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
$macro = $macro->bindTo(null, static::class);
}
return $macro(...$parameters);
}
/**
* Dynamically handle calls to the class.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public function __call($method, $parameters)
{
if (! static::has_macro($method)) {
throw new BadMethodCallException(sprintf('Method %s::%s does not exist.', static::class, $method,));
}
$macro = static::$macros[$method];
if ($macro instanceof Closure) {
$macro = $macro->bindTo($this, static::class);
}
return $macro(...$parameters);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
*/
namespace Webshr\Core\Utility\Contracts;
use Webshr\Core\Utility\Exceptions\Decrypt_Exception;
use Webshr\Core\Utility\Exceptions\Encrypt_Exception;
interface Encrypter
{
/**
* Encrypts the given data.
*
* @param string $value The data to be encrypted.
* @param bool $serialize Whether to serialize the data.
* @return string The encrypted data.
* @throws Encrypt_Exception If encryption fails.
*/
public function encrypt(string $value, bool $serialize = true): string;
/**
* Decrypts the given data.
*
* @param string $payload The data to be decrypted.
* @param bool $unserialize Whether to unserialize the data.
* @return mixed The decrypted data.
* @throws Decrypt_Exception If encryption fails.
*/
public function decrypt(string $payload, bool $unserialize = true): mixed;
/**
* Get the encryption key that the encrypter is currently using.
*
* @return string
*/
public function get_key(): string;
/**
* Get the current encryption key and all previous encryption keys.
*
* @return array
*/
public function get_all_keys(): array;
/**
* Get the previous encryption keys.
*
* @return array
*/
public function get_previous_keys(): array;
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
*/
namespace Webshr\Core\Utility\Contracts;
interface Hasher
{
/**
* Create a hash for the given password.
*
* @param string $password The password to be hashed.
* @return string The hashed password.
*/
public function make($password);
/**
* Verifies that the given password matches the provided hash.
*
* @param string $password The plain text password to check.
* @param string $hash The hashed password to compare against.
* @return bool True if the password matches the hash, false otherwise.
*/
public function check($password, $hash);
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
*/
namespace Webshr\Core\Utility\Contracts;
use Webshr\Core\Utility\Exceptions\Decrypt_Exception;
use Webshr\Core\Utility\Exceptions\Encrypt_Exception;
interface String_Encrypter
{
/**
* Encrypt a string without serialization.
*
* @param string $value
* @return string
*
* @throws Encrypt_Exception
*/
public function encrypt_string(string $value);
/**
* Decrypt the given string without unserialization.
*
* @param string $payload
* @return string
*
* @throws Decrypt_Exception
*/
public function decrypt_string(string $payload);
}

352
src/Utility/Encryption.php Normal file
View File

@@ -0,0 +1,352 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
*/
namespace Webshr\Core\Utility;
use RuntimeException;
use Webshr\Core\Utility\Exceptions\Encrypt_Exception;
use Webshr\Core\Utility\Exceptions\Decrypt_Exception;
use Webshr\Core\Contracts\Application as Application_Interface;
use Webshr\Core\Utility\Contracts\Encrypter as Encrypter_Interface;
use Webshr\Core\Utility\Contracts\String_Encrypter as String_Encrypter_Interface;
class Encryption implements Encrypter_Interface, String_Encrypter_Interface
{
/**
* The encryption key.
*
* @var string
*/
protected string $key;
/**
* The previous / legacy encryption keys.
*
* @var array
*/
protected $previous_keys = [];
/**
* The encryption cipher.
*
* @var string
*/
protected string $cipher;
/**
* The supported cipher algorithms and their properties.
*
* @var array
*/
private static $supported_ciphers = [
'aes-128-cbc' => ['size' => 16, 'aead' => false],
'aes-256-cbc' => ['size' => 32, 'aead' => false],
'aes-128-gcm' => ['size' => 16, 'aead' => true],
'aes-256-gcm' => ['size' => 32, 'aead' => true],
];
/**
* Initialize the Encryption instance.
*
* @param Application_Interface $app
*/
public function __construct(Application_Interface $app)
{
$key = $app->get('key');
$cipher = $app->get('cipher') ?? 'aes-256-cbc';
// Ensure the key length matches the expected length for the cipher
$key = $this->ensure_key_length($key, $cipher);
if (! static::supported($key, $cipher)) {
$ciphers = implode(', ', array_keys(self::$supported_ciphers));
throw new RuntimeException("Unsupported cipher or incorrect key length. Supported ciphers are: {$ciphers}.");
}
$this->key = $key;
$this->cipher = $cipher;
}
/**
* Ensure the key length matches the expected length for the cipher.
*
* @param string $key
* @param string $cipher
* @return string
*/
private function ensure_key_length(string $key, string $cipher): string
{
$expected_length = self::$supported_ciphers[strtolower($cipher)]['size'];
if (mb_strlen($key, '8bit') !== $expected_length) {
// Truncate or hash the key to the required length
$key = substr(hash('sha256', $key), 0, $expected_length);
}
return $key;
}
/**
* Determine if the given key and cipher combination is valid.
*
* @param string $key
* @param string $cipher
* @return bool
*/
public static function supported($key, $cipher)
{
if (! isset(self::$supported_ciphers[strtolower($cipher)])) {
return false;
}
return mb_strlen($key, '8bit') === self::$supported_ciphers[strtolower($cipher)]['size'];
}
/**
* Create a new encryption key for the given cipher.
*
* @param string $cipher
* @return string
*/
public static function generate_key($cipher)
{
return random_bytes(self::$supported_ciphers[strtolower($cipher)]['size'] ?? 32);
}
/**
* {@inheritDoc}
*/
public function encrypt(string $value, $serialize = true): string
{
if (! extension_loaded('openssl')) {
throw new Encrypt_Exception('The OpenSSL extension is not loaded.');
}
$iv = random_bytes(openssl_cipher_iv_length(strtolower($this->cipher)));
$value = \openssl_encrypt($serialize ? serialize($value) : $value, strtolower($this->cipher), $this->key, 0, $iv, $tag,);
if ($value === false) {
throw new Encrypt_Exception('Could not encrypt the data.');
}
$iv = base64_encode($iv);
$tag = base64_encode($tag ?? '');
$mac = self::$supported_ciphers[strtolower($this->cipher)]['aead']
? '' // For AEAD-algorithms, the tag / MAC is returned by openssl_encrypt...
: $this->hash($iv, $value, $this->key);
$json = json_encode(compact('iv', 'value', 'mac', 'tag'), JSON_UNESCAPED_SLASHES);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new RuntimeException('Could not encrypt the data.');
}
return base64_encode($json);
}
/**
* {@inheritDoc}
*/
public function encrypt_string(string $value): string
{
return $this->encrypt($value, false);
}
/**
* {@inheritDoc}
*/
public function decrypt(string $payload, bool $unserialize = true): string
{
$payload = $this->get_json_payload($payload);
$iv = base64_decode($payload['iv']);
$this->ensure_tag_is_valid($tag = empty($payload['tag']) ? null : base64_decode($payload['tag']));
$found_valid_mac = false;
// Here we will decrypt the value. If we are able to successfully decrypt it
// we will then unserialize it and return it out to the caller. If we are
// unable to decrypt this value we will throw out an exception message.
foreach ($this->get_all_keys() as $key) {
if (
$this->should_validate_mac() &&
! ($found_valid_mac = $found_valid_mac || $this->valid_mac_for_key($payload, $key))
) {
continue;
}
$decrypted = \openssl_decrypt($payload['value'], strtolower($this->cipher), $key, 0, $iv, $tag ?? '');
if ($decrypted !== false) {
break;
}
}
if ($this->should_validate_mac() && ! $found_valid_mac) {
throw new Decrypt_Exception('The MAC is invalid.');
}
if (($decrypted ?? false) === false) {
throw new Decrypt_Exception('Could not decrypt the data.');
}
return $unserialize ? unserialize($decrypted) : $decrypted;
}
/**
* {@inheritDoc}
*/
public function decrypt_string(string $payload): string
{
return $this->decrypt($payload, false);
}
/**
* Create a MAC for the given value.
*
* @param string $iv
* @param mixed $value
* @param string $key
* @return string
*/
protected function hash(string $iv, mixed $value, string $key)
{
return hash_hmac('sha256', $iv . $value, $key);
}
/**
* Get the JSON array from the given payload.
*
* @param string $payload
* @return array
*
* @throws Decrypt_Exception
*/
protected function get_json_payload(string $payload): array
{
if (! is_string($payload)) {
throw new Decrypt_Exception('The payload is invalid.');
}
$payload = json_decode(base64_decode($payload), true);
// If the payload is not valid JSON or does not have the proper keys set we will
// assume it is invalid and bail out of the routine since we will not be able
// to decrypt the given value. We'll also check the MAC for this encryption.
if (! $this->valid_payload($payload)) {
throw new Decrypt_Exception('The payload is invalid.');
}
return $payload;
}
/**
* Verify that the encryption payload is valid.
*
* @param mixed $payload
* @return bool
*/
protected function valid_payload(mixed $payload): bool
{
if (! is_array($payload)) {
return false;
}
foreach (['iv', 'value', 'mac'] as $item) {
if (! isset($payload[$item]) || ! is_string($payload[$item])) {
return false;
}
}
if (isset($payload['tag']) && ! is_string($payload['tag'])) {
return false;
}
return strlen(base64_decode($payload['iv'], true)) === openssl_cipher_iv_length(strtolower($this->cipher));
}
/**
* Determine if the MAC for the given payload is valid for the primary key.
*
* @param array $payload
* @return bool
*/
protected function valid_mac(array $payload): bool
{
return $this->valid_mac_for_key($payload, $this->key);
}
/**
* Determine if the MAC is valid for the given payload and key.
*
* @param array $payload
* @param string $key
* @return bool
*/
protected function valid_mac_for_key(array $payload, string $key): bool
{
return hash_equals($this->hash($payload['iv'], $payload['value'], $key), $payload['mac'],);
}
/**
* Ensure the given tag is a valid tag given the selected cipher.
*
* @param string $tag
* @return void
*/
protected function ensure_tag_is_valid($tag)
{
if (self::$supported_ciphers[strtolower($this->cipher)]['aead'] && strlen($tag) !== 16) {
throw new Decrypt_Exception('Could not decrypt the data.');
}
if (! self::$supported_ciphers[strtolower($this->cipher)]['aead'] && is_string($tag)) {
throw new Decrypt_Exception('Unable to use tag because the cipher algorithm does not support AEAD.');
}
}
/**
* Determine if we should validate the MAC while decrypting.
*
* @return bool
*/
protected function should_validate_mac()
{
return ! self::$supported_ciphers[strtolower($this->cipher)]['aead'];
}
/**
* {@inheritDoc}
*/
public function get_key(): string
{
return $this->key;
}
/**
* {@inheritDoc}
*/
public function get_all_keys(): array
{
return [$this->key, ...$this->previous_keys];
}
/**
* {@inheritDoc}
*/
public function get_previous_keys(): array
{
return $this->previous_keys;
}
/**
* Set the previous / legacy encryption keys that should be utilized if decryption fails.
*
* @param array $keys
* @return $this
*/
public function previous_keys(array $keys)
{
foreach ($keys as $key) {
if (! static::supported($key, $this->cipher)) {
$ciphers = implode(', ', array_keys(self::$supported_ciphers));
throw new RuntimeException("Unsupported cipher or incorrect key length. Supported ciphers are: {$ciphers}.");
}
}
$this->previous_keys = $keys;
return $this;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Webshr\Core\Utility\Exceptions;
use RuntimeException;
class Decrypt_Exception extends RuntimeException
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Webshr\Core\Utility\Exceptions;
use RuntimeException;
class Encrypt_Exception extends RuntimeException
{
//
}

33
src/Utility/Hash.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
/**
* @package Webshr\Core
* @since 1.0.0
* @author Webshore, H. Liebel
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://webshore.eu/
*
*/
namespace Webshr\Core\Utility;
use Webshr\Core\Utility\Contracts\Hasher as Haser_Interface;
class Hash implements Haser_Interface
{
/**
* {@inheritDoc}
*/
public function make($password)
{
return password_hash($password, PASSWORD_BCRYPT);
}
/**
* {@inheritDoc}
*/
public function check($password, $hash)
{
return password_verify($password, $hash);
}
}

156
src/globals.php Normal file
View File

@@ -0,0 +1,156 @@
<?php
use Webshr\Core\Application;
use Webshr\Core\Assets\Bundle;
use Webshr\Core\Assets\Asset\Asset;
if (! function_exists('app')) {
/**
* Get the available container instance.
*
* @param string|null $abstract
* @param array $parameters
* @return \Webshr\Core\Contracts\Application|\Webshr\Core\Application|mixed
*/
function app($abstract = null, array $parameters = [])
{
if (is_null($abstract)) {
return Application::get_instance();
}
return Application::get_instance()->make($abstract, $parameters);
}
}
if (! function_exists('lang_path')) {
/**
* Get the path to the language folder.
*
* @param string $path
* @return string
*/
function lang_path(string $path = '')
{
return app()->lang_path($path);
}
}
if (! function_exists('app_public_path')) {
/**
* Get the path to the public folder.
*
* @param string $path
* @return string
*/
function app_public_path(string $path = '')
{
return app()->public_path($path);
}
}
if (! function_exists('app_asset_path')) {
/**
* Generate an asset path for the application.
*
* @param string $path
* @param bool|null $secure
* @return string
*/
function app_asset_path(string $path, bool|null $secure = null)
{
return app('url')->asset($path, $secure);
}
}
if (!function_exists('asset')) {
/**
* Get an asset instance and return the URL or other methods.
*
* @param string $path
* @param string|null $manifest
* @return Asset
*/
function asset(string $path, ?string $manifest = null): Asset
{
// Use the core `asset()` from the helpers file
return \Webshr\Core\asset($path, $manifest);
}
}
if (!function_exists('bundle')) {
/**
* Get a bundle instance and return the methods.
*
* @param string $path
* @param string|null $manifest
* @return Bundle
*/
function bundle(string $path, ?string $manifest = null): Bundle
{
// Use the core `bundle()` from the helpers file
return \Webshr\Core\bundle($path, $manifest);
}
}
if (! function_exists('module')) {
/**
* Return the given module
*
* @param string $path
* @param bool|null $secure
* @return string
*/
function module($name)
{
return app('modules')->module($name);
}
}
/** Deprecated **/
if (! function_exists('asset_content')) {
/**
* Get the asset file content.
*
* @param string $path
* @example '/icons/..svg'
* @return void
*/
function asset_content(string $path): void
{
_doing_it_wrong('asset_content', 'This method has been deprecated in favor of asset()->contents', '0.2.0');
echo asset($path)->contents();
}
}
if (! function_exists('asset_path')) {
/**
* Get the asset file path.
*
* @param string $path
* @example '/images/..img'
* @return string
*/
function asset_path(string $path): string
{
_doing_it_wrong('asset_path', 'This method has been deprecated in favor of asset()->path', '0.2.0');
return asset($path)->path();
}
}
if (! function_exists('asset_uri')) {
/**
* Get the asset file uri.
*
* @param string $path
* @example '/images/..img'
* @return string
*/
function asset_uri(string $path): string
{
_doing_it_wrong('asset_uri', 'This method has been deprecated in favor of asset()->uri', '0.2.0');
return asset($path)->uri();
}
}

310
src/helpers.php Normal file
View File

@@ -0,0 +1,310 @@
<?php
namespace Webshr\Core;
use Webshr\Core\Application;
use Webshr\Core\Config\Environment;
use Webshr\Core\Assets\Bundle;
use Webshr\Core\Assets\Contracts\Asset;
use Webshr\Core\Assets\Contracts\Asset_Meta;
use Webshr\Core\Filesystem\Loader;
/**
* Instantiate the bootloader.
*
* @param Application|null $app
*
* @return Bootloader
*/
function bootloader(?Application $app = null): Bootloader
{
$bootloader = Bootloader::get_instance($app);
/**
* @deprecated
*/
\Webshr\Core\add_actions(['after_setup_theme', 'rest_api_init'], function () use ($bootloader) {
$app = $bootloader->get_application();
if ($app->has_been_bootstrapped()) {
return;
}
\Webshr\Core\wp_die(
'Webshr Core failed to boot. Run <code>\\Webshr\\Core\\bootloader()->boot()</code>.<br><br>If you\'re using a Webshore Theme, you need to <a href="https://git.webshore.io/Webshr/Core/functions.php#L32">update <strong>webshr/functions.php:32</strong></a>',
'<code>\\Webshr\\Core\\bootloader()</code> was called incorrectly.',
'Webshr Core &rsaquo; Boot Error',
'Check out the <a href="https://git.webshore.io/Webshr/Core">release notes</a> for more information.',
);
}, 6);
return $bootloader;
}
/**
* Get the available theme instance.
*
* @param string|null $abstract
* @param array $parameters
* @return \Webshr\Core\Application|mixed
*/
function app($abstract = null, array $parameters = [])
{
if (is_null($abstract)) {
return Application::get_instance();
}
return Application::get_instance()->make($abstract, $parameters);
}
/**
* Require files from a directory
*
* @param string|null $path
* @return void
*/
function require_files(string $dir = null): void
{
$loader = new Loader();
$loader->load($dir);
}
/**
* Get asset from manifest
*/
function asset(string $asset, ?string $manifest = null): Asset
{
if (! $manifest) {
return \app('assets.manifest')->asset($asset);
}
return \app('assets')->manifest($manifest)->asset($asset);
}
/**
* Get bundle from manifest
*/
function bundle(string $bundle, ?string $manifest = null): Bundle
{
if (! $manifest) {
return \app('assets.manifest')->bundle($bundle);
}
return \app('assets')->manifest($manifest)->bundle($bundle);
}
/**
* Get asset meta from manifest
*/
function meta(string $asset, ?string $manifest = null): Asset_Meta
{
if (! $manifest) {
return \app('assets.manifest')->meta($asset);
} else {
return \app('assets')->manifest($manifest)->meta($asset);
}
}
/**
* Get module from manager
*/
function module(string $module): Module
{
return \app()->module_manager->module($module);
}
/**
* Encrypts the given data.
*/
function encrypt(string $data): string
{
return \app('encryption')->encrypt($data);
}
/**
* Encrypts the given string without serialization.
*/
function encrypt_string(string $data): string
{
return \app('encryption')->encrypt_string($data);
}
/**
* Decrypts the given data.
*/
function decrypt(string $payload): string
{
return \app('encryption')->decrypt($payload);
}
/**
* Decrypts the given string without unserialization.
*/
function decrypt_string(string $payload): string
{
return \app('encryption')->decrypt_string($payload);
}
/**
* Hashes the given data.
*/
function hash(string $data): string
{
return \app('hash')->make($data);
}
/**
* Checks the given data against the hash.
*/
function check(string $data, string $hash): bool
{
return \app('hash')->check($data, $hash);
}
/**
* Gets the value of an environment variable.
*
* @param string $key
* @param mixed $default
* @return mixed
*
* @copyright Taylor Otwell
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://github.com/laravel/framework/blob/v5.6.25/src/Illuminate/Support/helpers.php#L597-L632 Original
*
* @deprecated use Webshr\Core\Config\Environment::get() instead
*/
function env($key, $default = null)
{
if (class_exists(Environment::class)) {
return Environment::get($key, $default);
}
$value = getenv($key);
if ($value === false) {
return value($default);
}
switch (strtolower($value)) {
case 'true':
case '(true)':
return true;
case 'false':
case '(false)':
return false;
case 'empty':
case '(empty)':
return '';
case 'null':
case '(null)':
return;
}
if (($valueLength = strlen($value)) > 1 && $value[0] === '"' && $value[($valueLength - 1)] === '"') {
return substr($value, 1, -1);
}
return $value;
}
/**
* Return the default value of the given value.
*
* @param mixed $value
* @return mixed
*
* @copyright Taylor Otwell
* @license https://opensource.org/licenses/GPL-3.0-or-later GPL-3.0-or-later
* @link https://github.com/laravel/framework/blob/v5.6.25/src/Illuminate/Support/helpers.php#L1143-L1152 Original
*/
function value($value)
{
return $value instanceof \Closure ? $value() : $value;
}
/**
* Bind single callback to multiple filters
*
* @param iterable $filters List of filters
* @param callable $callback
* @param integer $priority
* @param integer $args
* @return void
*/
function add_filters(iterable $filters, $callback, $priority = 10, $args = 2)
{
$count = count($filters);
array_map(
'\add_filter',
(array) $filters,
array_fill(0, $count, $callback),
array_fill(0, $count, $priority),
array_fill(0, $count, $args),
);
}
/**
* Remove single callback from multiple filters
*
* @param iterable $filters List of filters
* @param callable $callback
* @param integer $priority
* @return void
*/
function remove_filters(iterable $filters, $callback, $priority = 10)
{
$count = count($filters);
array_map(
'\remove_filter',
(array) $filters,
array_fill(0, $count, $callback),
array_fill(0, $count, $priority),
);
}
/**
* Alias of add_filters
*
* @see add_filters
* @param iterable $actions List of actions
* @param callable $callback
* @param integer $priority
* @param integer $args
* @return void
*/
function add_actions(iterable $actions, $callback, $priority = 10, $args = 2)
{
add_filters($actions, $callback, $priority, $args);
}
/**
* Alias of remove_filters
*
* @see remove_filters
* @param iterable $actions List of actions
* @param callable $callback
* @param integer $priority
* @return void
*/
function remove_actions(iterable $actions, $callback, $priority = 10)
{
remove_filters($actions, $callback, $priority);
}
/**
* Helper function for prettying up errors
*
* @param string $message
* @param string $subtitle
* @param string $title
* @param string $footer
*/
function wp_die($message, $subtitle = '', $title = '', $footer = '')
{
$title = $title ?: __('WordPress &rsaquo; Error', 'webshr');
$footer = $footer ?: '<a href="https://webshore.eu/">Webshore</a>';
$message = "<h1>{$title}<br><small>{$subtitle}</small></h1><p>{$message}</p><p>{$footer}</p>";
\wp_die($message, $title);
}