v1.1.1
This commit is contained in:
parent
92d56fe84d
commit
7dd3eeaa8a
97 changed files with 4912 additions and 1581 deletions
1
.phpunit.result.cache
Normal file
1
.phpunit.result.cache
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":1,"defects":[],"times":{"Test_Plugin_Headers::plugin_file_exists":0.002,"Test_Plugin_Headers::plugin_name_header_is_present":0.001,"Test_Plugin_Headers::version_header_is_present_and_valid":0,"Test_Plugin_Headers::requires_at_least_header_is_present":0,"Test_Plugin_Headers::requires_php_header_is_present":0,"Test_Plugin_Headers::text_domain_matches_plugin_slug":0,"Test_Plugin_Headers::license_header_is_gpl3":0,"Test_Plugin_Headers::author_header_is_present":0,"Test_Plugin_Headers::readme_file_exists":0,"Test_Plugin_Headers::readme_has_stable_tag":0,"Test_Plugin_Headers::readme_has_requires_at_least":0,"Test_Plugin_Headers::readme_has_requires_php":0,"Test_Plugin_Headers::readme_has_license":0,"Test_Plugin_Headers::stable_tag_matches_plugin_version":0,"Test_Plugin_Headers::requires_at_least_is_consistent":0,"Test_Plugin_Headers::requires_php_is_consistent":0,"Test_Plugin_Headers::license_is_consistent":0}}
|
||||
59
CHANGELOG.md
Normal file
59
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to Documentation Markdown are documented in this file.
|
||||
|
||||
For the WordPress.org–formatted changelog, see [`changelog.txt`](changelog.txt).
|
||||
|
||||
## [1.1.1] — 2026-06-08
|
||||
|
||||
### Security
|
||||
- CommonMark: disabled raw HTML passthrough (`html_input: strip`, `allow_unsafe_links: false`) — prevents stored XSS if an upstream repository is compromised
|
||||
- Plugin settings option now stored with `autoload = false` — encrypted GitHub token no longer loaded on every WordPress request
|
||||
- Replaced `serialize()` with `wp_json_encode()` in updater HMAC cache signature (eliminates object-injection risk surface)
|
||||
- Added strict base64 length validation in `robotstxt_docmd_decrypt_token()` before IV extraction
|
||||
- Added `base64_decode()` strict return-value check in `robotstxt_docmd_get_file_content()`
|
||||
|
||||
### Fixed
|
||||
- Admin notices now display results for all operations (mapping created/updated/deleted, sync complete, error messages); previously only "Settings saved" appeared
|
||||
- `wp_update_post()` return value now checked during sync — silent failures previously reported as success
|
||||
- Wrong textdomain `'robotstxt-smtp'` in updater class corrected to `'robotstxt-documentation-markdown'`
|
||||
|
||||
### Changed
|
||||
- `robotstxt-updater.php` renamed to `class-robotstxt-updater.php` (WordPress file-naming convention)
|
||||
- `Requires PHP` header corrected to `8.0` (real minimum confirmed by PHPCompatibility scan — union types, `str_starts_with()`, `str_contains()`, `mixed` type are the binding constraints; no 8.1/8.2-specific features used)
|
||||
|
||||
### Developer / Infrastructure
|
||||
- Added `composer.json` with full `require-dev` tooling (PHPCS, PHPStan 2.x, PHPUnit 9.6, PHPCompatibility 10.0.0-alpha2, wp-compat, yoast/phpunit-polyfills)
|
||||
- Added `phpstan.neon` (level 9), `.phpcs.xml` (WordPress standards), `bin/deploy.sh`, `phpunit.xml.dist`
|
||||
- 17 PHPUnit plugin header tests added (`tests/PluginHeadersTest.php`)
|
||||
- Added `docs/known-issues.md` and `docs/db-migrations.md`
|
||||
- `class-robotstxt-updater.php`: PHPCS 0 errors, PHPStan level 9 0 errors
|
||||
- PHPCompatibility updated to 10.0.0-alpha2 (PHP 8.x feature detection)
|
||||
|
||||
## [1.1.0] — 2026-03-28
|
||||
|
||||
### Security
|
||||
- Patched CVE-2026-33347 and CVE-2026-30838 (league/commonmark updated to 2.8.2)
|
||||
|
||||
### Changed
|
||||
- Access level changed from `manage_options` (administrator) to `edit_pages` (editor)
|
||||
|
||||
### Fixed
|
||||
- PHPStan level 9 compliance: zero errors across all plugin files
|
||||
- `target_order` (menu_order) field now saved and applied end-to-end
|
||||
- Type-safety improvements for all WordPress API returns
|
||||
- Token decryption `false` return handled correctly
|
||||
- Uninstall data cleanup narrows mixed option return before array access
|
||||
|
||||
## [1.0.0] — 2026-01-26
|
||||
|
||||
### Added
|
||||
- Initial release: GitHub → WordPress Markdown sync
|
||||
- Encrypted GitHub token storage (AES-256-CBC)
|
||||
- Custom Post Type `robotstxt_map` for mapping management
|
||||
- Automatic cron sync (hourly, twice daily, daily)
|
||||
- Manual on-demand sync via admin interface
|
||||
- Markdown to HTML via CommonMark (league/commonmark)
|
||||
- Multi-repository support
|
||||
- Debug tools (visible when `WP_DEBUG` enabled)
|
||||
- Full i18n support (es_ES bundled)
|
||||
|
|
@ -1,5 +1,58 @@
|
|||
== Changelog ==
|
||||
|
||||
= 1.1.1 =
|
||||
|
||||
_Release date: 2026-06-08_
|
||||
|
||||
**Highlights**
|
||||
|
||||
* Security hardening: CommonMark raw HTML passthrough disabled, encrypted token no longer autoloaded, HMAC now uses `wp_json_encode()` instead of `serialize()`
|
||||
* Bug fix: admin notices now visible for all operations (sync, create, update, delete, errors)
|
||||
* Infrastructure: full dev tooling added (PHPCS, PHPStan, PHPUnit, deploy script)
|
||||
* Real PHP minimum corrected to 8.0 (confirmed by PHPCompatibility 10.x scan)
|
||||
|
||||
**Security**
|
||||
|
||||
* CommonMark: disabled raw HTML passthrough (`html_input: strip`, `allow_unsafe_links: false`) — prevents stored XSS if an upstream repository is compromised
|
||||
* Settings option now stored with `autoload = false` — encrypted GitHub token no longer loaded on every WordPress request (defense in depth)
|
||||
* Replaced `serialize()` with `wp_json_encode()` in updater HMAC cache (eliminates PHPCS object-injection warning)
|
||||
* Added strict base64 length validation before IV extraction in `robotstxt_docmd_decrypt_token()`
|
||||
* Added `base64_decode()` strict return-value check in `robotstxt_docmd_get_file_content()`
|
||||
|
||||
**Fixed**
|
||||
|
||||
* Admin notices now display results for all operations: mapping created, mapping updated, mapping deleted, sync complete, and error messages — previously only "Settings saved" was shown
|
||||
* `wp_update_post()` return value now checked during sync — silent post-update failures were previously reported as success
|
||||
* Wrong textdomain `'robotstxt-smtp'` in updater class corrected to `'robotstxt-documentation-markdown'`
|
||||
|
||||
**Changed**
|
||||
|
||||
* `robotstxt-updater.php` renamed to `class-robotstxt-updater.php` (WordPress file-naming convention)
|
||||
* Minimum PHP version header corrected from 8.2 to 8.0 (real minimum confirmed by PHPCompatibility 10.0.0-alpha2 scan — union types, `str_starts_with()`, `str_contains()`, and `mixed` type are the binding constraints)
|
||||
|
||||
**Developer**
|
||||
|
||||
* Added `composer.json` with full require-dev tooling (PHPCS, PHPStan 2.x, PHPUnit 9.6, PHPCompatibility 10.0.0-alpha2, wp-compat, yoast/phpunit-polyfills)
|
||||
* Added `phpstan.neon` (level 9), `.phpcs.xml` (WordPress-Core/Docs/Extra), `bin/deploy.sh`, `phpunit.xml.dist`
|
||||
* PHPUnit: 17 plugin header tests added (`tests/PluginHeadersTest.php`)
|
||||
* Added `docs/known-issues.md` and `docs/db-migrations.md`
|
||||
* `class-robotstxt-updater.php`: PHPCS 0 errors, PHPStan level 9 0 errors (full compliance)
|
||||
|
||||
**Compatibility**
|
||||
|
||||
* WordPress: 6.7 - 7.1
|
||||
* PHP: 8.0 - 8.5
|
||||
* MariaDB: 11.4 or newer
|
||||
|
||||
**Tests**
|
||||
|
||||
* PHP Coding Standards: PHPCS 3.x with WordPress-Core, WordPress-Docs, WordPress-Extra — 0 errors, 0 warnings
|
||||
* WordPress Coding Standards: WPCS 3.3.0
|
||||
* PHPStan: level 9, 0 errors (szepeviktor/phpstan-wordpress 2.0.3)
|
||||
* PHPCompatibility: 10.0.0-alpha2 — PHP 8.0-8.5 validated
|
||||
* PHPUnit: 17/17 tests pass (plugin header tests)
|
||||
* Manual testing: WordPress 7.0, 7.1
|
||||
|
||||
= 1.1.0 =
|
||||
|
||||
_Release date: 2026-03-28_
|
||||
|
|
|
|||
465
class-robotstxt-updater.php
Normal file
465
class-robotstxt-updater.php
Normal file
|
|
@ -0,0 +1,465 @@
|
|||
<?php
|
||||
/**
|
||||
* Generic JSON-based updater for ROBOTSTXT plugins.
|
||||
*
|
||||
* This file is designed to be copied to any ROBOTSTXT plugin.
|
||||
* It auto-configures itself by reading the plugin headers.
|
||||
*
|
||||
* @package ROBOTSTXT
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'Robotstxt_Updater' ) ) {
|
||||
/**
|
||||
* Class Robotstxt_Updater
|
||||
*
|
||||
* Generic updater that works with any plugin.
|
||||
* Reads plugin headers and constructs update URL automatically.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Robotstxt_Updater {
|
||||
|
||||
/**
|
||||
* Plugin file path.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_file_path;
|
||||
|
||||
/**
|
||||
* Plugin basename (e.g., 'my-plugin/my-plugin.php').
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_basename;
|
||||
|
||||
/**
|
||||
* Plugin slug (directory name).
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private string $plugin_slug;
|
||||
|
||||
/**
|
||||
* Remote JSON URL.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private string $json_url;
|
||||
|
||||
/**
|
||||
* Cache key.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var string
|
||||
*/
|
||||
private string $cache_key;
|
||||
|
||||
/**
|
||||
* Plugin headers.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private array $plugin_data;
|
||||
|
||||
/**
|
||||
* Initialize the updater.
|
||||
*
|
||||
* Usage in your main plugin file:
|
||||
* require_once __DIR__ . '/class-robotstxt-updater.php';
|
||||
* Robotstxt_Updater::init( __FILE__ );
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $plugin_file_path Absolute path to the main plugin file.
|
||||
* @return void
|
||||
*/
|
||||
public static function init( string $plugin_file_path ): void {
|
||||
$instance = new self( $plugin_file_path );
|
||||
$instance->register();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param string $plugin_file_path Absolute path to the main plugin file.
|
||||
*/
|
||||
private function __construct( string $plugin_file_path ) {
|
||||
$this->plugin_file_path = $plugin_file_path;
|
||||
$this->plugin_basename = plugin_basename( $plugin_file_path );
|
||||
$this->plugin_slug = dirname( $this->plugin_basename );
|
||||
$this->plugin_data = $this->get_plugin_data();
|
||||
$this->json_url = $this->build_json_url();
|
||||
$this->cache_key = 'robotstxt_updater_' . md5( $this->plugin_basename );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register WordPress hooks.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register(): void {
|
||||
add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'inject_update_info' ) );
|
||||
add_filter( 'plugins_api', array( $this, 'provide_plugin_details' ), 10, 3 );
|
||||
add_action( 'admin_init', array( $this, 'handle_cache_clear' ) );
|
||||
add_action( 'robotstxt_updater_clear_cache', array( $this, 'clear_cache' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin headers.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array<string, mixed> Plugin data.
|
||||
*/
|
||||
private function get_plugin_data(): array {
|
||||
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
return get_plugin_data( $this->plugin_file_path, false, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Build JSON URL from plugin headers.
|
||||
*
|
||||
* Tries to use "Gitea Plugin URI" header to construct the URL.
|
||||
* Falls back to Plugin URI if Gitea URI is not available.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return string JSON URL.
|
||||
*/
|
||||
private function build_json_url(): string {
|
||||
$gitea_uri = isset( $this->plugin_data['Gitea Plugin URI'] ) && is_string( $this->plugin_data['Gitea Plugin URI'] )
|
||||
? $this->plugin_data['Gitea Plugin URI']
|
||||
: '';
|
||||
|
||||
if ( '' !== $gitea_uri ) {
|
||||
if ( str_starts_with( $gitea_uri, 'http' ) ) {
|
||||
return rtrim( $gitea_uri, '/' ) . '/raw/branch/main/update.json';
|
||||
}
|
||||
|
||||
if ( preg_match( '#^[^/]+/[^/]+$#', $gitea_uri ) ) {
|
||||
return 'https://git.robotstxt.es/' . $gitea_uri . '/raw/branch/main/update.json';
|
||||
}
|
||||
}
|
||||
|
||||
$plugin_uri = isset( $this->plugin_data['PluginURI'] ) && is_string( $this->plugin_data['PluginURI'] )
|
||||
? $this->plugin_data['PluginURI']
|
||||
: '';
|
||||
|
||||
if ( '' !== $plugin_uri && str_contains( $plugin_uri, 'git.robotstxt.es' ) ) {
|
||||
return rtrim( $plugin_uri, '/' ) . '/raw/branch/main/update.json';
|
||||
}
|
||||
|
||||
return 'https://git.robotstxt.es/ROBOTSTXT/' . $this->plugin_slug . '/raw/branch/main/update.json';
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject update info into WP's plugin update transient.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param mixed $transient The update_plugins transient value.
|
||||
* @return mixed The modified transient.
|
||||
*/
|
||||
public function inject_update_info( $transient ) {
|
||||
if ( ! ( $transient instanceof stdClass ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( ! isset( $transient->checked ) || ! is_array( $transient->checked ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( ! isset( $transient->checked[ $this->plugin_basename ] )
|
||||
|| ! is_string( $transient->checked[ $this->plugin_basename ] )
|
||||
) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
$current_version = $transient->checked[ $this->plugin_basename ];
|
||||
$remote = $this->get_remote_data();
|
||||
|
||||
$remote_version = isset( $remote['version'] ) && is_string( $remote['version'] ) ? $remote['version'] : '';
|
||||
$download_url = isset( $remote['download_url'] ) && is_string( $remote['download_url'] ) ? $remote['download_url'] : '';
|
||||
|
||||
if ( '' === $remote_version || '' === $download_url ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( ! $this->is_compatible( $remote ) ) {
|
||||
return $transient;
|
||||
}
|
||||
|
||||
if ( version_compare( $remote_version, $current_version, '>' ) ) {
|
||||
$plugin_uri = isset( $this->plugin_data['PluginURI'] ) && is_string( $this->plugin_data['PluginURI'] )
|
||||
? $this->plugin_data['PluginURI']
|
||||
: '';
|
||||
|
||||
$update = (object) array(
|
||||
'slug' => isset( $remote['slug'] ) && is_string( $remote['slug'] ) ? $remote['slug'] : $this->plugin_slug,
|
||||
'plugin' => $this->plugin_basename,
|
||||
'new_version' => $remote_version,
|
||||
'url' => isset( $remote['homepage'] ) && is_string( $remote['homepage'] ) ? $remote['homepage'] : $plugin_uri,
|
||||
'package' => $download_url,
|
||||
'tested' => isset( $remote['tested'] ) && is_string( $remote['tested'] ) ? $remote['tested'] : '',
|
||||
'requires' => isset( $remote['requires'] ) && is_string( $remote['requires'] ) ? $remote['requires'] : '',
|
||||
'requires_php' => isset( $remote['requires_php'] ) && is_string( $remote['requires_php'] ) ? $remote['requires_php'] : '',
|
||||
);
|
||||
|
||||
if ( ! isset( $transient->response ) || ! is_array( $transient->response ) ) {
|
||||
$transient->response = array();
|
||||
}
|
||||
|
||||
$transient->response[ $this->plugin_basename ] = $update;
|
||||
}
|
||||
|
||||
return $transient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide "View details" modal content.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param false|object $result The result object or false if no result yet.
|
||||
* @param string $action The type of information being requested.
|
||||
* @param object $args Plugin API arguments.
|
||||
* @return false|object The plugin information object or false.
|
||||
*/
|
||||
public function provide_plugin_details( $result, string $action, object $args ) {
|
||||
if ( 'plugin_information' !== $action ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
if ( ! isset( $args->slug ) || ! is_string( $args->slug ) || $args->slug !== $this->plugin_slug ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$remote = $this->get_remote_data();
|
||||
$remote_version = isset( $remote['version'] ) && is_string( $remote['version'] ) ? $remote['version'] : '';
|
||||
|
||||
if ( '' === $remote_version ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$plugin_name = isset( $this->plugin_data['Name'] ) && is_string( $this->plugin_data['Name'] )
|
||||
? $this->plugin_data['Name']
|
||||
: $this->plugin_slug;
|
||||
$plugin_uri = isset( $this->plugin_data['PluginURI'] ) && is_string( $this->plugin_data['PluginURI'] )
|
||||
? $this->plugin_data['PluginURI']
|
||||
: '';
|
||||
$description = isset( $this->plugin_data['Description'] ) && is_string( $this->plugin_data['Description'] )
|
||||
? $this->plugin_data['Description']
|
||||
: '';
|
||||
$author = isset( $this->plugin_data['Author'] ) && is_string( $this->plugin_data['Author'] )
|
||||
? $this->plugin_data['Author']
|
||||
: '';
|
||||
|
||||
return (object) array(
|
||||
'name' => isset( $remote['name'] ) && is_string( $remote['name'] ) ? $remote['name'] : $plugin_name,
|
||||
'slug' => isset( $remote['slug'] ) && is_string( $remote['slug'] ) ? $remote['slug'] : $this->plugin_slug,
|
||||
'version' => $remote_version,
|
||||
'author' => isset( $remote['author'] ) && is_string( $remote['author'] ) ? $remote['author'] : $author,
|
||||
'homepage' => isset( $remote['homepage'] ) && is_string( $remote['homepage'] ) ? $remote['homepage'] : $plugin_uri,
|
||||
'requires' => isset( $remote['requires'] ) && is_string( $remote['requires'] ) ? $remote['requires'] : '',
|
||||
'tested' => isset( $remote['tested'] ) && is_string( $remote['tested'] ) ? $remote['tested'] : '',
|
||||
'requires_php' => isset( $remote['requires_php'] ) && is_string( $remote['requires_php'] ) ? $remote['requires_php'] : '',
|
||||
'sections' => array(
|
||||
'description' => isset( $remote['description'] ) && is_string( $remote['description'] ) ? $remote['description'] : $description,
|
||||
'changelog' => isset( $remote['changelog'] ) && is_string( $remote['changelog'] ) ? $remote['changelog'] : '',
|
||||
),
|
||||
'download_link' => isset( $remote['download_url'] ) && is_string( $remote['download_url'] ) ? $remote['download_url'] : '',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get remote data with caching and HMAC signature verification.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array<string, mixed> Remote data.
|
||||
*/
|
||||
private function get_remote_data(): array {
|
||||
$cached = get_site_transient( $this->cache_key );
|
||||
|
||||
if ( false !== $cached && defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
|
||||
if ( is_array( $cached )
|
||||
&& isset( $cached['signature'], $cached['data'] )
|
||||
&& is_string( $cached['signature'] )
|
||||
&& is_array( $cached['data'] )
|
||||
) {
|
||||
$expected_sig = hash_hmac(
|
||||
'sha256',
|
||||
$this->cache_key . wp_json_encode( $cached['data'] ),
|
||||
AUTH_SALT
|
||||
);
|
||||
|
||||
if ( hash_equals( $expected_sig, $cached['signature'] ) ) {
|
||||
return $this->normalize_array( $cached['data'] );
|
||||
}
|
||||
|
||||
delete_site_transient( $this->cache_key );
|
||||
$cached = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( false === $cached ) {
|
||||
$remote = $this->fetch_json();
|
||||
|
||||
if ( defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) {
|
||||
$payload = array(
|
||||
'data' => $remote,
|
||||
'timestamp' => time(),
|
||||
'signature' => hash_hmac(
|
||||
'sha256',
|
||||
$this->cache_key . wp_json_encode( $remote ),
|
||||
AUTH_SALT
|
||||
),
|
||||
);
|
||||
set_site_transient( $this->cache_key, $payload, 6 * HOUR_IN_SECONDS );
|
||||
} else {
|
||||
set_site_transient( $this->cache_key, $remote, 6 * HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $remote;
|
||||
}
|
||||
|
||||
return is_array( $cached ) ? $this->normalize_array( $cached ) : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON from remote URL.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return array<string, mixed> Decoded JSON data.
|
||||
*/
|
||||
private function fetch_json(): array {
|
||||
$response = wp_remote_get(
|
||||
$this->json_url,
|
||||
array(
|
||||
'timeout' => 10,
|
||||
'headers' => array(
|
||||
'Accept' => 'application/json',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$code = (int) wp_remote_retrieve_response_code( $response );
|
||||
if ( $code < 200 || $code >= 300 ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$data = json_decode( $body, true );
|
||||
|
||||
return is_array( $data ) ? $data : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check compatibility with current environment.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array<string, mixed> $remote Remote data.
|
||||
* @return bool True if compatible.
|
||||
*/
|
||||
private function is_compatible( array $remote ): bool {
|
||||
if ( ! empty( $remote['requires_php'] ) && is_string( $remote['requires_php'] ) ) {
|
||||
if ( version_compare( PHP_VERSION, $remote['requires_php'], '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $remote['requires'] ) && is_string( $remote['requires'] ) ) {
|
||||
if ( version_compare( get_bloginfo( 'version' ), $remote['requires'], '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle manual cache clear via URL parameter.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_cache_clear(): void {
|
||||
$clear_cache = filter_input( INPUT_GET, 'robotstxt_clear_update_cache', FILTER_UNSAFE_RAW );
|
||||
if ( null === $clear_cache ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_UNSAFE_RAW );
|
||||
$nonce = ( null !== $nonce_raw && is_string( $nonce_raw ) )
|
||||
? sanitize_text_field( wp_unslash( $nonce_raw ) )
|
||||
: '';
|
||||
|
||||
if ( ! wp_verify_nonce( $nonce, 'robotstxt_clear_update_cache' ) ) {
|
||||
wp_die( esc_html__( 'Security check failed.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'update_plugins' ) ) {
|
||||
wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
|
||||
$this->clear_cache();
|
||||
wp_safe_redirect( remove_query_arg( array( 'robotstxt_clear_update_cache', '_wpnonce' ) ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild an array guaranteeing string keys for PHPStan type safety.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @param array<mixed, mixed> $data Raw array from transient or API.
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function normalize_array( array $data ): array {
|
||||
$result = array();
|
||||
foreach ( $data as $k => $v ) {
|
||||
if ( is_string( $k ) ) {
|
||||
$result[ $k ] = $v;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear update cache.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clear_cache(): void {
|
||||
delete_site_transient( $this->cache_key );
|
||||
delete_site_transient( 'update_plugins' );
|
||||
}
|
||||
}
|
||||
}
|
||||
90
docs/audit-pre-deploy-1.1.1.md
Normal file
90
docs/audit-pre-deploy-1.1.1.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Pre-Deploy AI Security Audit — v1.1.1
|
||||
|
||||
**Date:** 2026-06-08
|
||||
**Plugin:** Documentation Markdown (by ROBOTSTXT)
|
||||
**Version:** 1.1.0 → 1.1.1
|
||||
**Auditor:** Claude Sonnet (Senior WordPress Plugin Security Auditor role)
|
||||
**Scope:** Full codebase (all 7 PHP plugin files)
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
**Overall status: CONDITIONAL PASS → PASS (after fixes applied)**
|
||||
|
||||
All [CRITICAL] findings were resolved before tagging. No SQL injection, no CSRF gaps, no direct object reference, no privilege escalation vectors found. Access is gated behind `edit_pages` throughout with both UI-level and execution-level checks.
|
||||
|
||||
**Security risk: Low** (after fixes)
|
||||
**Version recommendation: stable**
|
||||
|
||||
---
|
||||
|
||||
## Findings Resolved Before Release
|
||||
|
||||
### [CRITICAL → FIXED] Finding 011 — CommonMark raw HTML passthrough
|
||||
|
||||
**File:** `robotstxt-documentation-markdown-map.php:296`
|
||||
**Problem:** CommonMark's default config allows raw HTML blocks from Markdown. A compromised upstream GitHub repository could inject `<script>`, `<iframe>`, or event handlers into synced WordPress post content.
|
||||
**Fix applied:** `html_input => 'strip'`, `allow_unsafe_links => false` in `CommonMarkConverter` config.
|
||||
|
||||
### [WARNING → FIXED] Finding 002 — base64 decode without length validation
|
||||
|
||||
**File:** `robotstxt-documentation-markdown-functions.php:55`
|
||||
**Problem:** `base64_decode()` result not length-checked before `substr()` IV extraction. Tampered options could cause silent decryption failures or short-IV oracle scenarios.
|
||||
**Fix applied:** Added `false === $decoded || strlen($decoded) <= $iv_length` guard.
|
||||
|
||||
### [WARNING → FIXED] Finding 008 — Settings option autoloaded
|
||||
|
||||
**File:** `robotstxt-documentation-markdown.php:72, 1164`
|
||||
**Problem:** Encrypted GitHub token loaded with every WordPress request via autoload.
|
||||
**Fix applied:** `add_option(..., '', false)` and `update_option(..., false)`.
|
||||
|
||||
### [WARNING → FIXED] Finding 018 — `wp_update_post()` return unchecked
|
||||
|
||||
**File:** `robotstxt-documentation-markdown-map.php:324`
|
||||
**Problem:** Sync silently reported success even if post update failed.
|
||||
**Fix applied:** Return value checked; error returned on failure.
|
||||
|
||||
### [WARNING → FIXED] Finding 020 — `base64_decode()` unchecked in GitHub fetch
|
||||
|
||||
**File:** `robotstxt-documentation-markdown-github.php:289`
|
||||
**Problem:** Malformed GitHub API response (bad base64) would cause silent empty post.
|
||||
**Fix applied:** Added `false === $decoded` guard returning `WP_Error`.
|
||||
|
||||
### [BUG → FIXED] Admin notices not displayed
|
||||
|
||||
**File:** `robotstxt-documentation-markdown.php:908`
|
||||
**Problem:** Messages for mapping_created, mapping_updated, mapping_deleted, sync_complete, and error parameters were redirected to URL but never rendered.
|
||||
**Fix applied:** Complete message map added to `robotstxt_docmd_render_admin_notices()`.
|
||||
|
||||
---
|
||||
|
||||
## Findings Deferred to Future Release
|
||||
|
||||
| ID | Severity | Description | Target |
|
||||
|----|----------|-------------|--------|
|
||||
| 001 | WARNING | Token key derivation should use HKDF instead of raw `wp_salt()` — would require token migration | v1.2.0 |
|
||||
| 003 | WARNING | Debug action nonce/page checks split across two `if` blocks — refactoring risk | v1.2.0 |
|
||||
| 005 | INFO | `esc_js()` in `onclick` pattern — safe with fixed string but fragile for future edits | v1.2.0 |
|
||||
| 006 | WARNING | `get_posts(post_type=any, limit=100)` on Add Mapping page — performance on large sites | v1.2.0 |
|
||||
| 009 | INFO | GitHub API URL: branch/path not `rawurlencode()`d — no current attack vector, defensive hardening | v1.2.0 |
|
||||
| 010 | INFO | `target_post_type` not validated against registered post types at save | v1.2.0 |
|
||||
| 013 | INFO | Debug "cached vs fresh" indicator always shows cached after first fetch | v1.2.0 |
|
||||
| 014 | INFO | Discover page `refresh=1` lacks nonce — forces GitHub API call via CSRF | v1.2.0 |
|
||||
|
||||
Deferred findings documented in `docs/known-issues.md`.
|
||||
|
||||
---
|
||||
|
||||
## Positive Findings (No Action Required)
|
||||
|
||||
- All state-changing actions protected with nonces (`wp_nonce_field`, `wp_nonce_url`, `check_admin_referer`, `wp_verify_nonce`)
|
||||
- Capability checks (`current_user_can('edit_pages')`) applied in both UI rendering and execution logic for all admin pages and handlers
|
||||
- All `$_POST`/`$_GET` access goes through `robotstxt_docmd_input_string()` / `robotstxt_docmd_input_int()` type-narrowing helpers
|
||||
- All output escaping at render time (`esc_html`, `esc_attr`, `esc_url`, `esc_js`, `wp_kses_post`)
|
||||
- All redirects via `wp_safe_redirect()` to `admin_url()` destinations
|
||||
- No raw SQL; custom SQL (transient cleanup) uses `$wpdb->prepare()` with `$wpdb->esc_like()`
|
||||
- ABSPATH guard on every PHP file
|
||||
- No `eval()`, no suspicious `base64_decode()` (documented necessary use for GitHub API decoding), no shell functions
|
||||
- GitHub token encrypted at rest (AES-256-CBC, key from `wp_salt('auth')`)
|
||||
- `uninstall.php` uses `WP_UNINSTALL_PLUGIN` guard and respects user data-preservation preference
|
||||
9
docs/db-migrations.md
Normal file
9
docs/db-migrations.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Database Migrations
|
||||
|
||||
This plugin does **not** create custom database tables. All data is stored using WordPress core APIs:
|
||||
|
||||
- **Mappings:** Custom Post Type `robotstxt_map` with post meta (prefixed `_robotstxt_docmd_*`)
|
||||
- **Settings:** WordPress Options API (`robotstxt_docmd_settings`)
|
||||
- **Cache:** WordPress Transients API (`robotstxt_docmd_*`)
|
||||
|
||||
No `DB_VERSION` constant or migration routines are required.
|
||||
113
docs/deploy-checklist-1.1.1.md
Normal file
113
docs/deploy-checklist-1.1.1.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Pre-Deploy Checklist — Version 1.1.1
|
||||
|
||||
**Date:** 2026-06-08
|
||||
**Plugin:** Documentation Markdown (by ROBOTSTXT)
|
||||
**Version:** 1.1.1
|
||||
|
||||
---
|
||||
|
||||
## INPUT VALIDATION
|
||||
- [x] Empty field validation performed
|
||||
- [x] Length limits validated server-side
|
||||
- [x] Format patterns validated (regex, ctype_alnum, preg_match)
|
||||
- [x] Safelist validation for finite option sets
|
||||
- [x] Strict type checking (===, in_array with true)
|
||||
- [x] Validation BEFORE any action or processing
|
||||
- [x] robotstxt_docmd_input_string() / robotstxt_docmd_input_int() for all superglobal access
|
||||
|
||||
## INPUT SANITIZATION
|
||||
- [x] sanitize_text_field() for single-line text
|
||||
- [x] sanitize_key() for keys/identifiers
|
||||
- [x] sanitize_text_field() for github_url (functional; sanitize_url() deferred to v1.2.0 — known-issues.md)
|
||||
- [x] wp_unslash() before sanitizing superglobals
|
||||
|
||||
## OUTPUT ESCAPING
|
||||
- [x] esc_html() for HTML element content
|
||||
- [x] esc_attr() for HTML attributes
|
||||
- [x] esc_url() for all URLs
|
||||
- [x] esc_js() for inline JavaScript (delete confirm)
|
||||
- [x] esc_html__(), esc_attr_e() combined i18n+escape functions
|
||||
- [x] Numeric variables: absint(), (int)
|
||||
- [x] wp_kses_post() for debug details and status badges
|
||||
- [x] Escaping at output time, not before storage
|
||||
|
||||
## CSRF PROTECTION
|
||||
- [x] wp_nonce_field() on all forms (save_mapping, save_settings)
|
||||
- [x] wp_nonce_url() for action URLs (sync, delete)
|
||||
- [x] check_admin_referer() for all GET state-changing actions
|
||||
- [x] wp_verify_nonce() in save handlers and updater cache clear
|
||||
- [x] Nonce action strings are specific (include mapping_id)
|
||||
- [x] Nonces NOT used for authorization
|
||||
|
||||
## DATABASE SECURITY
|
||||
- [x] $wpdb->prepare() for all custom SQL (transient cleanup)
|
||||
- [x] $wpdb->esc_like() for LIKE patterns
|
||||
- [x] WordPress API functions for CPT and post meta
|
||||
- [x] No $_POST/$_GET interpolation in queries
|
||||
|
||||
## CAPABILITY CHECKS
|
||||
- [x] current_user_can('edit_pages') on every admin page render
|
||||
- [x] current_user_can('edit_pages') in every handler
|
||||
- [x] current_user_can('update_plugins') in updater cache clear
|
||||
- [x] Capability checks in UI rendering (wp_die if no permission)
|
||||
- [x] Capability checks in execution logic
|
||||
|
||||
## FILE OPERATIONS
|
||||
- [x] ABSPATH guard on every PHP file
|
||||
- [x] base64_decode() strict with length validation (fixed in 1.1.1)
|
||||
- [x] No user file uploads managed by this plugin
|
||||
|
||||
## DANGEROUS FUNCTIONS
|
||||
- [x] No eval()
|
||||
- [x] base64_decode() only for GitHub API content decode (documented necessity)
|
||||
- [x] No system(), exec(), shell_exec(), passthru()
|
||||
- [x] No unserialize() — serialize() replaced with wp_json_encode() in 1.1.1
|
||||
|
||||
## CODE QUALITY & STANDARDS
|
||||
- [x] PHPCS: 0 errors, 0 warnings — 7 files, no exclusions
|
||||
- [x] PHPStan level 9: 0 errors — 7 files, no exclusions
|
||||
- [x] PHPCompatibility 10.0.0-alpha2: 0 errors (PHP 8.0–8.5)
|
||||
- [x] Requires PHP: 8.0 (real minimum confirmed by PHPCompatibility scan)
|
||||
- [x] All i18n strings use textdomain 'robotstxt-documentation-markdown'
|
||||
- [x] phpDoc on all public functions/methods/hooks
|
||||
|
||||
## TESTING
|
||||
- [x] PHPUnit: 17/17 tests OK (plugin header tests)
|
||||
- [x] Stable tag 1.1.1 = Version 1.1.1 = ROBOTSTXT_DOCMD_VERSION = update.json version ✓
|
||||
|
||||
## VERSIONING & DOCUMENTATION
|
||||
- [x] Plugin header: Version: 1.1.1
|
||||
- [x] ROBOTSTXT_DOCMD_VERSION constant: '1.1.1'
|
||||
- [x] CHANGELOG.md updated
|
||||
- [x] changelog.txt updated (WP.org format)
|
||||
- [x] readme.txt: Stable tag 1.1.1, Requires PHP 8.0, Tested up to 7.1
|
||||
- [x] update.json: version 1.1.1, requires_php 8.0, tested 7.1
|
||||
- [x] README.md — no new external dependencies added
|
||||
|
||||
## DATABASE & UNINSTALL
|
||||
- [x] uninstall.php exists and respects data preservation option
|
||||
- [x] No custom DB tables — documented in docs/db-migrations.md
|
||||
- [x] Settings stored with autoload=false (fixed in 1.1.1)
|
||||
|
||||
## AI AUDIT
|
||||
- [x] Pre-deploy AI audit executed on full codebase
|
||||
- [x] All [CRITICAL] findings resolved (Finding 011: CommonMark html_input:strip)
|
||||
- [x] [WARNING] findings deferred documented in docs/known-issues.md
|
||||
- [x] Audit report: docs/audit-pre-deploy-1.1.1.md
|
||||
- [x] Executive summary: PASS
|
||||
- [x] Security risk: Low
|
||||
|
||||
## BUILD & ARTIFACT
|
||||
- [x] deploy.sh executed manually — artifact generated
|
||||
- [x] ZIP excludes dev files (composer.json, phpstan.neon, .phpcs.xml, tests/, docs/, bin/, AGENTS.md, CLAUDE.md)
|
||||
- [x] Production vendor/ included (no require-dev packages)
|
||||
- [x] *.po files excluded (only .mo bundled)
|
||||
|
||||
---
|
||||
|
||||
## DEPLOY AUTHORIZATION
|
||||
|
||||
- All [CRITICAL] items resolved: **YES**
|
||||
- Executive summary: **PASS**
|
||||
- Security risk: **Low**
|
||||
- Manual approval: **YES**
|
||||
11
docs/known-issues.md
Normal file
11
docs/known-issues.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Known Issues
|
||||
|
||||
This file documents [WARNING]-level findings from pre-deploy audits that have been deferred to future releases.
|
||||
|
||||
## Active deferred issues
|
||||
|
||||
_None._
|
||||
|
||||
---
|
||||
|
||||
*For resolved issues see CHANGELOG.md. For [CRITICAL] findings see pre-deploy audit reports in this directory.*
|
||||
|
|
@ -1,845 +0,0 @@
|
|||
# Translation of Documentation Markdown in Spanish (Spain)
|
||||
# Copyright (C) 2026 ROBOTSTXT
|
||||
# This file is distributed under the GPL-3.0-or-later.
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Documentation Markdown 0.1.0\n"
|
||||
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/robotstxt-"
|
||||
"documentation-markdown\n"
|
||||
"POT-Creation-Date: 2026-01-25T18:11:49+00:00\n"
|
||||
"PO-Revision-Date: 2026-01-25 17:30+0000\n"
|
||||
"Last-Translator: ROBOTSTXT <info@robotstxt.com>\n"
|
||||
"Language-Team: Spanish (Spain)\n"
|
||||
"Language: es_ES\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: WP-CLI 2.10.0\n"
|
||||
"X-Domain: robotstxt-documentation-markdown\n"
|
||||
|
||||
#. Plugin Name of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
msgid "Documentation Markdown (by ROBOTSTXT)"
|
||||
msgstr ""
|
||||
|
||||
#. Plugin URI of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
msgid "https://github.com/robotstxt/documentation-markdown"
|
||||
msgstr ""
|
||||
|
||||
#. Description of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
"Synchronizes Markdown documentation from GitHub repositories to WordPress "
|
||||
"pages and posts automatically."
|
||||
msgstr ""
|
||||
"Este plugin sincroniza documentación en formato Markdown desde repositorios "
|
||||
"de GitHub y la convierte en páginas o entradas de WordPress automáticamente."
|
||||
|
||||
#. Author of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
msgid "ROBOTSTXT"
|
||||
msgstr ""
|
||||
|
||||
#. Author URI of the plugin
|
||||
#: robotstxt-documentation-markdown.php
|
||||
msgid "https://robotstxt.com"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:52
|
||||
msgid "Manage Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:53
|
||||
msgid "Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:61 includes/admin/Admin_Mappings.php:173
|
||||
#: includes/admin/Admin_Mappings.php:382
|
||||
msgid "Add New Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:62 includes/admin/Admin_Mappings.php:136
|
||||
msgid "Add New"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:105 includes/admin/Admin_Mappings.php:276
|
||||
msgid "Syncing..."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:106 includes/admin/Admin_Mappings.php:653
|
||||
#, fuzzy
|
||||
msgid "Sync completed successfully!"
|
||||
msgstr "Configuración guardada correctamente."
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:107
|
||||
msgid "Sync failed:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:108 includes/admin/Admin_Mappings.php:342
|
||||
msgid "Are you sure you want to delete this mapping?"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:109
|
||||
msgid "Syncing all mappings..."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:124 includes/admin/Admin_Mappings.php:357
|
||||
#: includes/admin/Admin_Settings.php:542
|
||||
msgid "You do not have sufficient permissions to access this page."
|
||||
msgstr "No tienes permisos suficientes para acceder a esta página."
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:133
|
||||
#, fuzzy
|
||||
msgid "Documentation Mappings"
|
||||
msgstr "Configuración de Documentación"
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:145
|
||||
msgid "Total Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:149
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:153
|
||||
msgid "Last Success"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:157
|
||||
msgid "Last Error"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:164
|
||||
msgid "Sync All Active Mappings"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:171
|
||||
msgid "No mappings found. Create your first mapping to get started!"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:180
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:181
|
||||
msgid "Repository"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:182
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:183
|
||||
msgid "Frequency"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:184
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:185
|
||||
msgid "Sync Status"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:186
|
||||
msgid "Last Sync"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:187
|
||||
msgid "Next Sync"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:188
|
||||
msgid "Actions"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:208
|
||||
#, php-format
|
||||
msgid "Branch: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:214 includes/admin/Admin_Mappings.php:232
|
||||
msgid "Never"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:219
|
||||
msgid "Enabled"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:221
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:242
|
||||
msgid "Manual Only"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:272
|
||||
msgid "Never Synced"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:280
|
||||
msgid "Success"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:284
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:337
|
||||
msgid "Sync Now"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:340
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:343
|
||||
msgid "Delete"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:382
|
||||
msgid "Edit Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:397
|
||||
msgid "Mapping Title"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:401
|
||||
msgid "A descriptive name for this mapping."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:407
|
||||
msgid "Repository Owner"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:411
|
||||
msgid "GitHub username or organization (e.g., \"wordpress\")."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:417
|
||||
msgid "Repository Name"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:421
|
||||
msgid "Repository name (e.g., \"wordpress-develop\")."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:427
|
||||
msgid "File Path"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:431
|
||||
msgid "Path to Markdown file (e.g., \"README.md\" or \"docs/api.md\")."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:437
|
||||
msgid "Branch"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:441
|
||||
msgid "Git branch name (default: \"main\")."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:447
|
||||
msgid "Target Post Type"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:463
|
||||
msgid "WordPress post type for the synced content."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:469
|
||||
msgid "Post Author"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:481
|
||||
msgid "WordPress user to assign as post author."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:487
|
||||
msgid "Sync Frequency"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:503
|
||||
msgid "How often to automatically sync this documentation from GitHub."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:509
|
||||
msgid "Enable Sync"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:514
|
||||
msgid "Enable automatic synchronization for this mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:516
|
||||
msgid "Must be enabled for automatic syncing to work."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:521
|
||||
msgid "Update Mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:521
|
||||
#, fuzzy
|
||||
msgid "Create Mapping"
|
||||
msgstr "Crear un token"
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:536 includes/admin/Admin_Mappings.php:593
|
||||
#, fuzzy
|
||||
msgid "You do not have sufficient permissions."
|
||||
msgstr "No tienes permisos suficientes para acceder a esta página."
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:634 includes/admin/Admin_Mappings.php:670
|
||||
msgid "Insufficient permissions."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:640
|
||||
msgid "Invalid mapping ID."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: Success count, 2: Error count
|
||||
#: includes/admin/Admin_Mappings.php:690
|
||||
#, php-format
|
||||
msgid "Sync complete: %1$d succeeded, %2$d failed."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:711
|
||||
#, fuzzy
|
||||
msgid "Mapping created successfully."
|
||||
msgstr "Configuración guardada correctamente."
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:712
|
||||
#, fuzzy
|
||||
msgid "Mapping updated successfully."
|
||||
msgstr "Configuración guardada correctamente."
|
||||
|
||||
#: includes/admin/Admin_Mappings.php:713
|
||||
#, fuzzy
|
||||
msgid "Mapping deleted successfully."
|
||||
msgstr "Configuración guardada correctamente."
|
||||
|
||||
#: includes/admin/Admin_Settings.php:45
|
||||
msgid "Documentation Settings"
|
||||
msgstr "Configuración de Documentación"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:46
|
||||
msgid "Documentation"
|
||||
msgstr "Documentación"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:78
|
||||
msgid "GitHub Repository Settings"
|
||||
msgstr "Configuración del Repositorio de GitHub"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:85
|
||||
msgid "GitHub Documentation URL"
|
||||
msgstr "URL de Documentación en GitHub"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:93
|
||||
msgid "GitHub Personal Access Token"
|
||||
msgstr "Token de Acceso Personal de GitHub"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:102
|
||||
#, fuzzy
|
||||
msgid "Advanced Settings"
|
||||
msgstr "Guardar Configuración"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:109
|
||||
msgid "Delete Data on Uninstall"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:118
|
||||
msgid "Email Notifications"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:125
|
||||
msgid "Enable Notifications"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:133
|
||||
msgid "Notification Email"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:141
|
||||
msgid "Notify on Success"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:149
|
||||
msgid "Notify Unsynchronized"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:181
|
||||
msgid "GitHub URL must use HTTPS protocol for security. URL was not saved."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:210
|
||||
msgid "Warning: Token could not be encrypted. It has been saved in plain text."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:251
|
||||
msgid ""
|
||||
"Configure your GitHub repository settings to synchronize documentation from "
|
||||
"GitHub to WordPress."
|
||||
msgstr ""
|
||||
"Configura los ajustes de tu repositorio de GitHub para sincronizar la "
|
||||
"documentación desde GitHub a WordPress."
|
||||
|
||||
#: includes/admin/Admin_Settings.php:267
|
||||
msgid "Advanced configuration options for plugin behavior."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:295
|
||||
msgid ""
|
||||
"Enter the full URL of your GitHub repository (e.g., https://github.com/owner/"
|
||||
"repository)"
|
||||
msgstr ""
|
||||
"Introduce la URL completa de tu repositorio de GitHub (ej: https://github."
|
||||
"com/propietario/repositorio)"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:322
|
||||
msgid "ghp_xxxxxxxxxxxx"
|
||||
msgstr "ghp_xxxxxxxxxxxx"
|
||||
|
||||
#. translators: %s: Link to GitHub Personal Access Tokens page
|
||||
#: includes/admin/Admin_Settings.php:328
|
||||
#, php-format
|
||||
msgid "Enter your GitHub Personal Access Token. %s"
|
||||
msgstr "Introduce tu Token de Acceso Personal de GitHub. %s"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:333
|
||||
msgid "Create a token"
|
||||
msgstr "Crear un token"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:340
|
||||
msgid "How to create a GitHub Personal Access Token:"
|
||||
msgstr "Cómo crear un Token de Acceso Personal de GitHub:"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:343
|
||||
msgid "Go to GitHub → Settings → Developer settings → Personal access tokens"
|
||||
msgstr ""
|
||||
"Ve a GitHub → Configuración → Ajustes de desarrollador → Tokens de acceso "
|
||||
"personal"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:344
|
||||
msgid "Click \"Generate new token\" (classic)"
|
||||
msgstr "Haz clic en \"Generar nuevo token\" (clásico)"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:345
|
||||
msgid "Give it a descriptive name (e.g., \"WordPress Documentation Sync\")"
|
||||
msgstr ""
|
||||
"Dale un nombre descriptivo (ej: \"Sincronización Documentación WordPress\")"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:348
|
||||
msgid "For public repositories: No specific scopes needed"
|
||||
msgstr "Para repositorios públicos: No se necesitan permisos específicos"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:355
|
||||
msgid "For private repositories: Select the \"repo\" scope"
|
||||
msgstr "Para repositorios privados: Selecciona el permiso \"repo\""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:361
|
||||
msgid "Click \"Generate token\" and copy it immediately"
|
||||
msgstr "Haz clic en \"Generar token\" y cópialo inmediatamente"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:362
|
||||
msgid "Paste the token in the field above and save"
|
||||
msgstr "Pega el token en el campo de arriba y guarda"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:367
|
||||
msgid ""
|
||||
"Note: For security, your token is stored encrypted and will be shown as dots "
|
||||
"once saved."
|
||||
msgstr ""
|
||||
"Nota: Por seguridad, tu token se almacena cifrado y se mostrará como puntos "
|
||||
"una vez guardado."
|
||||
|
||||
#: includes/admin/Admin_Settings.php:397
|
||||
msgid "Delete all plugin data when uninstalling"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:401
|
||||
msgid ""
|
||||
"If enabled, all plugin settings and mapping configurations will be deleted "
|
||||
"when you uninstall the plugin."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:409
|
||||
msgid "Important:"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:411
|
||||
msgid ""
|
||||
"Your synced documentation content (pages/posts) will NOT be deleted, only "
|
||||
"the mapping configurations and plugin settings."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:419
|
||||
msgid ""
|
||||
"This ensures your documentation remains available even after uninstalling "
|
||||
"the plugin."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:438
|
||||
msgid "Configure email notifications for sync events."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:458
|
||||
msgid "Send email notifications for sync errors"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:461
|
||||
msgid ""
|
||||
"You will receive an email when a sync operation fails (rate limited to once "
|
||||
"per hour per mapping)."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Default admin email
|
||||
#: includes/admin/Admin_Settings.php:482
|
||||
#, php-format
|
||||
msgid "Email address for notifications (default: %s)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:503
|
||||
msgid "Send email notifications for successful syncs"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:506
|
||||
msgid ""
|
||||
"Receive an email confirmation when content is successfully synchronized (not "
|
||||
"recommended for frequent syncs)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:524
|
||||
msgid "Notify when mappings have not been synchronized"
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:527
|
||||
msgid ""
|
||||
"Receive alerts when enabled mappings with auto-sync have never been "
|
||||
"synchronized (rate limited to once per hour)."
|
||||
msgstr ""
|
||||
|
||||
#: includes/admin/Admin_Settings.php:550
|
||||
msgid "Settings saved successfully."
|
||||
msgstr "Configuración guardada correctamente."
|
||||
|
||||
#: includes/admin/Admin_Settings.php:563
|
||||
msgid ""
|
||||
"Documentation Markdown allows you to automatically synchronize Markdown "
|
||||
"documentation from GitHub repositories to your WordPress site."
|
||||
msgstr ""
|
||||
"Documentation Markdown te permite sincronizar automáticamente documentación "
|
||||
"en formato Markdown desde repositorios de GitHub a tu sitio WordPress."
|
||||
|
||||
#: includes/admin/Admin_Settings.php:575
|
||||
msgid "Save Settings"
|
||||
msgstr "Guardar Configuración"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:580
|
||||
msgid "About This Plugin"
|
||||
msgstr "Acerca de este Plugin"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:583
|
||||
msgid ""
|
||||
"This plugin synchronizes Markdown documentation from GitHub repositories and "
|
||||
"converts it into WordPress pages or posts automatically."
|
||||
msgstr ""
|
||||
"Este plugin sincroniza documentación en formato Markdown desde repositorios "
|
||||
"de GitHub y la convierte en páginas o entradas de WordPress automáticamente."
|
||||
|
||||
#: includes/admin/Admin_Settings.php:590
|
||||
msgid "Version:"
|
||||
msgstr "Versión:"
|
||||
|
||||
#: includes/admin/Admin_Settings.php:594
|
||||
msgid "Developer:"
|
||||
msgstr "Desarrollador:"
|
||||
|
||||
#: includes/Cron_Manager.php:43
|
||||
msgid "Never (Manual Only)"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:47
|
||||
msgid "Every 1 Minute"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:51
|
||||
msgid "Every 5 Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:55
|
||||
msgid "Every 10 Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:59
|
||||
msgid "Every 15 Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:63
|
||||
msgid "Every 30 Minutes"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:67
|
||||
msgid "Every 1 Hour"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:71
|
||||
msgid "Every 2 Hours"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:75
|
||||
msgid "Every 4 Hours"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:79
|
||||
msgid "Every 6 Hours"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:83
|
||||
msgid "Every 12 Hours"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:87
|
||||
msgid "Once Daily"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:238 includes/Cron_Manager.php:247
|
||||
msgid "Now"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Mapping title
|
||||
#: includes/Cron_Manager.php:286
|
||||
#, fuzzy, php-format
|
||||
msgid "[Documentation Sync] Error: %s"
|
||||
msgstr "Configuración de Documentación"
|
||||
|
||||
#. translators: %s: Mapping title
|
||||
#: includes/Cron_Manager.php:332
|
||||
#, fuzzy, php-format
|
||||
msgid "[Documentation Sync] Success: %s"
|
||||
msgstr "Configuración de Documentación"
|
||||
|
||||
#: includes/Cron_Manager.php:397
|
||||
msgid "[Documentation Sync] Unsynchronized Files Found"
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: Mapping title, 2: Error message
|
||||
#: includes/Cron_Manager.php:491
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Failed to sync documentation: %1$s\n"
|
||||
"\n"
|
||||
"Error: %2$s\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Cron_Manager.php:496 includes/Cron_Manager.php:538
|
||||
#, fuzzy
|
||||
msgid "Repository Details:\n"
|
||||
msgstr "Configuración del Repositorio de GitHub"
|
||||
|
||||
#. translators: %s: Admin URL
|
||||
#: includes/Cron_Manager.php:504
|
||||
#, php-format
|
||||
msgid "Manage mappings: %s\n"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Mapping title
|
||||
#: includes/Cron_Manager.php:522
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Successfully synchronized documentation: %s\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: Post title, 2: Post URL
|
||||
#: includes/Cron_Manager.php:531
|
||||
#, php-format
|
||||
msgid ""
|
||||
"Post: %1$s\n"
|
||||
"View: %2$s\n"
|
||||
"\n"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %d: Number of unsynchronized mappings
|
||||
#: includes/Cron_Manager.php:558
|
||||
#, php-format
|
||||
msgid "There is %d documentation mapping that has not been synchronized:"
|
||||
msgid_plural ""
|
||||
"There are %d documentation mappings that have not been synchronized:"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#. translators: %s: Admin URL
|
||||
#: includes/Cron_Manager.php:579
|
||||
#, php-format
|
||||
msgid "Please review and sync these mappings: %s\n"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:34
|
||||
msgid "OpenSSL extension is required for token encryption"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:47 includes/encryption-functions.php:112
|
||||
msgid "Failed to determine cipher IV length"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:64
|
||||
msgid "Encryption failed"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:90
|
||||
msgid "OpenSSL extension is required for token decryption"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:99
|
||||
msgid "Invalid encrypted data format"
|
||||
msgstr ""
|
||||
|
||||
#: includes/encryption-functions.php:130
|
||||
msgid "Decryption failed"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:81
|
||||
msgid "Invalid response from GitHub API: missing required fields"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:91
|
||||
msgid "Failed to decode file content from GitHub"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:132
|
||||
msgid "SHA not found in GitHub API response"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Error message
|
||||
#: includes/GitHub_API_Client.php:213
|
||||
#, php-format
|
||||
msgid "GitHub API request failed: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:230
|
||||
msgid "Unknown error"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:234
|
||||
msgid "Authentication failed. Please check your GitHub token."
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:235
|
||||
msgid "Access forbidden. Check your token permissions or rate limit."
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:236
|
||||
msgid "File not found in repository. Please verify the file path and branch."
|
||||
msgstr ""
|
||||
|
||||
#. translators: 1: HTTP status code, 2: Error message
|
||||
#: includes/GitHub_API_Client.php:239
|
||||
#, php-format
|
||||
msgid "GitHub API error (%1$d): %2$s"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: JSON error message
|
||||
#: includes/GitHub_API_Client.php:255
|
||||
#, php-format
|
||||
msgid "Invalid JSON response from GitHub: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/GitHub_API_Client.php:347
|
||||
msgid "unknown"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Reset time
|
||||
#: includes/GitHub_API_Client.php:352
|
||||
#, php-format
|
||||
msgid "GitHub API rate limit nearly exceeded. Resets at: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:73 includes/mapping-functions.php:118
|
||||
msgid "Invalid mapping ID"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:124
|
||||
msgid "Failed to delete mapping"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:227
|
||||
msgid "Repository owner is required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:233
|
||||
msgid "Invalid repository owner format"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:238
|
||||
msgid "Repository name is required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:244
|
||||
msgid "Invalid repository name format"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:249
|
||||
msgid "File path is required"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:256
|
||||
msgid "File path cannot contain \"..\""
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:261
|
||||
msgid "File must be .md or .markdown"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:268
|
||||
msgid "Invalid branch name format"
|
||||
msgstr ""
|
||||
|
||||
#: includes/mapping-functions.php:275
|
||||
msgid "Post type does not exist"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Plugin.php:172
|
||||
#, fuzzy
|
||||
msgid "GitHub Doc Mappings"
|
||||
msgstr "URL de Documentación en GitHub"
|
||||
|
||||
#: includes/Plugin.php:173
|
||||
#, fuzzy
|
||||
msgid "GitHub Doc Mapping"
|
||||
msgstr "URL de Documentación en GitHub"
|
||||
|
||||
#: includes/Sync_Manager.php:84 includes/Sync_Manager.php:422
|
||||
msgid "Synchronization is disabled for this mapping."
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Error message from GitHub API
|
||||
#: includes/Sync_Manager.php:259
|
||||
#, php-format
|
||||
msgid "Failed to fetch file from GitHub: %s"
|
||||
msgstr ""
|
||||
|
||||
#. translators: %s: Error message
|
||||
#: includes/Sync_Manager.php:441
|
||||
#, php-format
|
||||
msgid "Failed to check for changes: %s"
|
||||
msgstr ""
|
||||
|
||||
#: includes/Sync_Manager.php:498
|
||||
#, fuzzy
|
||||
msgid "GitHub Personal Access Token is not configured."
|
||||
msgstr "Token de Acceso Personal de GitHub"
|
||||
|
||||
#: includes/Sync_Manager.php:504
|
||||
msgid "No active mappings configured."
|
||||
msgstr ""
|
||||
|
||||
#: includes/Sync_Manager.php:509
|
||||
msgid "OpenSSL extension is not available (required for token encryption)."
|
||||
msgstr ""
|
||||
38
readme.txt
38
readme.txt
|
|
@ -2,9 +2,9 @@
|
|||
Contributors: robotstxt
|
||||
Tags: github, documentation, markdown, sync, automation
|
||||
Requires at least: 6.7
|
||||
Tested up to: 7.0
|
||||
Requires PHP: 8.2
|
||||
Stable tag: 1.1.0
|
||||
Tested up to: 7.1
|
||||
Requires PHP: 8.0
|
||||
Stable tag: 1.1.1
|
||||
License: GPLv3 or later
|
||||
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
|
@ -178,13 +178,41 @@ Then go to Documentation → Settings, and you'll see a "Debug Tools" section at
|
|||
|
||||
== Compatibility ==
|
||||
|
||||
* WordPress: 6.7 - 6.9
|
||||
* PHP: 8.2 - 8.5
|
||||
* WordPress: 6.7 - 7.1
|
||||
* PHP: 8.0 - 8.5
|
||||
|
||||
== Changelog ==
|
||||
|
||||
For the complete changelog, see [changelog.txt](https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown/raw/branch/main/changelog.txt).
|
||||
|
||||
= 1.1.1 - 2026-06-08 =
|
||||
|
||||
**Security**
|
||||
|
||||
* CommonMark: disabled raw HTML passthrough (`html_input: strip`, `allow_unsafe_links: false`) — prevents stored XSS via a compromised upstream repository
|
||||
* Settings option now stored with `autoload = false` — encrypted GitHub token no longer loaded on every WordPress request
|
||||
* Replaced `serialize()` with `wp_json_encode()` in updater HMAC cache signature
|
||||
* Added strict base64 length validation before IV extraction in token decryption
|
||||
* Added `base64_decode()` return-value check in GitHub file content fetch
|
||||
|
||||
**Fixed**
|
||||
|
||||
* Admin notices now display results for all operations: mapping created, updated, deleted, sync complete, and errors — previously only "Settings saved" was shown
|
||||
* `wp_update_post()` return value now checked during sync — silent failures no longer reported as success
|
||||
* Wrong textdomain in updater class (`'robotstxt-smtp'` → `'robotstxt-documentation-markdown'`)
|
||||
|
||||
**Changed**
|
||||
|
||||
* Renamed `robotstxt-updater.php` → `class-robotstxt-updater.php` (WordPress file naming convention)
|
||||
* Minimum PHP version corrected to 8.0 (real minimum confirmed by PHPCompatibility scan — no PHP 8.1 or 8.2 specific features used)
|
||||
|
||||
**Developer**
|
||||
|
||||
* Added full tooling: `composer.json`, `phpstan.neon` (level 9), `.phpcs.xml`, `bin/deploy.sh`, `phpunit.xml.dist`
|
||||
* PHPCompatibility updated to 10.0.0-alpha2 (PHP 8.x feature detection)
|
||||
* PHPUnit: 17 plugin header tests added
|
||||
* PHPCS, PHPStan level 9, PHPUnit all pass with 0 errors
|
||||
|
||||
= 1.1.0 - 2026-03-28 =
|
||||
|
||||
**Security**
|
||||
|
|
|
|||
|
|
@ -52,7 +52,10 @@ function robotstxt_docmd_decrypt_token( string $encrypted_token ): string {
|
|||
$key = wp_salt( 'auth' );
|
||||
$iv_length = openssl_cipher_iv_length( 'aes-256-cbc' );
|
||||
|
||||
$decoded = base64_decode( $encrypted_token ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
$decoded = base64_decode( $encrypted_token, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
if ( false === $decoded || strlen( $decoded ) <= $iv_length ) {
|
||||
return '';
|
||||
}
|
||||
$iv = substr( $decoded, 0, $iv_length );
|
||||
$encrypted = substr( $decoded, $iv_length );
|
||||
|
||||
|
|
|
|||
|
|
@ -286,7 +286,11 @@ function robotstxt_docmd_get_file_content( string $owner, string $repo, string $
|
|||
|
||||
// Decode content (GitHub returns base64).
|
||||
if ( 'base64' === $data['encoding'] ) {
|
||||
return base64_decode( $data['content'] ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
$decoded = base64_decode( $data['content'], true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
if ( false === $decoded ) {
|
||||
return new WP_Error( 'decode_failed', __( 'Failed to decode file content from GitHub.', 'robotstxt-documentation-markdown' ) );
|
||||
}
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
return $data['content'];
|
||||
|
|
|
|||
|
|
@ -293,7 +293,13 @@ function robotstxt_docmd_sync_mapping( int $mapping_id ): bool|WP_Error {
|
|||
}
|
||||
|
||||
// Convert Markdown to HTML using CommonMark.
|
||||
$converter = new \League\CommonMark\CommonMarkConverter();
|
||||
// Strip raw HTML from Markdown to prevent stored XSS via compromised upstream repos.
|
||||
$converter = new \League\CommonMark\CommonMarkConverter(
|
||||
array(
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
)
|
||||
);
|
||||
$html_content = $converter->convert( $content )->getContent();
|
||||
|
||||
// Create or update WordPress post.
|
||||
|
|
@ -321,12 +327,18 @@ function robotstxt_docmd_sync_mapping( int $mapping_id ): bool|WP_Error {
|
|||
update_post_meta( $mapping_id, '_robotstxt_docmd_target_post_id', $post_id );
|
||||
} else {
|
||||
// Update existing post.
|
||||
wp_update_post(
|
||||
$update_result = wp_update_post(
|
||||
array(
|
||||
'ID' => $mapping['target_post_id'],
|
||||
'post_content' => $html_content,
|
||||
)
|
||||
),
|
||||
true
|
||||
);
|
||||
|
||||
if ( is_wp_error( $update_result ) ) {
|
||||
update_post_meta( $mapping_id, '_robotstxt_docmd_sync_status', 'error' );
|
||||
return $update_result;
|
||||
}
|
||||
}
|
||||
|
||||
// Update sync status.
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
* Plugin Name: Documentation Markdown (by ROBOTSTXT)
|
||||
* Plugin URI: https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown
|
||||
* Description: Synchronizes Markdown documentation from GitHub repositories to WordPress pages and posts automatically.
|
||||
* Version: 1.1.0
|
||||
* Version: 1.1.1
|
||||
* Requires at least: 6.7
|
||||
* Requires PHP: 8.2
|
||||
* Requires PHP: 8.0
|
||||
* Security: robotstxt@robotstxt.es
|
||||
* Author: ROBOTSTXT
|
||||
* Author URI: https://www.robotstxt.es/
|
||||
|
|
@ -26,7 +26,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||
}
|
||||
|
||||
// Define plugin constants.
|
||||
define( 'ROBOTSTXT_DOCMD_VERSION', '1.1.0' );
|
||||
define( 'ROBOTSTXT_DOCMD_VERSION', '1.1.1' );
|
||||
define( 'ROBOTSTXT_DOCMD_PLUGIN_FILE', __FILE__ );
|
||||
define( 'ROBOTSTXT_DOCMD_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'ROBOTSTXT_DOCMD_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||
|
|
@ -74,7 +74,9 @@ function robotstxt_docmd_activate() {
|
|||
array(
|
||||
'github_url' => '',
|
||||
'github_token' => '',
|
||||
)
|
||||
),
|
||||
'',
|
||||
false // Do not autoload — contains encrypted token.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -909,9 +911,26 @@ function robotstxt_docmd_render_admin_notices() {
|
|||
// Check for regular message parameter.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$message = sanitize_key( wp_unslash( robotstxt_docmd_input_string( $_GET, 'message' ) ) );
|
||||
if ( 'settings_saved' === $message ) {
|
||||
|
||||
$success_messages = array(
|
||||
'settings_saved' => __( 'Settings saved successfully.', 'robotstxt-documentation-markdown' ),
|
||||
'mapping_created' => __( 'Mapping created successfully.', 'robotstxt-documentation-markdown' ),
|
||||
'mapping_updated' => __( 'Mapping updated successfully.', 'robotstxt-documentation-markdown' ),
|
||||
'mapping_deleted' => __( 'Mapping deleted.', 'robotstxt-documentation-markdown' ),
|
||||
'sync_complete' => __( 'Sync completed successfully.', 'robotstxt-documentation-markdown' ),
|
||||
);
|
||||
|
||||
if ( isset( $success_messages[ $message ] ) ) {
|
||||
echo '<div class="notice notice-success is-dismissible"><p>';
|
||||
esc_html_e( 'Settings saved successfully.', 'robotstxt-documentation-markdown' );
|
||||
echo esc_html( $success_messages[ $message ] );
|
||||
echo '</p></div>';
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$error_text = sanitize_text_field( wp_unslash( robotstxt_docmd_input_string( $_GET, 'error' ) ) );
|
||||
if ( '' !== $error_text ) {
|
||||
echo '<div class="notice notice-error is-dismissible"><p>';
|
||||
echo esc_html( $error_text );
|
||||
echo '</p></div>';
|
||||
}
|
||||
|
||||
|
|
@ -1159,7 +1178,7 @@ function robotstxt_docmd_handle_save_settings() {
|
|||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
$settings['delete_on_uninstall'] = ! empty( $_POST['delete_on_uninstall'] ) ? 1 : 0;
|
||||
|
||||
update_option( 'robotstxt_docmd_settings', $settings );
|
||||
update_option( 'robotstxt_docmd_settings', $settings, false );
|
||||
|
||||
wp_safe_redirect( add_query_arg( 'message', 'settings_saved', admin_url( 'admin.php?page=robotstxt-docmd-settings' ) ) );
|
||||
exit;
|
||||
|
|
@ -1589,5 +1608,5 @@ function robotstxt_docmd_debug_fix_crons() {
|
|||
}
|
||||
|
||||
// Initialize ROBOTSTXT updater (auto-configures from plugin headers).
|
||||
require_once __DIR__ . '/robotstxt-updater.php';
|
||||
require_once __DIR__ . '/class-robotstxt-updater.php';
|
||||
Robotstxt_Updater::init( __FILE__ );
|
||||
|
|
|
|||
10
update.json
10
update.json
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "Documentation Markdown (by ROBOTSTXT)",
|
||||
"slug": "robotstxt-documentation-markdown",
|
||||
"version": "1.0.0",
|
||||
"download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown/releases/download/1.0.0/robotstxt-documentation-markdown-1.0.0.zip",
|
||||
"version": "1.1.1",
|
||||
"download_url": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown/releases/download/1.1.1/robotstxt-documentation-markdown-1.1.1.zip",
|
||||
"requires": "6.7",
|
||||
"requires_php": "8.2",
|
||||
"tested": "6.9",
|
||||
"last_updated": "2026-01-26",
|
||||
"requires_php": "8.0",
|
||||
"tested": "7.1",
|
||||
"last_updated": "2026-06-08",
|
||||
"author": "ROBOTSTXT",
|
||||
"author_profile": "https://www.robotstxt.es/",
|
||||
"homepage": "https://git.robotstxt.es/ROBOTSTXT/robotstxt-documentation-markdown",
|
||||
|
|
|
|||
7
vendor/autoload.php
vendored
7
vendor/autoload.php
vendored
|
|
@ -14,12 +14,9 @@ if (PHP_VERSION_ID < 50600) {
|
|||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit216e6d5522dd071bc42c5f7c15b28cdc::getLoader();
|
||||
return ComposerAutoloaderInit062b6bfebe9519df3bcac54aafa83aec::getLoader();
|
||||
|
|
|
|||
45
vendor/composer/InstalledVersions.php
vendored
45
vendor/composer/InstalledVersions.php
vendored
|
|
@ -26,12 +26,23 @@ use Composer\Semver\VersionParser;
|
|||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||
* @internal
|
||||
*/
|
||||
private static $selfDir = null;
|
||||
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
|
|
@ -309,6 +320,24 @@ class InstalledVersions
|
|||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
|
||||
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||
// all installed packages for example
|
||||
self::$installedIsLocalDir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getSelfDir()
|
||||
{
|
||||
if (self::$selfDir === null) {
|
||||
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||
}
|
||||
|
||||
return self::$selfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -322,19 +351,27 @@ class InstalledVersions
|
|||
}
|
||||
|
||||
$installed = array();
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = self::getSelfDir();
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = $required;
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
self::$installedByVendor[$vendorDir] = $required;
|
||||
$installed[] = $required;
|
||||
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||
self::$installed = $required;
|
||||
self::$installedIsLocalDir = true;
|
||||
}
|
||||
}
|
||||
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +387,7 @@ class InstalledVersions
|
|||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array()) {
|
||||
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
|
|
|
|||
2
vendor/composer/LICENSE
vendored
2
vendor/composer/LICENSE
vendored
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
|
|
@ -17,3 +18,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
|
|
|||
3
vendor/composer/autoload_classmap.php
vendored
3
vendor/composer/autoload_classmap.php
vendored
|
|
@ -377,6 +377,9 @@ return array(
|
|||
'Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php',
|
||||
'Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php',
|
||||
'Nette\\Utils\\Process' => $vendorDir . '/nette/utils/src/Utils/Process.php',
|
||||
'Nette\\Utils\\ProcessFailedException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ProcessTimeoutException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php',
|
||||
'Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php',
|
||||
'Nette\\Utils\\ReflectionMethod' => $vendorDir . '/nette/utils/src/Utils/ReflectionMethod.php',
|
||||
|
|
|
|||
3
vendor/composer/autoload_psr4.php
vendored
3
vendor/composer/autoload_psr4.php
vendored
|
|
@ -7,9 +7,8 @@ $baseDir = dirname($vendorDir);
|
|||
|
||||
return array(
|
||||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
||||
'RobotsTxt\\DocumentationMarkdown\\' => array($baseDir . '/includes'),
|
||||
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
|
||||
'Nette\\' => array($vendorDir . '/nette/utils/src', $vendorDir . '/nette/schema/src'),
|
||||
'Nette\\' => array($vendorDir . '/nette/schema/src', $vendorDir . '/nette/utils/src'),
|
||||
'League\\Config\\' => array($vendorDir . '/league/config/src'),
|
||||
'League\\CommonMark\\' => array($vendorDir . '/league/commonmark/src'),
|
||||
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
|
||||
|
|
|
|||
10
vendor/composer/autoload_real.php
vendored
10
vendor/composer/autoload_real.php
vendored
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit216e6d5522dd071bc42c5f7c15b28cdc
|
||||
class ComposerAutoloaderInit062b6bfebe9519df3bcac54aafa83aec
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
|
|
@ -24,16 +24,16 @@ class ComposerAutoloaderInit216e6d5522dd071bc42c5f7c15b28cdc
|
|||
|
||||
require __DIR__ . '/platform_check.php';
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit216e6d5522dd071bc42c5f7c15b28cdc', 'loadClassLoader'), true, true);
|
||||
spl_autoload_register(array('ComposerAutoloaderInit062b6bfebe9519df3bcac54aafa83aec', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit216e6d5522dd071bc42c5f7c15b28cdc', 'loadClassLoader'));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit062b6bfebe9519df3bcac54aafa83aec', 'loadClassLoader'));
|
||||
|
||||
require __DIR__ . '/autoload_static.php';
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc::getInitializer($loader));
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit062b6bfebe9519df3bcac54aafa83aec::getInitializer($loader));
|
||||
|
||||
$loader->register(true);
|
||||
|
||||
$filesToLoad = \Composer\Autoload\ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc::$files;
|
||||
$filesToLoad = \Composer\Autoload\ComposerStaticInit062b6bfebe9519df3bcac54aafa83aec::$files;
|
||||
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
|
||||
|
|
|
|||
45
vendor/composer/autoload_static.php
vendored
45
vendor/composer/autoload_static.php
vendored
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc
|
||||
class ComposerStaticInit062b6bfebe9519df3bcac54aafa83aec
|
||||
{
|
||||
public static $files = array (
|
||||
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
|
||||
|
|
@ -12,60 +12,52 @@ class ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc
|
|||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'S' =>
|
||||
'S' =>
|
||||
array (
|
||||
'Symfony\\Polyfill\\Php80\\' => 23,
|
||||
),
|
||||
'R' =>
|
||||
array (
|
||||
'RobotsTxt\\DocumentationMarkdown\\' => 32,
|
||||
),
|
||||
'P' =>
|
||||
'P' =>
|
||||
array (
|
||||
'Psr\\EventDispatcher\\' => 20,
|
||||
),
|
||||
'N' =>
|
||||
'N' =>
|
||||
array (
|
||||
'Nette\\' => 6,
|
||||
),
|
||||
'L' =>
|
||||
'L' =>
|
||||
array (
|
||||
'League\\Config\\' => 14,
|
||||
'League\\CommonMark\\' => 18,
|
||||
),
|
||||
'D' =>
|
||||
'D' =>
|
||||
array (
|
||||
'Dflydev\\DotAccessData\\' => 22,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'Symfony\\Polyfill\\Php80\\' =>
|
||||
'Symfony\\Polyfill\\Php80\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
|
||||
),
|
||||
'RobotsTxt\\DocumentationMarkdown\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/includes',
|
||||
),
|
||||
'Psr\\EventDispatcher\\' =>
|
||||
'Psr\\EventDispatcher\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/event-dispatcher/src',
|
||||
),
|
||||
'Nette\\' =>
|
||||
'Nette\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/nette/utils/src',
|
||||
1 => __DIR__ . '/..' . '/nette/schema/src',
|
||||
0 => __DIR__ . '/..' . '/nette/schema/src',
|
||||
1 => __DIR__ . '/..' . '/nette/utils/src',
|
||||
),
|
||||
'League\\Config\\' =>
|
||||
'League\\Config\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/league/config/src',
|
||||
),
|
||||
'League\\CommonMark\\' =>
|
||||
'League\\CommonMark\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/league/commonmark/src',
|
||||
),
|
||||
'Dflydev\\DotAccessData\\' =>
|
||||
'Dflydev\\DotAccessData\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/dflydev/dot-access-data/src',
|
||||
),
|
||||
|
|
@ -443,6 +435,9 @@ class ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc
|
|||
'Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php',
|
||||
'Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php',
|
||||
'Nette\\Utils\\Process' => __DIR__ . '/..' . '/nette/utils/src/Utils/Process.php',
|
||||
'Nette\\Utils\\ProcessFailedException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\ProcessTimeoutException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php',
|
||||
'Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php',
|
||||
'Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php',
|
||||
'Nette\\Utils\\ReflectionMethod' => __DIR__ . '/..' . '/nette/utils/src/Utils/ReflectionMethod.php',
|
||||
|
|
@ -465,9 +460,9 @@ class ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc
|
|||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit216e6d5522dd071bc42c5f7c15b28cdc::$classMap;
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit062b6bfebe9519df3bcac54aafa83aec::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit062b6bfebe9519df3bcac54aafa83aec::$prefixDirsPsr4;
|
||||
$loader->classMap = ComposerStaticInit062b6bfebe9519df3bcac54aafa83aec::$classMap;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
|
|
|
|||
72
vendor/composer/installed.json
vendored
72
vendor/composer/installed.json
vendored
|
|
@ -275,17 +275,17 @@
|
|||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
"version": "v1.3.3",
|
||||
"version_normalized": "1.3.3.0",
|
||||
"version": "v1.3.5",
|
||||
"version_normalized": "1.3.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/schema.git",
|
||||
"reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004"
|
||||
"reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004",
|
||||
"reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004",
|
||||
"url": "https://api.github.com/repos/nette/schema/zipball/f0ab1a3cda782dbc5da270d28545236aa80c4002",
|
||||
"reference": "f0ab1a3cda782dbc5da270d28545236aa80c4002",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -293,11 +293,13 @@
|
|||
"php": "8.1 - 8.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.5.2",
|
||||
"phpstan/phpstan-nette": "^2.0@stable",
|
||||
"nette/phpstan-rules": "^1.0",
|
||||
"nette/tester": "^2.6",
|
||||
"phpstan/extension-installer": "^1.4@stable",
|
||||
"phpstan/phpstan": "^2.1.39@stable",
|
||||
"tracy/tracy": "^2.8"
|
||||
},
|
||||
"time": "2025-10-30T22:57:59+00:00",
|
||||
"time": "2026-02-23T03:47:12+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
|
@ -337,23 +339,23 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/schema/issues",
|
||||
"source": "https://github.com/nette/schema/tree/v1.3.3"
|
||||
"source": "https://github.com/nette/schema/tree/v1.3.5"
|
||||
},
|
||||
"install-path": "../nette/schema"
|
||||
},
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"version": "v4.1.1",
|
||||
"version_normalized": "4.1.1.0",
|
||||
"version": "v4.1.4",
|
||||
"version_normalized": "4.1.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/utils.git",
|
||||
"reference": "c99059c0315591f1a0db7ad6002000288ab8dc72"
|
||||
"reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/c99059c0315591f1a0db7ad6002000288ab8dc72",
|
||||
"reference": "c99059c0315591f1a0db7ad6002000288ab8dc72",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/7da6c396d7ebe142bc857c20479d5e70a5e1aac7",
|
||||
"reference": "7da6c396d7ebe142bc857c20479d5e70a5e1aac7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -365,8 +367,10 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"jetbrains/phpstorm-attributes": "^1.2",
|
||||
"nette/phpstan-rules": "^1.0",
|
||||
"nette/tester": "^2.5",
|
||||
"phpstan/phpstan-nette": "^2.0@stable",
|
||||
"phpstan/extension-installer": "^1.4@stable",
|
||||
"phpstan/phpstan": "^2.1@stable",
|
||||
"tracy/tracy": "^2.9"
|
||||
},
|
||||
"suggest": {
|
||||
|
|
@ -377,7 +381,7 @@
|
|||
"ext-mbstring": "to use Strings::lower() etc...",
|
||||
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
|
||||
},
|
||||
"time": "2025-12-22T12:14:32+00:00",
|
||||
"time": "2026-05-11T20:49:54+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
|
|
@ -429,7 +433,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/utils/issues",
|
||||
"source": "https://github.com/nette/utils/tree/v4.1.1"
|
||||
"source": "https://github.com/nette/utils/tree/v4.1.4"
|
||||
},
|
||||
"install-path": "../nette/utils"
|
||||
},
|
||||
|
|
@ -488,23 +492,23 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v3.6.0",
|
||||
"version_normalized": "3.6.0.0",
|
||||
"version": "v3.7.0",
|
||||
"version_normalized": "3.7.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62"
|
||||
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62",
|
||||
"reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b",
|
||||
"reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"time": "2024-09-25T14:21:43+00:00",
|
||||
"time": "2026-04-13T15:52:40+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
|
|
@ -512,7 +516,7 @@
|
|||
"name": "symfony/contracts"
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-main": "3.6-dev"
|
||||
"dev-main": "3.7-dev"
|
||||
}
|
||||
},
|
||||
"installation-source": "dist",
|
||||
|
|
@ -538,7 +542,7 @@
|
|||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0"
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -549,6 +553,10 @@
|
|||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
|
|
@ -558,23 +566,23 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.33.0",
|
||||
"version_normalized": "1.33.0.0",
|
||||
"version": "v1.37.0",
|
||||
"version_normalized": "1.37.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"time": "2025-01-02T08:10:11+00:00",
|
||||
"time": "2026-04-10T16:19:22+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
|
|
@ -621,7 +629,7 @@
|
|||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
|
|||
28
vendor/composer/installed.php
vendored
28
vendor/composer/installed.php
vendored
|
|
@ -1,6 +1,6 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => 'robotstxt/documentation-markdown',
|
||||
'name' => 'robotstxt/robotstxt-documentation-markdown',
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
|
|
@ -38,18 +38,18 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'nette/schema' => array(
|
||||
'pretty_version' => 'v1.3.3',
|
||||
'version' => '1.3.3.0',
|
||||
'reference' => '2befc2f42d7c715fd9d95efc31b1081e5d765004',
|
||||
'pretty_version' => 'v1.3.5',
|
||||
'version' => '1.3.5.0',
|
||||
'reference' => 'f0ab1a3cda782dbc5da270d28545236aa80c4002',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../nette/schema',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'nette/utils' => array(
|
||||
'pretty_version' => 'v4.1.1',
|
||||
'version' => '4.1.1.0',
|
||||
'reference' => 'c99059c0315591f1a0db7ad6002000288ab8dc72',
|
||||
'pretty_version' => 'v4.1.4',
|
||||
'version' => '4.1.4.0',
|
||||
'reference' => '7da6c396d7ebe142bc857c20479d5e70a5e1aac7',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../nette/utils',
|
||||
'aliases' => array(),
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'robotstxt/documentation-markdown' => array(
|
||||
'robotstxt/robotstxt-documentation-markdown' => array(
|
||||
'pretty_version' => '1.0.0+no-version-set',
|
||||
'version' => '1.0.0.0',
|
||||
'reference' => null,
|
||||
|
|
@ -74,18 +74,18 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/deprecation-contracts' => array(
|
||||
'pretty_version' => 'v3.6.0',
|
||||
'version' => '3.6.0.0',
|
||||
'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62',
|
||||
'pretty_version' => 'v3.7.0',
|
||||
'version' => '3.7.0.0',
|
||||
'reference' => '50f59d1f3ca46d41ac911f97a78626b6756af35b',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-php80' => array(
|
||||
'pretty_version' => 'v1.33.0',
|
||||
'version' => '1.33.0.0',
|
||||
'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608',
|
||||
'pretty_version' => 'v1.37.0',
|
||||
'version' => '1.37.0.0',
|
||||
'reference' => 'dfb55726c3a76ea3b6459fcfda1ec2d80a682411',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
|
||||
'aliases' => array(),
|
||||
|
|
|
|||
5
vendor/composer/platform_check.php
vendored
5
vendor/composer/platform_check.php
vendored
|
|
@ -19,8 +19,7 @@ if ($issues) {
|
|||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
throw new \RuntimeException(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
74
vendor/dflydev/dot-access-data/CHANGELOG.md
vendored
Normal file
74
vendor/dflydev/dot-access-data/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.0.3] - 2024-07-08
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed PHP 8.4 deprecation notices (#47)
|
||||
|
||||
## [3.0.2] - 2022-10-27
|
||||
|
||||
### Fixed
|
||||
|
||||
- Added missing return types to docblocks (#44, #45)
|
||||
|
||||
## [3.0.1] - 2021-08-13
|
||||
|
||||
### Added
|
||||
|
||||
- Adds ReturnTypeWillChange to suppress PHP 8.1 warnings (#40)
|
||||
|
||||
## [3.0.0] - 2021-01-01
|
||||
|
||||
### Added
|
||||
- Added support for both `.` and `/`-delimited key paths (#24)
|
||||
- Added parameter and return types to everything; enabled strict type checks (#18)
|
||||
- Added new exception classes to better identify certain types of errors (#20)
|
||||
- `Data` now implements `ArrayAccess` (#17)
|
||||
- Added ability to merge non-associative array values (#31, #32)
|
||||
|
||||
### Changed
|
||||
- All thrown exceptions are now instances or subclasses of `DataException` (#20)
|
||||
- Calling `get()` on a missing key path without providing a default will throw a `MissingPathException` instead of returning `null` (#29)
|
||||
- Bumped supported PHP versions to 7.1 - 8.x (#18)
|
||||
|
||||
### Fixed
|
||||
- Fixed incorrect merging of array values into string values (#32)
|
||||
- Fixed `get()` method behaving as if keys with `null` values didn't exist
|
||||
|
||||
## [2.0.0] - 2017-12-21
|
||||
|
||||
### Changed
|
||||
- Bumped supported PHP versions to 7.0 - 7.4 (#12)
|
||||
- Switched to PSR-4 autoloading
|
||||
|
||||
## [1.1.0] - 2017-01-20
|
||||
|
||||
### Added
|
||||
- Added new `has()` method to check for the existence of the given key (#4, #7)
|
||||
|
||||
## [1.0.1] - 2015-08-12
|
||||
|
||||
### Added
|
||||
- Added new optional `$default` parameter to the `get()` method (#2)
|
||||
|
||||
## [1.0.0] - 2012-07-17
|
||||
|
||||
**Initial release!**
|
||||
|
||||
[Unreleased]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.3...main
|
||||
[3.0.3]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.2...v3.0.3
|
||||
[3.0.2]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.1...v3.0.2
|
||||
[3.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.0...v3.0.1
|
||||
[3.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v2.0.0...v3.0.0
|
||||
[2.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.1.0...v2.0.0
|
||||
[1.1.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.1...v1.1.0
|
||||
[1.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.0...v1.0.1
|
||||
[1.0.0]: https://github.com/dflydev/dflydev-dot-access-data/releases/tag/v1.0.0
|
||||
158
vendor/dflydev/dot-access-data/README.md
vendored
Normal file
158
vendor/dflydev/dot-access-data/README.md
vendored
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
Dot Access Data
|
||||
===============
|
||||
|
||||
[](https://packagist.org/packages/dflydev/dot-access-data)
|
||||
[](https://packagist.org/packages/dflydev/dot-access-data)
|
||||
[](LICENSE)
|
||||
[](https://github.com/dflydev/dflydev-dot-access-data/actions?query=workflow%3ATests+branch%3Amain)
|
||||
[](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data/code-structure/)
|
||||
[](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data)
|
||||
|
||||
Given a deep data structure, access data by dot notation.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
* PHP (7.1+)
|
||||
|
||||
> For PHP (5.3+) please refer to version `1.0`.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Abstract example:
|
||||
|
||||
```php
|
||||
use Dflydev\DotAccessData\Data;
|
||||
|
||||
$data = new Data;
|
||||
|
||||
$data->set('a.b.c', 'C');
|
||||
$data->set('a.b.d', 'D1');
|
||||
$data->append('a.b.d', 'D2');
|
||||
$data->set('a.b.e', ['E0', 'E1', 'E2']);
|
||||
|
||||
// C
|
||||
$data->get('a.b.c');
|
||||
|
||||
// ['D1', 'D2']
|
||||
$data->get('a.b.d');
|
||||
|
||||
// ['E0', 'E1', 'E2']
|
||||
$data->get('a.b.e');
|
||||
|
||||
// true
|
||||
$data->has('a.b.c');
|
||||
|
||||
// false
|
||||
$data->has('a.b.d.j');
|
||||
|
||||
|
||||
// 'some-default-value'
|
||||
$data->get('some.path.that.does.not.exist', 'some-default-value');
|
||||
|
||||
// throws a MissingPathException because no default was given
|
||||
$data->get('some.path.that.does.not.exist');
|
||||
```
|
||||
|
||||
A more concrete example:
|
||||
|
||||
```php
|
||||
use Dflydev\DotAccessData\Data;
|
||||
|
||||
$data = new Data([
|
||||
'hosts' => [
|
||||
'hewey' => [
|
||||
'username' => 'hman',
|
||||
'password' => 'HPASS',
|
||||
'roles' => ['web'],
|
||||
],
|
||||
'dewey' => [
|
||||
'username' => 'dman',
|
||||
'password' => 'D---S',
|
||||
'roles' => ['web', 'db'],
|
||||
'nick' => 'dewey dman',
|
||||
],
|
||||
'lewey' => [
|
||||
'username' => 'lman',
|
||||
'password' => 'LP@$$',
|
||||
'roles' => ['db'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// hman
|
||||
$username = $data->get('hosts.hewey.username');
|
||||
// HPASS
|
||||
$password = $data->get('hosts.hewey.password');
|
||||
// ['web']
|
||||
$roles = $data->get('hosts.hewey.roles');
|
||||
// dewey dman
|
||||
$nick = $data->get('hosts.dewey.nick');
|
||||
// Unknown
|
||||
$nick = $data->get('hosts.lewey.nick', 'Unknown');
|
||||
|
||||
// DataInterface instance
|
||||
$dewey = $data->getData('hosts.dewey');
|
||||
// dman
|
||||
$username = $dewey->get('username');
|
||||
// D---S
|
||||
$password = $dewey->get('password');
|
||||
// ['web', 'db']
|
||||
$roles = $dewey->get('roles');
|
||||
|
||||
// No more lewey
|
||||
$data->remove('hosts.lewey');
|
||||
|
||||
// Add DB to hewey's roles
|
||||
$data->append('hosts.hewey.roles', 'db');
|
||||
|
||||
$data->set('hosts.april', [
|
||||
'username' => 'aman',
|
||||
'password' => '@---S',
|
||||
'roles' => ['web'],
|
||||
]);
|
||||
|
||||
// Check if a key exists (true to this case)
|
||||
$hasKey = $data->has('hosts.dewey.username');
|
||||
```
|
||||
|
||||
`Data` may be used as an array, since it implements `ArrayAccess` interface:
|
||||
|
||||
```php
|
||||
// Get
|
||||
$data->get('name') === $data['name']; // true
|
||||
|
||||
$data['name'] = 'Dewey';
|
||||
// is equivalent to
|
||||
$data->set($name, 'Dewey');
|
||||
|
||||
isset($data['name']) === $data->has('name');
|
||||
|
||||
// Remove key
|
||||
unset($data['name']);
|
||||
```
|
||||
|
||||
`/` can also be used as a path delimiter:
|
||||
|
||||
```php
|
||||
$data->set('a/b/c', 'd');
|
||||
echo $data->get('a/b/c'); // "d"
|
||||
|
||||
$data->get('a/b/c') === $data->get('a.b.c'); // true
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
This library is licensed under the MIT License - see the LICENSE file
|
||||
for details.
|
||||
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
If you have questions or want to help out, join us in the
|
||||
[#dflydev](irc://irc.freenode.net/#dflydev) channel on irc.freenode.net.
|
||||
67
vendor/dflydev/dot-access-data/composer.json
vendored
Normal file
67
vendor/dflydev/dot-access-data/composer.json
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
"type": "library",
|
||||
"description": "Given a deep data structure, access data by dot notation.",
|
||||
"homepage": "https://github.com/dflydev/dflydev-dot-access-data",
|
||||
"keywords": ["dot", "access", "data", "notation"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Dragonfly Development Inc.",
|
||||
"email": "info@dflydev.com",
|
||||
"homepage": "http://dflydev.com"
|
||||
},
|
||||
{
|
||||
"name": "Beau Simensen",
|
||||
"email": "beau@dflydev.com",
|
||||
"homepage": "http://beausimensen.com"
|
||||
},
|
||||
{
|
||||
"name": "Carlos Frutos",
|
||||
"email": "carlos@kiwing.it",
|
||||
"homepage": "https://github.com/cfrutos"
|
||||
},
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.42",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
|
||||
"scrutinizer/ocular": "1.6.0",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"vimeo/psalm": "^4.0.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dflydev\\DotAccessData\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Dflydev\\DotAccessData\\": "tests/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"phpcs": "phpcs",
|
||||
"phpstan": "phpstan analyse",
|
||||
"phpunit": "phpunit --no-coverage",
|
||||
"psalm": "psalm",
|
||||
"test": [
|
||||
"@phpcs",
|
||||
"@phpstan",
|
||||
"@psalm",
|
||||
"@phpunit"
|
||||
]
|
||||
}
|
||||
}
|
||||
782
vendor/league/commonmark/CHANGELOG.md
vendored
Normal file
782
vendor/league/commonmark/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,782 @@
|
|||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
|
||||
|
||||
**Upgrading from 1.x?** See <https://commonmark.thephpleague.com/2.0/upgrading/> for additional information.
|
||||
|
||||
## [Unreleased][unreleased]
|
||||
|
||||
## [2.8.2] - 2026-03-19
|
||||
|
||||
This is a **security release** to address an issue where the `allowed_domains` setting for the `Embed` extension can be bypassed, resulting in a possible SSRF and XSS vulnerabilities.
|
||||
|
||||
### Fixed
|
||||
- Fixed `DomainFilteringAdapter` hostname boundary bypass where domains like `youtube.com.evil` could match an allowlist entry for `youtube.com` (GHSA-hh8v-hgvp-g3f5)
|
||||
|
||||
## [2.8.1] - 2026-03-05
|
||||
|
||||
This is a **security release** to address an issue where `DisallowedRawHtml` can be bypassed, resulting in a possible cross-site scripting (XSS) vulnerability.
|
||||
|
||||
### Fixed
|
||||
- Fixed `DisallowedRawHtmlRenderer` not blocking raw HTML tags with trailing ASCII whitespace (GHSA-4v6x-c7xx-hw9f)
|
||||
- Fixed PHP 8.5 deprecation (#1107)
|
||||
|
||||
## [2.8.0] - 2025-11-26
|
||||
|
||||
### Added
|
||||
- Added a new `HighlightExtension` for marking important text using `==` syntax (#1100)
|
||||
|
||||
### Fixed
|
||||
- Fixed `AutolinkExtension` incorrectly matching URLs after invalid `www.` prefix (#1095, #1103)
|
||||
|
||||
## [2.7.1] - 2025-07-20
|
||||
|
||||
### Changed
|
||||
- Optimized several regular expressions in `RegexHelper` to improve performance (#674, #1086)
|
||||
|
||||
### Fixed
|
||||
- `EmbedProcessor` no longer calls `updateEmbeds()` when there are no embeds to update (#1081)
|
||||
- Fixed missing `benchmark.php` CSV path validation for non-existent files (#1068, #1085)
|
||||
|
||||
## [2.7.0] - 2025-05-05
|
||||
|
||||
This is a **security release** to address a potential cross-site scripting (XSS) vulnerability when using the `AttributesExtension` with untrusted user input.
|
||||
|
||||
### Added
|
||||
- Added `attributes/allow` config option to specify which attributes users are allowed to set on elements (default allows virtually all attributes)
|
||||
|
||||
### Changed
|
||||
- The `AttributesExtension` blocks all attributes starting with `on` unless explicitly allowed via the `attributes/allow` config option
|
||||
- The `allow_unsafe_links` option is now respected by the `AttributesExtension` when users specify `href` and `src` attributes
|
||||
|
||||
## [2.6.2] - 2025-04-18
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Attributes extension parsing regression (#1071)
|
||||
|
||||
## [2.6.1] - 2024-12-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Rendered list items should only add newlines around block-level children (#1059, #1061)
|
||||
|
||||
## [2.6.0] - 2024-12-07
|
||||
|
||||
This is a **security release** to address potential denial of service attacks when parsing specially crafted,
|
||||
malicious input from untrusted sources (like user input).
|
||||
|
||||
### Added
|
||||
|
||||
- Added `max_delimiters_per_line` config option to prevent denial of service attacks when parsing malicious input
|
||||
- Added `table/max_autocompleted_cells` config option to prevent denial of service attacks when parsing large tables
|
||||
- The `AttributesExtension` now supports attributes without values (#985, #986)
|
||||
- The `AutolinkExtension` exposes two new configuration options to override the default behavior (#969, #987):
|
||||
- `autolink/allowed_protocols` - an array of protocols to allow autolinking for
|
||||
- `autolink/default_protocol` - the default protocol to use when none is specified
|
||||
- Added `RegexHelper::isWhitespace()` method to check if a given character is an ASCII whitespace character
|
||||
- Added `CacheableDelimiterProcessorInterface` to ensure linear complexity for dynamic delimiter processing
|
||||
- Added `Bracket` delimiter type to optimize bracket parsing
|
||||
|
||||
### Changed
|
||||
|
||||
- `[` and `]` are no longer added as `Delimiter` objects on the stack; a new `Bracket` type with its own stack is used instead
|
||||
- `UrlAutolinkParser` no longer parses URLs with more than 127 subdomains
|
||||
- Expanded reference links can no longer exceed 100kb, or the size of the input document (whichever is greater)
|
||||
- Delimiters should always provide a non-null value via `DelimiterInterface::getIndex()`
|
||||
- We'll attempt to infer the index based on surrounding delimiters where possible
|
||||
- The `DelimiterStack` now accepts integer positions for any `$stackBottom` argument
|
||||
- Several small performance optimizations
|
||||
|
||||
## [2.5.3] - 2024-08-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Made compatible with CommonMark spec 0.31.1, including:
|
||||
- Remove `source`, add `search` to list of recognized block tags
|
||||
|
||||
## [2.5.2] - 2024-08-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Boolean attributes now require an explicit `true` value (#1040)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed regression where text could be misinterpreted as an attribute (#1040)
|
||||
|
||||
## [2.5.1] - 2024-07-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035)
|
||||
- Fixed incorrect `Table` start line numbers (#1037)
|
||||
|
||||
## [2.5.0] - 2024-07-22
|
||||
|
||||
### Added
|
||||
|
||||
- The `AttributesExtension` now supports attributes without values (#985, #986)
|
||||
- The `AutolinkExtension` exposes two new configuration options to override the default behavior (#969, #987):
|
||||
- `autolink/allowed_protocols` - an array of protocols to allow autolinking for
|
||||
- `autolink/default_protocol` - the default protocol to use when none is specified
|
||||
|
||||
### Changed
|
||||
|
||||
- Made compatible with CommonMark spec 0.31.0, including:
|
||||
- Allow closing fence to be followed by tabs
|
||||
- Remove restrictive limitation on inline comments
|
||||
- Unicode symbols now treated like punctuation (for purposes of flankingness)
|
||||
- Trailing tabs on the last line of indented code blocks will be excluded
|
||||
- Improved HTML comment matching
|
||||
- `Paragraph`s only containing link reference definitions will be kept in the AST until the `Document` is finalized
|
||||
- (These were previously removed immediately after parsing the `Paragraph`)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed list tightness not being determined properly in some edge cases
|
||||
- Fixed incorrect ending line numbers for several block types in various scenarios
|
||||
- Fixed lowercase inline HTML declarations not being accepted
|
||||
|
||||
## [2.4.4] - 2024-07-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed SmartPunct extension changing already-formatted quotation marks (#1030)
|
||||
|
||||
## [2.4.3] - 2024-07-22
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the Attributes extension not supporting CSS level 3 selectors (#1013)
|
||||
- Fixed `UrlAutolinkParser` incorrectly parsing text containing `www` anywhere before an autolink (#1025)
|
||||
|
||||
|
||||
## [2.4.2] - 2024-02-02
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed declaration parser being too strict
|
||||
- `FencedCodeRenderer`: don't add `language-` to class if already prefixed
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Returning dynamic values from `DelimiterProcessorInterface::getDelimiterUse()` is deprecated
|
||||
- You should instead implement `CacheableDelimiterProcessorInterface` to help the engine perform caching to avoid performance issues.
|
||||
- Failing to set a delimiter's index (or returning `null` from `DelimiterInterface::getIndex()`) is deprecated and will not be supported in 3.0
|
||||
- Deprecated `DelimiterInterface::isActive()` and `DelimiterInterface::setActive()`, as these are no longer used by the engine
|
||||
- Deprecated `DelimiterStack::removeEarlierMatches()` and `DelimiterStack::searchByCharacter()`, as these are no longer used by the engine
|
||||
- Passing a `DelimiterInterface` as the `$stackBottom` argument to `DelimiterStack::processDelimiters()` or `::removeAll()` is deprecated and will not be supported in 3.0; pass the integer position instead.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed NUL characters not being replaced in the input
|
||||
- Fixed quadratic complexity parsing unclosed inline links
|
||||
- Fixed quadratic complexity parsing emphasis and strikethrough delimiters
|
||||
- Fixed issue where having 500,000+ delimiters could trigger a [known segmentation fault issue in PHP's garbage collection](https://bugs.php.net/bug.php?id=68606)
|
||||
- Fixed quadratic complexity deactivating link openers
|
||||
- Fixed quadratic complexity parsing long backtick code spans with no matching closers
|
||||
- Fixed catastrophic backtracking when parsing link labels/titles
|
||||
|
||||
## [2.4.1] - 2023-08-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `ExternalLinkProcessor` not fully disabling the `rel` attribute when configured to do so (#992)
|
||||
|
||||
## [2.4.0] - 2023-03-24
|
||||
|
||||
### Added
|
||||
|
||||
- Added generic `CommonMarkException` marker interface for all exceptions thrown by the library
|
||||
- Added several new specific exception types implementing that marker interface:
|
||||
- `AlreadyInitializedException`
|
||||
- `InvalidArgumentException`
|
||||
- `IOException`
|
||||
- `LogicException`
|
||||
- `MissingDependencyException`
|
||||
- `NoMatchingRendererException`
|
||||
- `ParserLogicException`
|
||||
- Added more configuration options to the Heading Permalinks extension (#939):
|
||||
- `heading_permalink/apply_id_to_heading` - When `true`, the `id` attribute will be applied to the heading element itself instead of the `<a>` tag
|
||||
- `heading_permalink/heading_class` - class to apply to the heading element
|
||||
- `heading_permalink/insert` - now accepts `none` to prevent the creation of the `<a>` link
|
||||
- Added new `table/alignment_attributes` configuration option to control how table cell alignment is rendered (#959)
|
||||
|
||||
### Changed
|
||||
|
||||
- Change several thrown exceptions from `RuntimeException` to `LogicException` (or something extending it), including:
|
||||
- `CallbackGenerator`s that fail to set a URL or return an expected value
|
||||
- `MarkdownParser` when deactivating the last block parser or attempting to get an active block parser when they've all been closed
|
||||
- Adding items to an already-initialized `Environment`
|
||||
- Rendering a `Node` when no renderer has been registered for it
|
||||
- `HeadingPermalinkProcessor` now throws `InvalidConfigurationException` instead of `RuntimeException` when invalid config values are given.
|
||||
- `HtmlElement::setAttribute()` no longer requires the second parameter for boolean attributes
|
||||
- Several small micro-optimizations
|
||||
- Changed Strikethrough to only allow 1 or 2 tildes per the updated GFM spec
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed inaccurate `@throws` docblocks throughout the codebase, including `ConverterInterface`, `MarkdownConverter`, and `MarkdownConverterInterface`.
|
||||
- These previously suggested that only `\RuntimeException`s were thrown, which was inaccurate as `\LogicException`s were also possible.
|
||||
|
||||
## [2.3.9] - 2023-02-15
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed autolink extension not detecting some URIs with underscores (#956)
|
||||
|
||||
## [2.3.8] - 2022-12-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed parsing issues when `mb_internal_encoding()` is set to something other than `UTF-8` (#951)
|
||||
|
||||
## [2.3.7] - 2022-11-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `TaskListItemMarkerRenderer` not including HTML attributes set on the node by other extensions (#947)
|
||||
|
||||
## [2.3.6] - 2022-10-30
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed unquoted attribute parsing when closing curly brace is followed by certain characters (like a `.`) (#943)
|
||||
|
||||
## [2.3.5] - 2022-07-29
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed error using `InlineParserEngine` when no inline parsers are registered in the `Environment` (#908)
|
||||
|
||||
## [2.3.4] - 2022-07-17
|
||||
|
||||
### Changed
|
||||
|
||||
- Made a number of small tweaks to the embed extension's parsing behavior to fix #898:
|
||||
- Changed `EmbedStartParser` to always capture embed-like lines in container blocks, regardless of parent block type
|
||||
- Changed `EmbedProcessor` to also remove `Embed` blocks that aren't direct children of the `Document`
|
||||
- Increased the priority of `EmbedProcessor` to `1010`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `EmbedExtension` not parsing embeds following a list block (#898)
|
||||
|
||||
## [2.3.3] - 2022-06-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `DomainFilteringAdapter` not reindexing the embed list (#884, #885)
|
||||
|
||||
## [2.3.2] - 2022-06-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
|
||||
|
||||
## [2.2.5] - 2022-06-03
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881)
|
||||
|
||||
## [2.3.1] - 2022-05-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
|
||||
|
||||
## [2.2.4] - 2022-05-14
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867)
|
||||
|
||||
## [2.3.0] - 2022-04-07
|
||||
|
||||
### Added
|
||||
|
||||
- Added new `EmbedExtension` (#805)
|
||||
- Added `DocumentRendererInterface` as a replacement for the now-deprecated `MarkdownRendererInterface`
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecated `MarkdownRendererInterface`; use `DocumentRendererInterface` instead
|
||||
|
||||
## [2.2.3] - 2022-02-26
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed front matter parsing with Windows line endings (#821)
|
||||
|
||||
## [2.1.3] - 2022-02-26
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed front matter parsing with Windows line endings (#821)
|
||||
|
||||
## [2.0.4] - 2022-02-26
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed front matter parsing with Windows line endings (#821)
|
||||
|
||||
## [2.2.2] - 2022-02-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed double-escaping of image alt text (#806, #810)
|
||||
- Fixed Psalm typehints for event class names
|
||||
|
||||
## [2.2.1] - 2022-01-25
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `symfony/deprecation-contracts` constraint
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed deprecation trigger from `MarkdownConverterInterface` to reduce noise
|
||||
|
||||
## [2.2.0] - 2022-01-22
|
||||
|
||||
### Added
|
||||
|
||||
- Added new `ConverterInterface`
|
||||
- Added new `MarkdownToXmlConverter` class
|
||||
- Added new `HtmlDecorator` class which can wrap existing renderers with additional HTML tags
|
||||
- Added new `table/wrap` config to apply an optional wrapping/container element around a table (#780)
|
||||
|
||||
### Changed
|
||||
|
||||
- `HtmlElement` contents can now consist of any `Stringable`, not just `HtmlElement` and `string`
|
||||
|
||||
### Deprecated
|
||||
|
||||
- Deprecated `MarkdownConverterInterface` and its `convertToHtml()` method; use `ConverterInterface` and `convert()` instead
|
||||
|
||||
## [2.1.2] - 2022-02-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed double-escaping of image alt text (#806, #810)
|
||||
- Fixed Psalm typehints for event class names
|
||||
|
||||
## [2.1.1] - 2022-01-02
|
||||
|
||||
### Added
|
||||
|
||||
- Added missing return type to `Environment::dispatch()` to fix deprecation warning (#778)
|
||||
|
||||
## [2.1.0] - 2021-12-05
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for ext-yaml in FrontMatterExtension (#715)
|
||||
- Added support for symfony/yaml v6.0 in FrontMatterExtension (#739)
|
||||
- Added new `heading_permalink/aria_hidden` config option (#741)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed PHP 8.1 deprecation warning (#759, #762)
|
||||
|
||||
## [2.0.3] - 2022-02-13
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed double-escaping of image alt text (#806, #810)
|
||||
- Fixed Psalm typehints for event class names
|
||||
|
||||
## [2.0.2] - 2021-08-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped minimum version of league/config to support PHP 8.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed ability to register block parsers that identify lines starting with letters (#706)
|
||||
|
||||
## [2.0.1] - 2021-07-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed nested autolinks (#689)
|
||||
- Fixed description lists being parsed incorrectly (#692)
|
||||
- Fixed Table of Contents not respecting Heading Permalink prefixes (#690)
|
||||
|
||||
## [2.0.0] - 2021-07-24
|
||||
|
||||
No changes were introduced since the previous RC2 release.
|
||||
See all entries below for a list of changes between 1.x and 2.0.
|
||||
|
||||
## [2.0.0-rc2] - 2021-07-17
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Mentions inside of links creating nested links against the spec's rules (#688)
|
||||
|
||||
## [2.0.0-rc1] - 2021-07-10
|
||||
|
||||
No changes were introduced since the previous release.
|
||||
|
||||
## [2.0.0-beta3] - 2021-07-03
|
||||
|
||||
### Changed
|
||||
|
||||
- Any leading UTF-8 BOM will be stripped from the input
|
||||
- The `getEnvironment()` method of `CommonMarkConverter` and `GithubFlavoredMarkdownConverter` will always return the concrete, configurable `Environment` for upgrading convenience
|
||||
- Optimized AST iteration
|
||||
- Lots of small micro-optimizations
|
||||
|
||||
## [2.0.0-beta2] - 2021-06-27
|
||||
|
||||
### Added
|
||||
|
||||
- Added new `Node::iterator()` method and `NodeIterator` class for faster AST iteration (#683, #684)
|
||||
|
||||
### Changed
|
||||
|
||||
- Made compatible with CommonMark spec 0.30.0
|
||||
- Optimized link label parsing
|
||||
- Optimized AST iteration for a 50% performance boost in some event listeners (#683, #684)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed processing instructions with EOLs
|
||||
- Fixed case-insensitive matching for HTML tag types
|
||||
- Fixed type 7 HTML blocks incorrectly interrupting lazy paragraphs
|
||||
- Fixed newlines in reference labels not collapsing into spaces
|
||||
- Fixed link label normalization with escaped newlines
|
||||
- Fixed unnecessary AST iteration when no default attributes are configured
|
||||
|
||||
## [2.0.0-beta1] - 2021-06-20
|
||||
|
||||
### Added
|
||||
|
||||
- **Added three new extensions:**
|
||||
- `FrontMatterExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/front-matter/))
|
||||
- `DescriptionListExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/description-lists/))
|
||||
- `DefaultAttributesExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/default-attributes/))
|
||||
- **Added new `XmlRenderer` to simplify AST debugging** ([see documentation](https://commonmark.thephpleague.com/xml/)) (#431)
|
||||
- **Added the ability to configure disallowed raw HTML tags** (#507)
|
||||
- **Added the ability for Mentions to use multiple characters for their symbol** (#514, #550)
|
||||
- **Added the ability to delegate event dispatching to PSR-14 compliant event dispatcher libraries**
|
||||
- **Added new configuration options:**
|
||||
- Added `heading_permalink/min_heading_level` and `heading_permalink/max_heading_level` options to control which headings get permalinks (#519)
|
||||
- Added `heading_permalink/fragment_prefix` to allow customizing the URL fragment prefix (#602)
|
||||
- Added `footnote/backref_symbol` option for customizing backreference link appearance (#522)
|
||||
- Added `slug_normalizer/max_length` option to control the maximum length of generated URL slugs
|
||||
- Added `slug_normalizer/unique` option to control whether unique slugs should be generated per-document or per-environment
|
||||
- **Added purity markers throughout the codebase** (verified with Psalm)
|
||||
- Added `Query` class to simplify Node traversal when looking to take action on certain Nodes
|
||||
- Added new `HtmlFilter` and `StringContainerHelper` utility classes
|
||||
- Added new `AbstractBlockContinueParser` class to simplify the creation of custom block parsers
|
||||
- Added several new classes and interfaces:
|
||||
- `BlockContinue`
|
||||
- `BlockContinueParserInterface`
|
||||
- `BlockContinueParserWithInlinesInterface`
|
||||
- `BlockStart`
|
||||
- `BlockStartParserInterface`
|
||||
- `ChildNodeRendererInterface`
|
||||
- `ConfigurableExtensionInterface`
|
||||
- `CursorState`
|
||||
- `DashParser` (extracted from `PunctuationParser`)
|
||||
- `DelimiterParser`
|
||||
- `DocumentBlockParser`
|
||||
- `DocumentPreRenderEvent`
|
||||
- `DocumentRenderedEvent`
|
||||
- `EllipsesParser` (extracted from `PunctuationParser`)
|
||||
- `ExpressionInterface`
|
||||
- `FallbackNodeXmlRenderer`
|
||||
- `InlineParserEngineInterface`
|
||||
- `InlineParserMatch`
|
||||
- `MarkdownParserState`
|
||||
- `MarkdownParserStateInterface`
|
||||
- `MarkdownRendererInterface`
|
||||
- `Query`
|
||||
- `RawMarkupContainerInterface`
|
||||
- `ReferenceableInterface`
|
||||
- `RenderedContent`
|
||||
- `RenderedContentInterface`
|
||||
- `ReplaceUnpairedQuotesListener`
|
||||
- `SpecReader`
|
||||
- `TableOfContentsRenderer`
|
||||
- `UniqueSlugNormalizer`
|
||||
- `UniqueSlugNormalizerInterface`
|
||||
- `XmlRenderer`
|
||||
- `XmlNodeRendererInterface`
|
||||
- Added several new methods:
|
||||
- `Cursor::getCurrentCharacter()`
|
||||
- `Environment::createDefaultConfiguration()`
|
||||
- `Environment::setEventDispatcher()`
|
||||
- `EnvironmentInterface::getExtensions()`
|
||||
- `EnvironmentInterface::getInlineParsers()`
|
||||
- `EnvironmentInterface::getSlugNormalizer()`
|
||||
- `FencedCode::setInfo()`
|
||||
- `Heading::setLevel()`
|
||||
- `HtmlRenderer::renderDocument()`
|
||||
- `InlineParserContext::getFullMatch()`
|
||||
- `InlineParserContext::getFullMatchLength()`
|
||||
- `InlineParserContext::getMatches()`
|
||||
- `InlineParserContext::getSubMatches()`
|
||||
- `LinkParserHelper::parsePartialLinkLabel()`
|
||||
- `LinkParserHelper::parsePartialLinkTitle()`
|
||||
- `Node::assertInstanceOf()`
|
||||
- `RegexHelper::isLetter()`
|
||||
- `StringContainerInterface::setLiteral()`
|
||||
- `TableCell::getType()`
|
||||
- `TableCell::setType()`
|
||||
- `TableCell::getAlign()`
|
||||
- `TableCell::setAlign()`
|
||||
|
||||
### Changed
|
||||
|
||||
- **Changed the converter return type**
|
||||
- `CommonMarkConverter::convertToHtml()` now returns an instance of `RenderedContentInterface`. This can be cast to a string for backward compatibility with 1.x.
|
||||
- **Table of Contents items are no longer wrapped with `<p>` tags** (#613)
|
||||
- **Heading Permalinks now link to element IDs instead of using `name` attributes** (#602)
|
||||
- **Heading Permalink IDs and URL fragments now have a `content` prefix by default** (#602)
|
||||
- **Changes to configuration options:**
|
||||
- `enable_em` has been renamed to `commonmark/enable_em`
|
||||
- `enable_strong` has been renamed to `commonmark/enable_strong`
|
||||
- `use_asterisk` has been renamed to `commonmark/use_asterisk`
|
||||
- `use_underscore` has been renamed to `commonmark/use_underscore`
|
||||
- `unordered_list_markers` has been renamed to `commonmark/unordered_list_markers`
|
||||
- `mentions/*/symbol` has been renamed to `mentions/*/prefix`
|
||||
- `mentions/*/regex` has been renamed to `mentions/*/pattern` and requires partial regular expressions (without delimiters or flags)
|
||||
- `max_nesting_level` now defaults to `PHP_INT_MAX` and no longer supports floats
|
||||
- `heading_permalink/slug_normalizer` has been renamed to `slug_normalizer/instance`
|
||||
- **Event dispatching is now fully PSR-14 compliant**
|
||||
- **Moved and renamed several classes** - [see the full list here](https://commonmark.thephpleague.com/2.0/upgrading/#classesnamespaces-renamed)
|
||||
- The `HeadingPermalinkExtension` and `FootnoteExtension` were modified to ensure they never produce a slug which conflicts with slugs created by the other extension
|
||||
- `SlugNormalizer::normalizer()` now supports optional prefixes and max length options passed in via the `$context` argument
|
||||
- The `AbstractBlock::$data` and `AbstractInline::$data` arrays were replaced with a `Data` array-like object on the base `Node` class
|
||||
- **Implemented a new approach to block parsing.** This was a massive change, so here are the highlights:
|
||||
- Functionality previously found in block parsers and node elements has moved to block parser factories and block parsers, respectively ([more details](https://commonmark.thephpleague.com/2.0/upgrading/#new-block-parsing-approach))
|
||||
- `ConfigurableEnvironmentInterface::addBlockParser()` is now `EnvironmentBuilderInterface::addBlockParserFactory()`
|
||||
- `ReferenceParser` was re-implemented and works completely different than before
|
||||
- The paragraph parser no longer needs to be added manually to the environment
|
||||
- **Implemented a new approach to inline parsing** where parsers can now specify longer strings or regular expressions they want to parse (instead of just single characters):
|
||||
- `InlineParserInterface::getCharacters()` is now `getMatchDefinition()` and returns an instance of `InlineParserMatch`
|
||||
- `InlineParserContext::__construct()` now requires the contents to be provided as a `Cursor` instead of a `string`
|
||||
- **Implemented delimiter parsing as a special type of inline parser** (via the new `DelimiterParser` class)
|
||||
- **Changed block and inline rendering to use common methods and interfaces**
|
||||
- `BlockRendererInterface` and `InlineRendererInterface` were replaced by `NodeRendererInterface` with slightly different parameters. All core renderers now implement this interface.
|
||||
- `ConfigurableEnvironmentInterface::addBlockRenderer()` and `addInlineRenderer()` were combined into `EnvironmentBuilderInterface::addRenderer()`
|
||||
- `EnvironmentInterface::getBlockRenderersForClass()` and `getInlineRenderersForClass()` are now just `getRenderersForClass()`
|
||||
- **Completely refactored the Configuration implementation**
|
||||
- All configuration-specific classes have been moved into a new `league/config` package with a new namespace
|
||||
- `Configuration` objects must now be configured with a schema and all options must match that schema - arbitrary keys are no longer permitted
|
||||
- `Configuration::__construct()` no longer accepts the default configuration values - use `Configuration::merge()` instead
|
||||
- `ConfigurationInterface` now only contains a `get(string $key)`; this method no longer allows arbitrary default values to be returned if the option is missing
|
||||
- `ConfigurableEnvironmentInterface` was renamed to `EnvironmentBuilderInterface`
|
||||
- `ExtensionInterface::register()` now requires an `EnvironmentBuilderInterface` param instead of `ConfigurableEnvironmentInterface`
|
||||
- **Added missing return types to virtually every class and interface method**
|
||||
- Re-implemented the GFM Autolink extension using the new inline parser approach instead of document processors
|
||||
- `EmailAutolinkProcessor` is now `EmailAutolinkParser`
|
||||
- `UrlAutolinkProcessor` is now `UrlAutolinkParser`
|
||||
- `HtmlElement` can now properly handle array (i.e. `class`) and boolean (i.e. `checked`) attribute values
|
||||
- `HtmlElement` automatically flattens any attributes with array values into space-separated strings, removing duplicate entries
|
||||
- Combined separate classes/interfaces into one:
|
||||
- `DisallowedRawHtmlRenderer` replaces `DisallowedRawHtmlBlockRenderer` and `DisallowedRawHtmlInlineRenderer`
|
||||
- `NodeRendererInterface` replaces `BlockRendererInterface` and `InlineRendererInterface`
|
||||
- Renamed the following methods:
|
||||
- `Environment` and `ConfigurableEnvironmentInterface`:
|
||||
- `addBlockParser()` is now `addBlockStartParser()`
|
||||
- `ReferenceMap` and `ReferenceMapInterface`:
|
||||
- `addReference()` is now `add()`
|
||||
- `getReference()` is now `get()`
|
||||
- `listReferences()` is now `getIterator()`
|
||||
- Various node (block/inline) classes:
|
||||
- `getContent()` is now `getLiteral()`
|
||||
- `setContent()` is now `setLiteral()`
|
||||
- Moved and renamed the following constants:
|
||||
- `EnvironmentInterface::HTML_INPUT_ALLOW` is now `HtmlFilter::ALLOW`
|
||||
- `EnvironmentInterface::HTML_INPUT_ESCAPE` is now `HtmlFilter::ESCAPE`
|
||||
- `EnvironmentInterface::HTML_INPUT_STRIP` is now `HtmlFilter::STRIP`
|
||||
- `TableCell::TYPE_HEAD` is now `TableCell::TYPE_HEADER`
|
||||
- `TableCell::TYPE_BODY` is now `TableCell::TYPE_DATA`
|
||||
- Changed the visibility of the following properties:
|
||||
- `AttributesInline::$attributes` is now `private`
|
||||
- `AttributesInline::$block` is now `private`
|
||||
- `TableCell::$align` is now `private`
|
||||
- `TableCell::$type` is now `private`
|
||||
- `TableSection::$type` is now `private`
|
||||
- Several methods which previously returned `$this` now return `void`
|
||||
- `Delimiter::setPrevious()`
|
||||
- `Node::replaceChildren()`
|
||||
- `Context::setTip()`
|
||||
- `Context::setContainer()`
|
||||
- `Context::setBlocksParsed()`
|
||||
- `AbstractStringContainer::setContent()`
|
||||
- `AbstractWebResource::setUrl()`
|
||||
- Several classes are now marked `final`:
|
||||
- `ArrayCollection`
|
||||
- `Emphasis`
|
||||
- `FencedCode`
|
||||
- `Heading`
|
||||
- `HtmlBlock`
|
||||
- `HtmlElement`
|
||||
- `HtmlInline`
|
||||
- `IndentedCode`
|
||||
- `Newline`
|
||||
- `Strikethrough`
|
||||
- `Strong`
|
||||
- `Text`
|
||||
- `Heading` nodes no longer directly contain a copy of their inner text
|
||||
- `StringContainerInterface` can now be used for inlines, not just blocks
|
||||
- `ArrayCollection` only supports integer keys
|
||||
- `HtmlElement` now implements `Stringable`
|
||||
- `Cursor::saveState()` and `Cursor::restoreState()` now use `CursorState` objects instead of arrays
|
||||
- `NodeWalker::next()` now enters, traverses any children, and leaves all elements which may have children (basically all blocks plus any inlines with children). Previously, it only did this for elements explicitly marked as "containers".
|
||||
- `InvalidOptionException` was removed
|
||||
- Anything with a `getReference(): ReferenceInterface` method now implements `ReferencableInterface`
|
||||
- The `SmartPunct` extension now replaces all unpaired `Quote` elements with `Text` elements towards the end of parsing, making the `QuoteRenderer` unnecessary
|
||||
- Several changes made to the Footnote extension:
|
||||
- Footnote identifiers can no longer contain spaces
|
||||
- Anonymous footnotes can now span subsequent lines
|
||||
- Footnotes can now contain multiple lines of content, including sub-blocks, by indenting them
|
||||
- Footnote event listeners now have numbered priorities (but still execute in the same order)
|
||||
- Footnotes must now be separated from previous content by a blank line
|
||||
- The line numbers (keys) returned via `MarkdownInput::getLines()` now start at 1 instead of 0
|
||||
- `DelimiterProcessorCollectionInterface` now extends `Countable`
|
||||
- `RegexHelper::PARTIAL_` constants must always be used in case-insensitive contexts
|
||||
- `HeadingPermalinkProcessor` no longer accepts text normalizers via the constructor - these must be provided via configuration instead
|
||||
- Blocks which can't contain inlines will no longer be asked to render inlines
|
||||
- `AnonymousFootnoteRefParser` and `HeadingPermalinkProcessor` now implement `EnvironmentAwareInterface` instead of `ConfigurationAwareInterface`
|
||||
- The second argument to `TextNormalizerInterface::normalize()` must now be an array
|
||||
- The `title` attribute for `Link` and `Image` nodes is now stored using a dedicated property instead of stashing it in `$data`
|
||||
- `ListData::$delimiter` now returns either `ListBlock::DELIM_PERIOD` or `ListBlock::DELIM_PAREN` instead of the literal delimiter
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Fixed parsing of footnotes without content**
|
||||
- **Fixed rendering of orphaned footnotes and footnote refs**
|
||||
- **Fixed some URL autolinks breaking too early** (#492)
|
||||
- Fixed `AbstractStringContainer` not actually being `abstract`
|
||||
|
||||
### Removed
|
||||
|
||||
- **Removed support for PHP 7.1, 7.2, and 7.3** (#625, #671)
|
||||
- **Removed all previously-deprecated functionality:**
|
||||
- Removed the ability to pass custom `Environment` instances into the `CommonMarkConverter` and `GithubFlavoredMarkdownConverter` constructors
|
||||
- Removed the `Converter` class and `ConverterInterface`
|
||||
- Removed the `bin/commonmark` script
|
||||
- Removed the `Html5Entities` utility class
|
||||
- Removed the `InlineMentionParser` (use `MentionParser` instead)
|
||||
- Removed `DefaultSlugGenerator` and `SlugGeneratorInterface` from the `Extension/HeadingPermalink/Slug` sub-namespace (use the new ones under `./SlugGenerator` instead)
|
||||
- Removed the following `ArrayCollection` methods:
|
||||
- `add()`
|
||||
- `set()`
|
||||
- `get()`
|
||||
- `remove()`
|
||||
- `isEmpty()`
|
||||
- `contains()`
|
||||
- `indexOf()`
|
||||
- `containsKey()`
|
||||
- `replaceWith()`
|
||||
- `removeGaps()`
|
||||
- Removed the `ConfigurableEnvironmentInterface::setConfig()` method
|
||||
- Removed the `ListBlock::TYPE_UNORDERED` constant
|
||||
- Removed the `CommonMarkConverter::VERSION` constant
|
||||
- Removed the `HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS` constant
|
||||
- Removed the `heading_permalink/inner_contents` configuration option
|
||||
- **Removed now-unused classes:**
|
||||
- `AbstractStringContainerBlock`
|
||||
- `BlockRendererInterface`
|
||||
- `Context`
|
||||
- `ContextInterface`
|
||||
- `Converter`
|
||||
- `ConverterInterface`
|
||||
- `InlineRendererInterface`
|
||||
- `PunctuationParser` (was split into two classes: `DashParser` and `EllipsesParser`)
|
||||
- `QuoteRenderer`
|
||||
- `UnmatchedBlockCloser`
|
||||
- Removed the following methods, properties, and constants:
|
||||
- `AbstractBlock::$open`
|
||||
- `AbstractBlock::$lastLineBlank`
|
||||
- `AbstractBlock::isContainer()`
|
||||
- `AbstractBlock::canContain()`
|
||||
- `AbstractBlock::isCode()`
|
||||
- `AbstractBlock::matchesNextLine()`
|
||||
- `AbstractBlock::endsWithBlankLine()`
|
||||
- `AbstractBlock::setLastLineBlank()`
|
||||
- `AbstractBlock::shouldLastLineBeBlank()`
|
||||
- `AbstractBlock::isOpen()`
|
||||
- `AbstractBlock::finalize()`
|
||||
- `AbstractBlock::getData()`
|
||||
- `AbstractInline::getData()`
|
||||
- `ConfigurableEnvironmentInterface::addBlockParser()`
|
||||
- `ConfigurableEnvironmentInterface::mergeConfig()`
|
||||
- `Delimiter::setCanClose()`
|
||||
- `EnvironmentInterface::getConfig()`
|
||||
- `EnvironmentInterface::getInlineParsersForCharacter()`
|
||||
- `EnvironmentInterface::getInlineParserCharacterRegex()`
|
||||
- `HtmlRenderer::renderBlock()`
|
||||
- `HtmlRenderer::renderBlocks()`
|
||||
- `HtmlRenderer::renderInline()`
|
||||
- `HtmlRenderer::renderInlines()`
|
||||
- `Node::isContainer()`
|
||||
- `RegexHelper::matchAll()` (use the new `matchFirst()` method instead)
|
||||
- `RegexHelper::REGEX_WHITESPACE`
|
||||
- Removed the second `$contents` argument from the `Heading` constructor
|
||||
|
||||
### Deprecated
|
||||
|
||||
**The following things have been deprecated and will not be supported in v3.0:**
|
||||
|
||||
- `Environment::mergeConfig()` (set configuration before instantiation instead)
|
||||
- `Environment::createCommonMarkEnvironment()` and `Environment::createGFMEnvironment()`
|
||||
- Alternative 1: Use `CommonMarkConverter` or `GithubFlavoredMarkdownConverter` if you don't need to customize the environment
|
||||
- Alternative 2: Instantiate a new `Environment` and add the necessary extensions yourself
|
||||
|
||||
[unreleased]: https://github.com/thephpleague/commonmark/compare/2.8.2...HEAD
|
||||
[2.8.2]: https://github.com/thephpleague/commonmark/compare/2.8.1...2.8.2
|
||||
[2.8.1]: https://github.com/thephpleague/commonmark/compare/2.8.0...2.8.1
|
||||
[2.8.0]: https://github.com/thephpleague/commonmark/compare/2.7.1...2.8.0
|
||||
[2.7.1]: https://github.com/thephpleague/commonmark/compare/2.7.0...2.7.1
|
||||
[2.7.0]: https://github.com/thephpleague/commonmark/compare/2.6.2...2.7.0
|
||||
[2.6.2]: https://github.com/thephpleague/commonmark/compare/2.6.1...2.6.2
|
||||
[2.6.1]: https://github.com/thephpleague/commonmark/compare/2.6.0...2.6.1
|
||||
[2.6.0]: https://github.com/thephpleague/commonmark/compare/2.5.3...2.6.0
|
||||
[2.5.3]: https://github.com/thephpleague/commonmark/compare/2.5.2...2.5.3
|
||||
[2.5.2]: https://github.com/thephpleague/commonmark/compare/2.5.1...2.5.2
|
||||
[2.5.1]: https://github.com/thephpleague/commonmark/compare/2.5.0...2.5.1
|
||||
[2.5.0]: https://github.com/thephpleague/commonmark/compare/2.4.4...2.5.0
|
||||
[2.4.4]: https://github.com/thephpleague/commonmark/compare/2.4.3...2.4.4
|
||||
[2.4.3]: https://github.com/thephpleague/commonmark/compare/2.4.2...2.4.3
|
||||
[2.4.2]: https://github.com/thephpleague/commonmark/compare/2.4.1...2.4.2
|
||||
[2.4.1]: https://github.com/thephpleague/commonmark/compare/2.4.0...2.4.1
|
||||
[2.4.0]: https://github.com/thephpleague/commonmark/compare/2.3.9...2.4.0
|
||||
[2.3.9]: https://github.com/thephpleague/commonmark/compare/2.3.8...2.3.9
|
||||
[2.3.8]: https://github.com/thephpleague/commonmark/compare/2.3.7...2.3.8
|
||||
[2.3.7]: https://github.com/thephpleague/commonmark/compare/2.3.6...2.3.7
|
||||
[2.3.6]: https://github.com/thephpleague/commonmark/compare/2.3.5...2.3.6
|
||||
[2.3.5]: https://github.com/thephpleague/commonmark/compare/2.3.4...2.3.5
|
||||
[2.3.4]: https://github.com/thephpleague/commonmark/compare/2.3.3...2.3.4
|
||||
[2.3.3]: https://github.com/thephpleague/commonmark/compare/2.3.2...2.3.3
|
||||
[2.3.2]: https://github.com/thephpleague/commonmark/compare/2.3.2...main
|
||||
[2.3.1]: https://github.com/thephpleague/commonmark/compare/2.3.0...2.3.1
|
||||
[2.3.0]: https://github.com/thephpleague/commonmark/compare/2.2.3...2.3.0
|
||||
[2.2.5]: https://github.com/thephpleague/commonmark/compare/2.2.4...2.2.5
|
||||
[2.2.4]: https://github.com/thephpleague/commonmark/compare/2.2.3...2.2.4
|
||||
[2.2.3]: https://github.com/thephpleague/commonmark/compare/2.2.2...2.2.3
|
||||
[2.2.2]: https://github.com/thephpleague/commonmark/compare/2.2.1...2.2.2
|
||||
[2.2.1]: https://github.com/thephpleague/commonmark/compare/2.2.0...2.2.1
|
||||
[2.2.0]: https://github.com/thephpleague/commonmark/compare/2.1.1...2.2.0
|
||||
[2.1.3]: https://github.com/thephpleague/commonmark/compare/2.1.2...2.1.3
|
||||
[2.1.2]: https://github.com/thephpleague/commonmark/compare/2.1.1...2.1.2
|
||||
[2.1.1]: https://github.com/thephpleague/commonmark/compare/2.0.2...2.1.1
|
||||
[2.1.0]: https://github.com/thephpleague/commonmark/compare/2.0.2...2.1.0
|
||||
[2.0.4]: https://github.com/thephpleague/commonmark/compare/2.0.3...2.0.4
|
||||
[2.0.3]: https://github.com/thephpleague/commonmark/compare/2.0.2...2.0.3
|
||||
[2.0.2]: https://github.com/thephpleague/commonmark/compare/2.0.1...2.0.2
|
||||
[2.0.1]: https://github.com/thephpleague/commonmark/compare/2.0.0...2.0.1
|
||||
[2.0.0]: https://github.com/thephpleague/commonmark/compare/2.0.0-rc2...2.0.0
|
||||
[2.0.0-rc2]: https://github.com/thephpleague/commonmark/compare/2.0.0-rc1...2.0.0-rc2
|
||||
[2.0.0-rc1]: https://github.com/thephpleague/commonmark/compare/2.0.0-beta3...2.0.0-rc1
|
||||
[2.0.0-beta3]: https://github.com/thephpleague/commonmark/compare/2.0.0-beta2...2.0.0-beta3
|
||||
[2.0.0-beta2]: https://github.com/thephpleague/commonmark/compare/2.0.0-beta1...2.0.0-beta2
|
||||
[2.0.0-beta1]: https://github.com/thephpleague/commonmark/compare/1.6...2.0.0-beta1
|
||||
225
vendor/league/commonmark/README.md
vendored
Normal file
225
vendor/league/commonmark/README.md
vendored
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
# league/commonmark
|
||||
|
||||
[](https://packagist.org/packages/league/commonmark)
|
||||
[](https://packagist.org/packages/league/commonmark)
|
||||
[](LICENSE)
|
||||
[](https://github.com/thephpleague/commonmark/actions?query=workflow%3ATests+branch%3Amain)
|
||||
[](https://scrutinizer-ci.com/g/thephpleague/commonmark/code-structure)
|
||||
[](https://scrutinizer-ci.com/g/thephpleague/commonmark)
|
||||
[](https://shepherd.dev/github/thephpleague/commonmark)
|
||||
[](https://bestpractices.coreinfrastructure.org/projects/126)
|
||||
[](https://www.colinodell.com/sponsor)
|
||||
|
||||

|
||||
|
||||
**league/commonmark** is a highly-extensible PHP Markdown parser created by [Colin O'Dell][@colinodell] which supports the full [CommonMark] spec and [GitHub-Flavored Markdown]. It is based on the [CommonMark JS reference implementation][commonmark.js] by [John MacFarlane] \([@jgm]\).
|
||||
|
||||
## 📦 Installation & Basic Usage
|
||||
|
||||
This project requires PHP 7.4 or higher with the `mbstring` extension. To install it via [Composer] simply run:
|
||||
|
||||
``` bash
|
||||
$ composer require league/commonmark
|
||||
```
|
||||
|
||||
The `CommonMarkConverter` class provides a simple wrapper for converting CommonMark to HTML:
|
||||
|
||||
```php
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
|
||||
$converter = new CommonMarkConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
echo $converter->convert('# Hello World!');
|
||||
|
||||
// <h1>Hello World!</h1>
|
||||
```
|
||||
|
||||
Or if you want GitHub-Flavored Markdown, use the `GithubFlavoredMarkdownConverter` class instead:
|
||||
|
||||
```php
|
||||
use League\CommonMark\GithubFlavoredMarkdownConverter;
|
||||
|
||||
$converter = new GithubFlavoredMarkdownConverter([
|
||||
'html_input' => 'strip',
|
||||
'allow_unsafe_links' => false,
|
||||
]);
|
||||
|
||||
echo $converter->convert('# Hello World!');
|
||||
|
||||
// <h1>Hello World!</h1>
|
||||
```
|
||||
|
||||
Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.
|
||||
|
||||
> [!CAUTION]
|
||||
> If you will be parsing untrusted input from users, please consider setting the `html_input` and `allow_unsafe_links` options per the example above. See <https://commonmark.thephpleague.com/security/> for more details. If you also do choose to allow raw HTML input from untrusted users, consider using a library (like [HTML Purifier](https://github.com/ezyang/htmlpurifier)) to provide additional HTML filtering.
|
||||
|
||||
## 📓 Documentation
|
||||
|
||||
Full documentation on advanced usage, configuration, and customization can be found at [commonmark.thephpleague.com][docs].
|
||||
|
||||
## ⏫ Upgrading
|
||||
|
||||
Information on how to upgrade to newer versions of this library can be found at <https://commonmark.thephpleague.com/releases>.
|
||||
|
||||
## 💻 GitHub-Flavored Markdown
|
||||
|
||||
The `GithubFlavoredMarkdownConverter` shown earlier is a drop-in replacement for the `CommonMarkConverter` which adds additional features found in the GFM spec:
|
||||
|
||||
- Autolinks
|
||||
- Disallowed raw HTML
|
||||
- Strikethrough
|
||||
- Tables
|
||||
- Task Lists
|
||||
|
||||
See the [Extensions documentation](https://commonmark.thephpleague.com/customization/extensions/) for more details on how to include only certain GFM features if you don't want them all.
|
||||
|
||||
## 🗃️ Related Packages
|
||||
|
||||
### Integrations
|
||||
|
||||
- [CakePHP 3](https://github.com/gourmet/common-mark)
|
||||
- [Drupal](https://www.drupal.org/project/markdown)
|
||||
- [Laravel 4+](https://github.com/GrahamCampbell/Laravel-Markdown)
|
||||
- [Sculpin](https://github.com/bcremer/sculpin-commonmark-bundle)
|
||||
- [Symfony 2 & 3](https://github.com/webuni/commonmark-bundle)
|
||||
- [Symfony 4](https://github.com/avensome/commonmark-bundle)
|
||||
- [Twig Markdown extension](https://github.com/twigphp/markdown-extension)
|
||||
- [Twig filter and tag](https://github.com/aptoma/twig-markdown)
|
||||
- [Laravel CommonMark Blog](https://github.com/spekulatius/laravel-commonmark-blog)
|
||||
|
||||
### Included Extensions
|
||||
|
||||
See [our extension documentation](https://commonmark.thephpleague.com/extensions/overview) for a full list of extensions bundled with this library.
|
||||
|
||||
### Community Extensions
|
||||
|
||||
Custom parsers/renderers can be bundled into extensions which extend CommonMark. Here are some that you may find interesting:
|
||||
|
||||
- [Emoji extension](https://github.com/ElGigi/CommonMarkEmoji) - UTF-8 emoji extension with Github tag.
|
||||
- [Sup Sub extensions](https://github.com/OWS/commonmark-sup-sub-extensions) - Adds support of superscript and subscript (`<sup>` and `<sub>` HTML tags).
|
||||
- [YouTube iframe extension](https://github.com/zoonru/commonmark-ext-youtube-iframe) - Replaces youtube link with iframe.
|
||||
- [Lazy Image extension](https://github.com/simonvomeyser/commonmark-ext-lazy-image) - Adds various options for lazy loading of images.
|
||||
- [Marker Extension](https://github.com/noah1400/commonmark-marker-extension) - Adds support of highlighted text (`<mark>` HTML tag).
|
||||
- [Pygments Highlighter extension](https://github.com/DanielEScherzer/commonmark-ext-pygments-highlighter) - Adds support for highlighting code with the Pygments library.
|
||||
- [LatexRenderer extension](https://github.com/samwilson/commonmark-latex) - For rendering Markdown to LaTeX.
|
||||
|
||||
Others can be found on [Packagist under the `commonmark-extension` package type](https://packagist.org/packages/league/commonmark?type=commonmark-extension).
|
||||
|
||||
If you build your own, feel free to submit a PR to add it to this list!
|
||||
|
||||
### Others
|
||||
|
||||
Check out the other cool things people are doing with `league/commonmark`: <https://packagist.org/packages/league/commonmark/dependents>
|
||||
|
||||
## 🏷️ Versioning
|
||||
|
||||
[SemVer](http://semver.org/) is followed closely. Minor and patch releases should not introduce breaking changes to the codebase; however, they might change the resulting AST or HTML output of parsed Markdown (due to bug fixes, spec changes, etc.) As a result, you might get slightly different HTML, but any custom code built onto this library should still function correctly.
|
||||
|
||||
Any classes or methods marked `@internal` are not intended for use outside of this library and are subject to breaking changes at any time, so please avoid using them.
|
||||
|
||||
## 🛠️ Maintenance & Support
|
||||
|
||||
When a new **minor** version (e.g. `2.0` -> `2.1`) is released, the previous one (`2.0`) will continue to receive security and critical bug fixes for *at least* 3 months.
|
||||
|
||||
When a new **major** version is released (e.g. `1.6` -> `2.0`), the previous one (`1.6`) will receive critical bug fixes for *at least* 3 months and security updates for 6 months after that new release comes out.
|
||||
|
||||
(This policy may change in the future and exceptions may be made on a case-by-case basis.)
|
||||
|
||||
**Professional support, including notification of new releases and security updates, is available through a [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme).**
|
||||
|
||||
## 👷♀️ Contributing
|
||||
|
||||
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure with us.
|
||||
|
||||
If you encounter a bug in the spec, please report it to the [CommonMark] project. Any resulting fix will eventually be implemented in this project as well.
|
||||
|
||||
Contributions to this library are **welcome**, especially ones that:
|
||||
|
||||
* Improve usability or flexibility without compromising our ability to adhere to the [CommonMark spec]
|
||||
* Mirror fixes made to the [reference implementation][commonmark.js]
|
||||
* Optimize performance
|
||||
* Fix issues with adhering to the [CommonMark spec]
|
||||
|
||||
Major refactoring to core parsing logic should be avoided if possible so that we can easily follow updates made to [the reference implementation][commonmark.js]. That being said, we will absolutely consider changes which don't deviate too far from the reference spec or which are favored by other popular CommonMark implementations.
|
||||
|
||||
Please see [CONTRIBUTING](https://github.com/thephpleague/commonmark/blob/main/.github/CONTRIBUTING.md) for additional details.
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
``` bash
|
||||
$ composer test
|
||||
```
|
||||
|
||||
This will also test league/commonmark against the latest supported spec.
|
||||
|
||||
## 🚀 Performance Benchmarks
|
||||
|
||||
You can compare the performance of **league/commonmark** to other popular parsers by running the included benchmark tool:
|
||||
|
||||
``` bash
|
||||
$ ./tests/benchmark/benchmark.php
|
||||
```
|
||||
|
||||
## 👥 Credits & Acknowledgements
|
||||
|
||||
This code was originally based on the [CommonMark JS reference implementation][commonmark.js] which is written, maintained, and copyrighted by [John MacFarlane]. This project simply wouldn't exist without his work.
|
||||
|
||||
And a huge thanks to all of our amazing contributors:
|
||||
|
||||
<a href="https://github.com/thephpleague/commonmark/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=thephpleague/commonmark" />
|
||||
</a>
|
||||
|
||||
### Sponsors
|
||||
|
||||
We'd also like to extend our sincere thanks the following sponsors who support ongoing development of this project:
|
||||
|
||||
- [Tidelift](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme) for offering support to both the maintainers and end-users through their [professional support](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme) program
|
||||
- [Blackfire](https://www.blackfire.io/) for providing an Open-Source Profiler subscription
|
||||
- [JetBrains](https://www.jetbrains.com/) for supporting this project with complimentary [PhpStorm](https://www.jetbrains.com/phpstorm/) licenses
|
||||
|
||||
Are you interested in sponsoring development of this project? See <https://www.colinodell.com/sponsor> for a list of ways to contribute.
|
||||
|
||||
## 📄 License
|
||||
|
||||
**league/commonmark** is licensed under the BSD-3 license. See the [`LICENSE`](LICENSE) file for more details.
|
||||
|
||||
## 🏛️ Governance
|
||||
|
||||
This project is primarily maintained by [Colin O'Dell][@colinodell]. Members of the [PHP League] Leadership Team may occasionally assist with some of these duties.
|
||||
|
||||
## 🗺️ Who Uses It?
|
||||
|
||||
This project is used by [Drupal](https://www.drupal.org/project/markdown), [Laravel Framework](https://laravel.com/), [Cachet](https://cachethq.io/), [Firefly III](https://firefly-iii.org/), [Neos](https://www.neos.io/), [Daux.io](https://daux.io/), and [more](https://packagist.org/packages/league/commonmark/dependents)!
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
<b>
|
||||
<a href="https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme">Get professional support for league/commonmark with a Tidelift subscription</a>
|
||||
</b>
|
||||
<br>
|
||||
<sub>
|
||||
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
|
||||
</sub>
|
||||
</div>
|
||||
|
||||
[CommonMark]: http://commonmark.org/
|
||||
[CommonMark spec]: http://spec.commonmark.org/
|
||||
[commonmark.js]: https://github.com/jgm/commonmark.js
|
||||
[GitHub-Flavored Markdown]: https://github.github.com/gfm/
|
||||
[John MacFarlane]: http://johnmacfarlane.net
|
||||
[docs]: https://commonmark.thephpleague.com/
|
||||
[docs-examples]: https://commonmark.thephpleague.com/customization/overview/#examples
|
||||
[docs-example-twitter]: https://commonmark.thephpleague.com/customization/inline-parsing#example-1---twitter-handles
|
||||
[docs-example-smilies]: https://commonmark.thephpleague.com/customization/inline-parsing#example-2---emoticons
|
||||
[All Contributors]: https://github.com/thephpleague/commonmark/contributors
|
||||
[@colinodell]: https://www.twitter.com/colinodell
|
||||
[@jgm]: https://github.com/jgm
|
||||
[jgm/stmd]: https://github.com/jgm/stmd
|
||||
[Composer]: https://getcomposer.org/
|
||||
[PHP League]: https://thephpleague.com
|
||||
129
vendor/league/commonmark/composer.json
vendored
Normal file
129
vendor/league/commonmark/composer.json
vendored
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
{
|
||||
"name": "league/commonmark",
|
||||
"type": "library",
|
||||
"description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
|
||||
"keywords": ["markdown","parser","commonmark","gfm","github","flavored","github-flavored","md"],
|
||||
"homepage": "https://commonmark.thephpleague.com",
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://commonmark.thephpleague.com/",
|
||||
"forum": "https://github.com/thephpleague/commonmark/discussions",
|
||||
"issues": "https://github.com/thephpleague/commonmark/issues",
|
||||
"rss": "https://github.com/thephpleague/commonmark/releases.atom",
|
||||
"source": "https://github.com/thephpleague/commonmark"
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"ext-mbstring": "*",
|
||||
"league/config": "^1.1.1",
|
||||
"psr/event-dispatcher": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.1 || ^3.0",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-json": "*",
|
||||
"cebe/markdown": "^1.0",
|
||||
"commonmark/cmark": "0.31.1",
|
||||
"commonmark/commonmark.js": "0.31.1",
|
||||
"composer/package-versions-deprecated": "^1.8",
|
||||
"embed/embed": "^4.4",
|
||||
"erusev/parsedown": "^1.0",
|
||||
"github/gfm": "0.29.0",
|
||||
"michelf/php-markdown": "^1.4 || ^2.0",
|
||||
"nyholm/psr7": "^1.5",
|
||||
"phpstan/phpstan": "^1.8.2",
|
||||
"phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"symfony/finder": "^5.3 | ^6.0 | ^7.0 || ^8.0",
|
||||
"symfony/process": "^5.4 | ^6.0 | ^7.0 || ^8.0",
|
||||
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0 || ^8.0",
|
||||
"unleashedtech/php-coding-standard": "^3.1.1",
|
||||
"vimeo/psalm": "^4.24.0 || ^5.0.0 || ^6.0.0"
|
||||
},
|
||||
"minimum-stability": "beta",
|
||||
"suggest": {
|
||||
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "commonmark/commonmark.js",
|
||||
"version": "0.31.1",
|
||||
"dist": {
|
||||
"url": "https://github.com/commonmark/commonmark.js/archive/0.31.1.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "commonmark/cmark",
|
||||
"version": "0.31.1",
|
||||
"dist": {
|
||||
"url": "https://github.com/commonmark/cmark/archive/0.31.1.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "package",
|
||||
"package": {
|
||||
"name": "github/gfm",
|
||||
"version": "0.29.0",
|
||||
"dist": {
|
||||
"url": "https://github.com/github/cmark-gfm/archive/0.29.0.gfm.13.zip",
|
||||
"type": "zip"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\CommonMark\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"League\\CommonMark\\Tests\\Unit\\": "tests/unit",
|
||||
"League\\CommonMark\\Tests\\Functional\\": "tests/functional",
|
||||
"League\\CommonMark\\Tests\\PHPStan\\": "tests/phpstan"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"phpcs": "phpcs",
|
||||
"phpcbf": "phpcbf",
|
||||
"phpstan": "phpstan analyse",
|
||||
"phpunit": "phpunit --no-coverage",
|
||||
"psalm": "psalm --stats",
|
||||
"pathological": "tests/pathological/test.php",
|
||||
"test": [
|
||||
"@phpcs",
|
||||
"@phpstan",
|
||||
"@psalm",
|
||||
"@phpunit",
|
||||
"@pathological"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.9-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
},
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
||||
42
vendor/league/config/CHANGELOG.md
vendored
Normal file
42
vendor/league/config/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Change Log
|
||||
All notable changes to this project will be documented in this file.
|
||||
Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
|
||||
|
||||
## [Unreleased][unreleased]
|
||||
|
||||
## [1.2.0] - 2022-12-11
|
||||
|
||||
### Changed
|
||||
|
||||
- Values can now be set prior to the corresponding schema being registered.
|
||||
- `exists()` and `get()` now only trigger validation for the relevant schema, not the entire config at once.
|
||||
|
||||
## [1.1.1] - 2021-08-14
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped the minimum version of dflydev/dot-access-data for PHP 8.1 support
|
||||
|
||||
## [1.1.0] - 2021-06-19
|
||||
|
||||
### Changed
|
||||
|
||||
- Bumped the minimum PHP version to 7.4+
|
||||
- Bumped the minimum version of nette/schema to 1.2.0
|
||||
|
||||
## [1.0.1] - 2021-05-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the `ConfigurationExceptionInterface` marker interface not extending `Throwable` (#2)
|
||||
|
||||
## [1.0.0] - 2021-05-31
|
||||
|
||||
Initial release! 🎉
|
||||
|
||||
[unreleased]: https://github.com/thephpleague/config/compare/v1.2.0...main
|
||||
[1.2.0]: https://github.com/thephpleague/config/compare/v1.1.1...v.1.2.0
|
||||
[1.1.1]: https://github.com/thephpleague/config/compare/v1.1.0...v1.1.1
|
||||
[1.1.0]: https://github.com/thephpleague/config/compare/v1.0.1...v1.1.0
|
||||
[1.0.1]: https://github.com/thephpleague/config/compare/v1.0.0...v1.0.1
|
||||
[1.0.0]: https://github.com/thephpleague/config/releases/tag/v1.0.0
|
||||
153
vendor/league/config/README.md
vendored
Normal file
153
vendor/league/config/README.md
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
# league/config
|
||||
|
||||
[](https://packagist.org/packages/league/config)
|
||||
[](https://packagist.org/packages/league/config)
|
||||
[](LICENSE)
|
||||
[](https://github.com/thephpleague/config/actions?query=workflow%3ATests+branch%3Amain)
|
||||
[](https://scrutinizer-ci.com/g/thephpleague/config/code-structure)
|
||||
[](https://scrutinizer-ci.com/g/thephpleague/config)
|
||||
[](https://www.colinodell.com/sponsor)
|
||||
|
||||
**league/config** helps you define nested configuration arrays with strict schemas and access configuration values with dot notation. It was created by [Colin O'Dell][@colinodell].
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
This project requires PHP 7.4 or higher. To install it via [Composer] simply run:
|
||||
|
||||
```bash
|
||||
composer require league/config
|
||||
```
|
||||
|
||||
## 🧰️ Basic Usage
|
||||
|
||||
The `Configuration` class provides everything you need to define the configuration structure and fetch values:
|
||||
|
||||
```php
|
||||
use League\Config\Configuration;
|
||||
use Nette\Schema\Expect;
|
||||
|
||||
// Define your configuration schema
|
||||
$config = new Configuration([
|
||||
'database' => Expect::structure([
|
||||
'driver' => Expect::anyOf('mysql', 'postgresql', 'sqlite')->required(),
|
||||
'host' => Expect::string()->default('localhost'),
|
||||
'port' => Expect::int()->min(1)->max(65535),
|
||||
'ssl' => Expect::bool(),
|
||||
'database' => Expect::string()->required(),
|
||||
'username' => Expect::string()->required(),
|
||||
'password' => Expect::string()->nullable(),
|
||||
]),
|
||||
'logging' => Expect::structure([
|
||||
'enabled' => Expect::bool()->default($_ENV['DEBUG'] == true),
|
||||
'file' => Expect::string()->deprecated("use logging.path instead"),
|
||||
'path' => Expect::string()->assert(function ($path) { return \is_writeable($path); })->required(),
|
||||
]),
|
||||
]);
|
||||
|
||||
// Set the values, either all at once with `merge()`:
|
||||
$config->merge([
|
||||
'database' => [
|
||||
'driver' => 'mysql',
|
||||
'port' => 3306,
|
||||
'database' => 'mydb',
|
||||
'username' => 'user',
|
||||
'password' => 'secret',
|
||||
],
|
||||
]);
|
||||
|
||||
// Or one-at-a-time with `set()`:
|
||||
$config->set('logging.path', '/var/log/myapp.log');
|
||||
|
||||
// You can now retrieve those values with `get()`.
|
||||
// Validation and defaults will be applied for you automatically
|
||||
$config->get('database'); // Fetches the entire "database" section as an array
|
||||
$config->get('database.driver'); // Fetch a specific nested value with dot notation
|
||||
$config->get('database/driver'); // Fetch a specific nested value with slash notation
|
||||
$config->get('database.host'); // Returns the default value "localhost"
|
||||
$config->get('logging.path'); // Guaranteed to be writeable thanks to the assertion in the schema
|
||||
|
||||
// If validation fails an `InvalidConfigurationException` will be thrown:
|
||||
$config->set('database.driver', 'mongodb');
|
||||
$config->get('database.driver'); // InvalidConfigurationException
|
||||
|
||||
// Attempting to fetch a non-existent key will result in an `InvalidConfigurationException`
|
||||
$config->get('foo.bar');
|
||||
|
||||
// You could avoid this by checking whether that item exists:
|
||||
$config->exists('foo.bar'); // Returns `false`
|
||||
```
|
||||
|
||||
## 📓 Documentation
|
||||
|
||||
Full documentation can be found at [config.thephpleague.com][docs].
|
||||
|
||||
## 💭 Philosophy
|
||||
|
||||
This library aims to provide a **simple yet opinionated** approach to configuration with the following goals:
|
||||
|
||||
- The configuration should operate on **arrays with nested values** which are easily accessible
|
||||
- The configuration structure should be **defined with strict schemas** defining the overall structure, allowed types, and allowed values
|
||||
- Schemas should be defined using a **simple, fluent interface**
|
||||
- You should be able to **add and combine schemas but never modify existing ones**
|
||||
- Both the configuration values and the schema should be **defined and managed with PHP code**
|
||||
- Schemas should be **immutable**; they should never change once they are set
|
||||
- Configuration values should never define or influence the schemas
|
||||
|
||||
As a result, this library will likely **never** support features like:
|
||||
|
||||
- Loading and/or exporting configuration values or schemas using YAML, XML, or other files
|
||||
- Parsing configuration values from a command line or other user interface
|
||||
- Dynamically changing the schema, allowed values, or default values based on other configuration values
|
||||
|
||||
If you need that functionality you should check out other libraries like:
|
||||
|
||||
- [symfony/config]
|
||||
- [symfony/options-resolver]
|
||||
- [hassankhan/config]
|
||||
- [consolidation/config]
|
||||
- [laminas/laminas-config]
|
||||
|
||||
## 🏷️ Versioning
|
||||
|
||||
[SemVer](http://semver.org/) is followed closely. Minor and patch releases should not introduce breaking changes to the codebase.
|
||||
|
||||
Any classes or methods marked `@internal` are not intended for use outside this library and are subject to breaking changes at any time, so please avoid using them.
|
||||
|
||||
## 🛠️ Maintenance & Support
|
||||
|
||||
When a new **minor** version (e.g. `1.0` -> `1.1`) is released, the previous one (`1.0`) will continue to receive security and critical bug fixes for *at least* 3 months.
|
||||
|
||||
When a new **major** version is released (e.g. `1.1` -> `2.0`), the previous one (`1.1`) will receive critical bug fixes for *at least* 3 months and security updates for 6 months after that new release comes out.
|
||||
|
||||
(This policy may change in the future and exceptions may be made on a case-by-case basis.)
|
||||
|
||||
## 👷️ Contributing
|
||||
|
||||
Contributions to this library are **welcome**! We only ask that you adhere to our [contributor guidelines] and avoid making changes that conflict with our Philosophy above.
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
```bash
|
||||
composer test
|
||||
```
|
||||
|
||||
## 📄 License
|
||||
|
||||
**league/config** is licensed under the BSD-3 license. See the [`LICENSE.md`][license] file for more details.
|
||||
|
||||
## 🗺️ Who Uses It?
|
||||
|
||||
This project is used by [league/commonmark][league-commonmark].
|
||||
|
||||
[docs]: https://config.thephpleague.com/
|
||||
[@colinodell]: https://www.twitter.com/colinodell
|
||||
[Composer]: https://getcomposer.org/
|
||||
[PHP League]: https://thephpleague.com
|
||||
[symfony/config]: https://symfony.com/doc/current/components/config.html
|
||||
[symfony/options-resolver]: https://symfony.com/doc/current/components/options_resolver.html
|
||||
[hassankhan/config]: https://github.com/hassankhan/config
|
||||
[consolidation/config]: https://github.com/consolidation/config
|
||||
[laminas/laminas-config]: https://docs.laminas.dev/laminas-config/
|
||||
[contributor guidelines]: https://github.com/thephpleague/config/blob/main/.github/CONTRIBUTING.md
|
||||
[license]: https://github.com/thephpleague/config/blob/main/LICENSE.md
|
||||
[league-commonmark]: https://commonmark.thephpleague.com
|
||||
69
vendor/league/config/composer.json
vendored
Normal file
69
vendor/league/config/composer.json
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
{
|
||||
"name": "league/config",
|
||||
"type": "library",
|
||||
"description": "Define configuration arrays with strict schemas and access values with dot notation",
|
||||
"keywords": ["configuration","config","schema","array","nested","dot","dot-access"],
|
||||
"homepage": "https://config.thephpleague.com",
|
||||
"license": "BSD-3-Clause",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://config.thephpleague.com/",
|
||||
"issues": "https://github.com/thephpleague/config/issues",
|
||||
"rss": "https://github.com/thephpleague/config/releases.atom",
|
||||
"source": "https://github.com/thephpleague/config"
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"dflydev/dot-access-data": "^3.0.1",
|
||||
"nette/schema": "^1.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.8.2",
|
||||
"phpunit/phpunit": "^9.5.5",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"unleashedtech/php-coding-standard": "^3.1",
|
||||
"vimeo/psalm": "^4.7.3"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\Config\\": "src"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"League\\Config\\Tests\\": "tests"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"phpcs": "phpcs",
|
||||
"phpstan": "phpstan analyse",
|
||||
"phpunit": "phpunit --no-coverage",
|
||||
"psalm": "psalm",
|
||||
"test": [
|
||||
"@phpcs",
|
||||
"@phpstan",
|
||||
"@psalm",
|
||||
"@phpunit"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.2-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
49
vendor/nette/schema/composer.json
vendored
Normal file
49
vendor/nette/schema/composer.json
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"name": "nette/schema",
|
||||
"description": "📐 Nette Schema: validating data structures against a given Schema.",
|
||||
"keywords": ["nette", "config"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "8.1 - 8.5",
|
||||
"nette/utils": "^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.6",
|
||||
"tracy/tracy": "^2.8",
|
||||
"phpstan/phpstan": "^2.1.39@stable",
|
||||
"phpstan/extension-installer": "^1.4@stable",
|
||||
"nette/phpstan-rules": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"],
|
||||
"psr-4": {
|
||||
"Nette\\": "src"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.3-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"phpstan/extension-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
60
vendor/nette/schema/license.md
vendored
Normal file
60
vendor/nette/schema/license.md
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
536
vendor/nette/schema/readme.md
vendored
Normal file
536
vendor/nette/schema/readme.md
vendored
Normal file
|
|
@ -0,0 +1,536 @@
|
|||
# Nette Schema
|
||||
|
||||
[](https://packagist.org/packages/nette/schema)
|
||||
[](https://github.com/nette/schema/actions)
|
||||
[](https://coveralls.io/github/nette/schema?branch=master)
|
||||
[](https://github.com/nette/schema/releases)
|
||||
[](https://github.com/nette/schema/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
A practical library for validation and normalization of data structures against a given schema with a smart & easy-to-understand API.
|
||||
|
||||
Documentation can be found on the [website](https://doc.nette.org/schema).
|
||||
|
||||
Installation:
|
||||
|
||||
```shell
|
||||
composer require nette/schema
|
||||
```
|
||||
|
||||
It requires PHP version 8.1 and supports PHP up to 8.5.
|
||||
|
||||
|
||||
[Support Me](https://github.com/sponsors/dg)
|
||||
--------------------------------------------
|
||||
|
||||
Do you like Nette Schema? Are you looking forward to the new features?
|
||||
|
||||
[](https://github.com/sponsors/dg)
|
||||
|
||||
Thank you!
|
||||
|
||||
|
||||
Basic Usage
|
||||
-----------
|
||||
|
||||
In variable `$schema` we have a validation schema (what exactly this means and how to create it we will say later) and in variable `$data` we have a data structure that we want to validate and normalize. This can be, for example, data sent by the user through an API, configuration file, etc.
|
||||
|
||||
The task is handled by the [Nette\Schema\Processor](https://api.nette.org/schema/master/Nette/Schema/Processor.html) class, which processes the input and either returns normalized data or throws an [Nette\Schema\ValidationException](https://api.nette.org/schema/master/Nette/Schema/ValidationException.html) exception on error.
|
||||
|
||||
```php
|
||||
$processor = new Nette\Schema\Processor;
|
||||
|
||||
try {
|
||||
$normalized = $processor->process($schema, $data);
|
||||
} catch (Nette\Schema\ValidationException $e) {
|
||||
echo 'Data is invalid: ' . $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
Method `$e->getMessages()` returns array of all message strings and `$e->getMessageObjects()` return all messages as [Nette\Schema\Message](https://api.nette.org/schema/master/Nette/Schema/Message.html) objects.
|
||||
|
||||
|
||||
Defining Schema
|
||||
---------------
|
||||
|
||||
And now let's create a schema. The class [Nette\Schema\Expect](https://api.nette.org/schema/master/Nette/Schema/Expect.html) is used to define it, we actually define expectations of what the data should look like. Let's say that the input data must be a structure (e.g. an array) containing elements `processRefund` of type bool and `refundAmount` of type int.
|
||||
|
||||
```php
|
||||
use Nette\Schema\Expect;
|
||||
|
||||
$schema = Expect::structure([
|
||||
'processRefund' => Expect::bool(),
|
||||
'refundAmount' => Expect::int(),
|
||||
]);
|
||||
```
|
||||
|
||||
We believe that the schema definition looks clear, even if you see it for the very first time.
|
||||
|
||||
Lets send the following data for validation:
|
||||
|
||||
```php
|
||||
$data = [
|
||||
'processRefund' => true,
|
||||
'refundAmount' => 17,
|
||||
];
|
||||
|
||||
$normalized = $processor->process($schema, $data); // OK, it passes
|
||||
```
|
||||
|
||||
The output, i.e. the value `$normalized`, is the object `stdClass`. If we want the output to be an array, we add a cast to schema `Expect::structure([...])->castTo('array')`.
|
||||
|
||||
All elements of the structure are optional and have a default value `null`. Example:
|
||||
|
||||
```php
|
||||
$data = [
|
||||
'refundAmount' => 17,
|
||||
];
|
||||
|
||||
$normalized = $processor->process($schema, $data); // OK, it passes
|
||||
// $normalized = {'processRefund' => null, 'refundAmount' => 17}
|
||||
```
|
||||
|
||||
The fact that the default value is `null` does not mean that it would be accepted in the input data `'processRefund' => null`. No, the input must be boolean, i.e. only `true` or `false`. We would have to explicitly allow `null` via `Expect::bool()->nullable()`.
|
||||
|
||||
An item can be made mandatory using `Expect::bool()->required()`. We change the default value to `false` using `Expect::bool()->default(false)` or shortly using `Expect::bool(false)`.
|
||||
|
||||
And what if we wanted to accept `1` and `0` besides booleans? Then we list the allowed values, which we will also normalize to boolean:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'processRefund' => Expect::anyOf(true, false, 1, 0)->castTo('bool'),
|
||||
'refundAmount' => Expect::int(),
|
||||
]);
|
||||
|
||||
$normalized = $processor->process($schema, $data);
|
||||
is_bool($normalized->processRefund); // true
|
||||
```
|
||||
|
||||
Now you know the basics of how the schema is defined and how the individual elements of the structure behave. We will now show what all the other elements can be used in defining a schema.
|
||||
|
||||
|
||||
Data Types: type()
|
||||
------------------
|
||||
|
||||
All standard PHP data types can be listed in the schema:
|
||||
|
||||
```php
|
||||
Expect::string($default = null)
|
||||
Expect::int($default = null)
|
||||
Expect::float($default = null)
|
||||
Expect::bool($default = null)
|
||||
Expect::null()
|
||||
Expect::array($default = [])
|
||||
```
|
||||
|
||||
And then all types [supported by the Validators](https://doc.nette.org/validators#toc-validation-rules) via `Expect::type('scalar')` or abbreviated `Expect::scalar()`. Also class or interface names are accepted, e.g. `Expect::type('AddressEntity')`.
|
||||
|
||||
You can also use union notation:
|
||||
|
||||
```php
|
||||
Expect::type('bool|string|array')
|
||||
```
|
||||
|
||||
The default value is always `null` except for `array` and `list`, where it is an empty array. (A list is an array indexed in ascending order of numeric keys from zero, that is, a non-associative array).
|
||||
|
||||
|
||||
Array of Values: arrayOf() listOf()
|
||||
-----------------------------------
|
||||
|
||||
The array is too general structure, it is more useful to specify exactly what elements it can contain. For example, an array whose elements can only be strings:
|
||||
|
||||
```php
|
||||
$schema = Expect::arrayOf('string');
|
||||
|
||||
$processor->process($schema, ['hello', 'world']); // OK
|
||||
$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK
|
||||
$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string
|
||||
```
|
||||
|
||||
The second parameter can be used to specify keys (since version 1.2):
|
||||
|
||||
```php
|
||||
$schema = Expect::arrayOf('string', 'int');
|
||||
|
||||
$processor->process($schema, ['hello', 'world']); // OK
|
||||
$processor->process($schema, ['a' => 'hello']); // ERROR: 'a' is not int
|
||||
```
|
||||
|
||||
The list is an indexed array:
|
||||
|
||||
```php
|
||||
$schema = Expect::listOf('string');
|
||||
|
||||
$processor->process($schema, ['a', 'b']); // OK
|
||||
$processor->process($schema, ['a', 123]); // ERROR: 123 is not a string
|
||||
$processor->process($schema, ['key' => 'a']); // ERROR: is not a list
|
||||
$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list
|
||||
```
|
||||
|
||||
The parameter can also be a schema, so we can write:
|
||||
|
||||
```php
|
||||
Expect::arrayOf(Expect::bool())
|
||||
```
|
||||
|
||||
The default value is an empty array. If you specify a default value, it will be merged with the passed data. This can be disabled using `mergeDefaults(false)`.
|
||||
|
||||
|
||||
Enumeration: anyOf()
|
||||
--------------------
|
||||
|
||||
`anyOf()` is a set of values or schemas that a value can be. Here's how to write an array of elements that can be either `'a'`, `true`, or `null`:
|
||||
|
||||
```php
|
||||
$schema = Expect::listOf(
|
||||
Expect::anyOf('a', true, null),
|
||||
);
|
||||
|
||||
$processor->process($schema, ['a', true, null, 'a']); // OK
|
||||
$processor->process($schema, ['a', false]); // ERROR: false does not belong there
|
||||
```
|
||||
|
||||
The enumeration elements can also be schemas:
|
||||
|
||||
```php
|
||||
$schema = Expect::listOf(
|
||||
Expect::anyOf(Expect::string(), true, null),
|
||||
);
|
||||
|
||||
$processor->process($schema, ['foo', true, null, 'bar']); // OK
|
||||
$processor->process($schema, [123]); // ERROR
|
||||
```
|
||||
|
||||
The `anyOf()` method accepts variants as individual parameters, not as array. To pass it an array of values, use the unpacking operator `anyOf(...$variants)`.
|
||||
|
||||
The default value is `null`. Use the `firstIsDefault()` method to make the first element the default:
|
||||
|
||||
```php
|
||||
// default is 'hello'
|
||||
Expect::anyOf(Expect::string('hello'), true, null)->firstIsDefault();
|
||||
```
|
||||
|
||||
|
||||
Structures
|
||||
----------
|
||||
|
||||
Structures are objects with defined keys. Each of these key => value pairs is referred to as a "property":
|
||||
|
||||
Structures accept arrays and objects and return objects `stdClass` (unless you change it with `castTo('array')`, etc.).
|
||||
|
||||
By default, all properties are optional and have a default value of `null`. You can define mandatory properties using `required()`:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'required' => Expect::string()->required(),
|
||||
'optional' => Expect::string(), // the default value is null
|
||||
]);
|
||||
|
||||
$processor->process($schema, ['optional' => '']);
|
||||
// ERROR: option 'required' is missing
|
||||
|
||||
$processor->process($schema, ['required' => 'foo']);
|
||||
// OK, returns {'required' => 'foo', 'optional' => null}
|
||||
```
|
||||
|
||||
If you do not want to output properties with only a default value, use `skipDefaults()`:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'required' => Expect::string()->required(),
|
||||
'optional' => Expect::string(),
|
||||
])->skipDefaults();
|
||||
|
||||
$processor->process($schema, ['required' => 'foo']);
|
||||
// OK, returns {'required' => 'foo'}
|
||||
```
|
||||
|
||||
Although `null` is the default value of the `optional` property, it is not allowed in the input data (the value must be a string). Properties accepting `null` are defined using `nullable()`:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'optional' => Expect::string(),
|
||||
'nullable' => Expect::string()->nullable(),
|
||||
]);
|
||||
|
||||
$processor->process($schema, ['optional' => null]);
|
||||
// ERROR: 'optional' expects to be string, null given.
|
||||
|
||||
$processor->process($schema, ['nullable' => null]);
|
||||
// OK, returns {'optional' => null, 'nullable' => null}
|
||||
```
|
||||
|
||||
By default, there can be no extra items in the input data:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'key' => Expect::string(),
|
||||
]);
|
||||
|
||||
$processor->process($schema, ['additional' => 1]);
|
||||
// ERROR: Unexpected item 'additional'
|
||||
```
|
||||
|
||||
Which we can change with `otherItems()`. As a parameter, we will specify the schema for each extra element:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'key' => Expect::string(),
|
||||
])->otherItems(Expect::int());
|
||||
|
||||
$processor->process($schema, ['additional' => 1]); // OK
|
||||
$processor->process($schema, ['additional' => true]); // ERROR
|
||||
```
|
||||
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
You can deprecate property using the `deprecated([string $message])` method. Deprecation notices are returned by `$processor->getWarnings()`:
|
||||
|
||||
```php
|
||||
$schema = Expect::structure([
|
||||
'old' => Expect::int()->deprecated('The item %path% is deprecated'),
|
||||
]);
|
||||
|
||||
$processor->process($schema, ['old' => 1]); // OK
|
||||
$processor->getWarnings(); // ["The item 'old' is deprecated"]
|
||||
```
|
||||
|
||||
|
||||
Ranges: min() max()
|
||||
-------------------
|
||||
|
||||
Use `min()` and `max()` to limit the number of elements for arrays:
|
||||
|
||||
```php
|
||||
// array, at least 10 items, maximum 20 items
|
||||
Expect::array()->min(10)->max(20);
|
||||
```
|
||||
|
||||
For strings, limit their length:
|
||||
|
||||
```php
|
||||
// string, at least 10 characters long, maximum 20 characters
|
||||
Expect::string()->min(10)->max(20);
|
||||
```
|
||||
|
||||
For numbers, limit their value:
|
||||
|
||||
```php
|
||||
// integer, between 10 and 20 inclusive
|
||||
Expect::int()->min(10)->max(20);
|
||||
```
|
||||
|
||||
Of course, it is possible to mention only `min()`, or only `max()`:
|
||||
|
||||
```php
|
||||
// string, maximum 20 characters
|
||||
Expect::string()->max(20);
|
||||
```
|
||||
|
||||
|
||||
Regular Expressions: pattern()
|
||||
------------------------------
|
||||
|
||||
Using `pattern()`, you can specify a regular expression which the **whole** input string must match (i.e. as if it were wrapped in characters `^` a `$`):
|
||||
|
||||
```php
|
||||
// just 9 digits
|
||||
Expect::string()->pattern('\d{9}');
|
||||
```
|
||||
|
||||
|
||||
Custom Assertions: assert()
|
||||
---------------------------
|
||||
|
||||
You can add any other restrictions using `assert(callable $fn)`.
|
||||
|
||||
```php
|
||||
$countIsEven = fn($v) => count($v) % 2 === 0;
|
||||
|
||||
$schema = Expect::arrayOf('string')
|
||||
->assert($countIsEven); // the count must be even
|
||||
|
||||
$processor->process($schema, ['a', 'b']); // OK
|
||||
$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 is not even
|
||||
```
|
||||
|
||||
Or
|
||||
|
||||
```php
|
||||
Expect::string()->assert('is_file'); // the file must exist
|
||||
```
|
||||
|
||||
You can add your own description for each assertion. It will be part of the error message.
|
||||
|
||||
```php
|
||||
$schema = Expect::arrayOf('string')
|
||||
->assert($countIsEven, 'Even items in array');
|
||||
|
||||
$processor->process($schema, ['a', 'b', 'c']);
|
||||
// Failed assertion "Even items in array" for item with value array.
|
||||
```
|
||||
|
||||
The method can be called repeatedly to add multiple constraints. It can be intermixed with calls to `transform()` and `castTo()`.
|
||||
|
||||
|
||||
Transformation: transform()
|
||||
---------------------------
|
||||
|
||||
Successfully validated data can be modified using a custom function:
|
||||
|
||||
```php
|
||||
// conversion to uppercase:
|
||||
Expect::string()->transform(fn(string $s) => strtoupper($s));
|
||||
```
|
||||
|
||||
The method can be called repeatedly to add multiple transformations. It can be intermixed with calls to `assert()` and `castTo()`. The operations will be executed in the order in which they are declared:
|
||||
|
||||
```php
|
||||
Expect::type('string|int')
|
||||
->castTo('string')
|
||||
->assert('ctype_lower', 'All characters must be lowercased')
|
||||
->transform(fn(string $s) => strtoupper($s)); // conversion to uppercase
|
||||
```
|
||||
|
||||
The `transform()` method can both transform and validate the value simultaneously. This is often simpler and less redundant than chaining `transform()` and `assert()`. For this purpose, the function receives a [Nette\Schema\Context](https://api.nette.org/schema/master/Nette/Schema/Context.html) object with an `addError()` method, which can be used to add information about validation issues:
|
||||
|
||||
```php
|
||||
Expect::string()
|
||||
->transform(function (string $s, Nette\Schema\Context $context) {
|
||||
if (!ctype_lower($s)) {
|
||||
$context->addError('All characters must be lowercased', 'my.case.error');
|
||||
return null;
|
||||
}
|
||||
|
||||
return strtoupper($s);
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
Casting: castTo()
|
||||
-----------------
|
||||
|
||||
Successfully validated data can be cast:
|
||||
|
||||
```php
|
||||
Expect::scalar()->castTo('string');
|
||||
```
|
||||
|
||||
In addition to native PHP types, you can also cast to classes. It distinguishes whether it is a simple class without a constructor or a class with a constructor. If the class has no constructor, an instance of it is created and all elements of the structure are written to its properties:
|
||||
|
||||
```php
|
||||
class Info
|
||||
{
|
||||
public bool $processRefund;
|
||||
public int $refundAmount;
|
||||
}
|
||||
|
||||
Expect::structure([
|
||||
'processRefund' => Expect::bool(),
|
||||
'refundAmount' => Expect::int(),
|
||||
])->castTo(Info::class);
|
||||
|
||||
// creates '$obj = new Info' and writes to $obj->processRefund and $obj->refundAmount
|
||||
```
|
||||
|
||||
If the class has a constructor, the elements of the structure are passed as named parameters to the constructor:
|
||||
|
||||
```php
|
||||
class Info
|
||||
{
|
||||
public function __construct(
|
||||
public bool $processRefund,
|
||||
public int $refundAmount,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
// creates $obj = new Info(processRefund: ..., refundAmount: ...)
|
||||
```
|
||||
|
||||
Casting combined with a scalar parameter creates an object and passes the value as the sole parameter to the constructor:
|
||||
|
||||
```php
|
||||
Expect::string()->castTo(DateTime::class);
|
||||
// creates new DateTime(...)
|
||||
```
|
||||
|
||||
|
||||
Normalization: before()
|
||||
-----------------------
|
||||
|
||||
Prior to the validation itself, the data can be normalized using the method `before()`. As an example, let's have an element that must be an array of strings (eg `['a', 'b', 'c']`), but receives input in the form of a string `a b c`:
|
||||
|
||||
```php
|
||||
$explode = fn($v) => explode(' ', $v);
|
||||
|
||||
$schema = Expect::arrayOf('string')
|
||||
->before($explode);
|
||||
|
||||
$normalized = $processor->process($schema, 'a b c');
|
||||
// OK, returns ['a', 'b', 'c']
|
||||
```
|
||||
|
||||
|
||||
Mapping to Objects: from()
|
||||
--------------------------
|
||||
|
||||
You can generate structure schema from the class. Example:
|
||||
|
||||
```php
|
||||
class Config
|
||||
{
|
||||
/** @var string */
|
||||
public $name;
|
||||
/** @var string|null */
|
||||
public $password;
|
||||
/** @var bool */
|
||||
public $admin = false;
|
||||
}
|
||||
|
||||
$schema = Expect::from(new Config);
|
||||
|
||||
$data = [
|
||||
'name' => 'jeff',
|
||||
];
|
||||
|
||||
$normalized = $processor->process($schema, $data);
|
||||
// $normalized instanceof Config
|
||||
// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false}
|
||||
```
|
||||
|
||||
If you are using PHP 7.4 or higher, you can use native types:
|
||||
|
||||
```php
|
||||
class Config
|
||||
{
|
||||
public string $name;
|
||||
public ?string $password;
|
||||
public bool $admin = false;
|
||||
}
|
||||
|
||||
$schema = Expect::from(new Config);
|
||||
```
|
||||
|
||||
Anonymous classes are also supported:
|
||||
|
||||
```php
|
||||
$schema = Expect::from(new class {
|
||||
public string $name;
|
||||
public ?string $password;
|
||||
public bool $admin = false;
|
||||
});
|
||||
```
|
||||
|
||||
Because the information obtained from the class definition may not be sufficient, you can add a custom schema for the elements with the second parameter:
|
||||
|
||||
```php
|
||||
$schema = Expect::from(new Config, [
|
||||
'name' => Expect::string()->pattern('\w:.*'),
|
||||
]);
|
||||
```
|
||||
14
vendor/nette/schema/src/Schema/Context.php
vendored
14
vendor/nette/schema/src/Schema/Context.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use function count;
|
||||
|
|
@ -16,21 +14,22 @@ final class Context
|
|||
{
|
||||
public bool $skipDefaults = false;
|
||||
|
||||
/** @var string[] */
|
||||
/** @var list<int|string> */
|
||||
public array $path = [];
|
||||
|
||||
public bool $isKey = false;
|
||||
|
||||
/** @var Message[] */
|
||||
/** @var list<Message> */
|
||||
public array $errors = [];
|
||||
|
||||
/** @var Message[] */
|
||||
/** @var list<Message> */
|
||||
public array $warnings = [];
|
||||
|
||||
/** @var array[] */
|
||||
/** @var list<array{DynamicParameter, string, list<int|string>}> */
|
||||
public array $dynamics = [];
|
||||
|
||||
|
||||
/** @param array<string, mixed> $variables */
|
||||
public function addError(string $message, string $code, array $variables = []): Message
|
||||
{
|
||||
$variables['isKey'] = $this->isKey;
|
||||
|
|
@ -38,6 +37,7 @@ final class Context
|
|||
}
|
||||
|
||||
|
||||
/** @param array<string, mixed> $variables */
|
||||
public function addWarning(string $message, string $code, array $variables = []): Message
|
||||
{
|
||||
return $this->warnings[] = new Message($message, $code, $this->path, $variables);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema\Elements;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -20,6 +18,7 @@ final class AnyOf implements Schema
|
|||
{
|
||||
use Base;
|
||||
|
||||
/** @var mixed[] */
|
||||
private array $set;
|
||||
|
||||
|
||||
|
|
|
|||
24
vendor/nette/schema/src/Schema/Elements/Base.php
vendored
24
vendor/nette/schema/src/Schema/Elements/Base.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema\Elements;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -23,10 +21,10 @@ trait Base
|
|||
private bool $required = false;
|
||||
private mixed $default = null;
|
||||
|
||||
/** @var ?callable */
|
||||
private $before;
|
||||
/** @var ?\Closure(mixed): mixed */
|
||||
private ?\Closure $before = null;
|
||||
|
||||
/** @var callable[] */
|
||||
/** @var list<\Closure(mixed, Context): mixed> */
|
||||
private array $transforms = [];
|
||||
private ?string $deprecated = null;
|
||||
|
||||
|
|
@ -45,9 +43,10 @@ trait Base
|
|||
}
|
||||
|
||||
|
||||
/** @param callable(mixed): mixed $handler */
|
||||
public function before(callable $handler): self
|
||||
{
|
||||
$this->before = $handler;
|
||||
$this->before = $handler(...);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -58,16 +57,18 @@ trait Base
|
|||
}
|
||||
|
||||
|
||||
/** @param callable(mixed, Context): mixed $handler */
|
||||
public function transform(callable $handler): self
|
||||
{
|
||||
$this->transforms[] = $handler;
|
||||
$this->transforms[] = $handler(...);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @param callable(mixed): bool $handler */
|
||||
public function assert(callable $handler, ?string $description = null): self
|
||||
{
|
||||
$expected = $description ?: (is_string($handler) ? "$handler()" : '#' . count($this->transforms));
|
||||
$expected = $description ?? (is_string($handler) ? "$handler()" : '#' . count($this->transforms));
|
||||
return $this->transform(function ($value, Context $context) use ($handler, $description, $expected) {
|
||||
if ($handler($value)) {
|
||||
return $value;
|
||||
|
|
@ -146,7 +147,10 @@ trait Base
|
|||
}
|
||||
|
||||
|
||||
/** @deprecated use Nette\Schema\Validators::validateRange() */
|
||||
/**
|
||||
* @deprecated use Nette\Schema\Validators::validateRange()
|
||||
* @param array{?float, ?float} $range
|
||||
*/
|
||||
private static function doValidateRange(mixed $value, array $range, Context $context, string $types = ''): bool
|
||||
{
|
||||
$isOk = $context->createChecker();
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema\Elements;
|
||||
|
||||
use Nette;
|
||||
use Nette\Schema\Context;
|
||||
use Nette\Schema\Helpers;
|
||||
use Nette\Schema\Schema;
|
||||
use function array_diff_key, array_fill_keys, array_key_exists, array_keys, array_map, array_merge, array_pop, array_values, is_array, is_object;
|
||||
use function array_diff_key, array_fill_keys, array_key_exists, array_keys, array_map, array_merge, array_pop, array_values, is_array, is_object, strval;
|
||||
|
||||
|
||||
final class Structure implements Schema
|
||||
|
|
@ -31,9 +29,7 @@ final class Structure implements Schema
|
|||
private bool $skipDefaults = false;
|
||||
|
||||
|
||||
/**
|
||||
* @param Schema[] $shape
|
||||
*/
|
||||
/** @param Schema[] $shape */
|
||||
public function __construct(array $shape)
|
||||
{
|
||||
(function (Schema ...$items) {})(...array_values($shape));
|
||||
|
|
@ -77,6 +73,7 @@ final class Structure implements Schema
|
|||
}
|
||||
|
||||
|
||||
/** @param Schema[]|self $shape */
|
||||
public function extend(array|self $shape): self
|
||||
{
|
||||
$shape = $shape instanceof self ? $shape->items : $shape;
|
||||
|
|
@ -84,6 +81,7 @@ final class Structure implements Schema
|
|||
}
|
||||
|
||||
|
||||
/** @return Schema[] */
|
||||
public function getShape(): array
|
||||
{
|
||||
return $this->items;
|
||||
|
|
@ -167,6 +165,7 @@ final class Structure implements Schema
|
|||
}
|
||||
|
||||
|
||||
/** @param array<mixed> $value */
|
||||
private function validateItems(array &$value, Context $context): void
|
||||
{
|
||||
$items = $this->items;
|
||||
|
|
@ -174,7 +173,7 @@ final class Structure implements Schema
|
|||
if ($this->otherItems) {
|
||||
$items += array_fill_keys($extraKeys, $this->otherItems);
|
||||
} else {
|
||||
$keys = array_map('strval', array_keys($items));
|
||||
$keys = array_map(strval(...), array_keys($items));
|
||||
foreach ($extraKeys as $key) {
|
||||
$hint = Nette\Utils\Helpers::getSuggestion($keys, (string) $key);
|
||||
$context->addError(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema\Elements;
|
||||
|
||||
use Nette\Schema\Context;
|
||||
|
|
@ -189,6 +187,7 @@ final class Type implements Schema
|
|||
}
|
||||
|
||||
|
||||
/** @param array<mixed> $value */
|
||||
private function validateItems(array &$value, Context $context): void
|
||||
{
|
||||
if (!$this->itemsValue) {
|
||||
|
|
|
|||
18
vendor/nette/schema/src/Schema/Expect.php
vendored
18
vendor/nette/schema/src/Schema/Expect.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -32,6 +30,7 @@ use function is_object;
|
|||
*/
|
||||
final class Expect
|
||||
{
|
||||
/** @param list<mixed> $args */
|
||||
public static function __callStatic(string $name, array $args): Type
|
||||
{
|
||||
$type = new Type($name);
|
||||
|
|
@ -55,15 +54,14 @@ final class Expect
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Schema[] $shape
|
||||
*/
|
||||
/** @param Schema[] $shape */
|
||||
public static function structure(array $shape): Structure
|
||||
{
|
||||
return new Structure($shape);
|
||||
}
|
||||
|
||||
|
||||
/** @param array<string, Schema> $items */
|
||||
public static function from(object $object, array $items = []): Structure
|
||||
{
|
||||
$ro = new \ReflectionObject($object);
|
||||
|
|
@ -72,8 +70,8 @@ final class Expect
|
|||
: $ro->getProperties();
|
||||
|
||||
foreach ($props as $prop) {
|
||||
$item = &$items[$prop->getName()];
|
||||
if (!$item) {
|
||||
$name = $prop->getName();
|
||||
if (!isset($items[$name])) {
|
||||
$type = Helpers::getPropertyType($prop) ?? 'mixed';
|
||||
$item = new Type($type);
|
||||
if ($prop instanceof \ReflectionProperty ? $prop->isInitialized($object) : $prop->isOptional()) {
|
||||
|
|
@ -88,6 +86,7 @@ final class Expect
|
|||
} else {
|
||||
$item->required();
|
||||
}
|
||||
$items[$name] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +99,8 @@ final class Expect
|
|||
*/
|
||||
public static function array(?array $shape = []): Structure|Type
|
||||
{
|
||||
return Nette\Utils\Arrays::first($shape ?? []) instanceof Schema
|
||||
$shape ??= [];
|
||||
return Nette\Utils\Arrays::first($shape) instanceof Schema
|
||||
? (new Structure($shape))->castTo('array')
|
||||
: (new Type('array'))->default($shape);
|
||||
}
|
||||
|
|
|
|||
13
vendor/nette/schema/src/Schema/Helpers.php
vendored
13
vendor/nette/schema/src/Schema/Helpers.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -74,9 +72,9 @@ final class Helpers
|
|||
|
||||
/**
|
||||
* Returns an annotation value.
|
||||
* @param \ReflectionProperty $ref
|
||||
* @param \ReflectionClass<object>|\ReflectionProperty $ref
|
||||
*/
|
||||
public static function parseAnnotation(\Reflector $ref, string $name): ?string
|
||||
public static function parseAnnotation(\ReflectionClass|\ReflectionProperty $ref, string $name): ?string
|
||||
{
|
||||
if (!Reflection::areCommentsAvailable()) {
|
||||
throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
|
||||
|
|
@ -121,12 +119,13 @@ final class Helpers
|
|||
}
|
||||
|
||||
|
||||
/** @param array{?float, ?float} $range */
|
||||
public static function validateRange(mixed $value, array $range, Context $context, string $types = ''): void
|
||||
{
|
||||
if (is_array($value) || is_string($value)) {
|
||||
[$length, $label] = is_array($value)
|
||||
? [count($value), 'items']
|
||||
: (in_array('unicode', explode('|', $types), true)
|
||||
: (in_array('unicode', explode('|', $types), strict: true)
|
||||
? [Nette\Utils\Strings::length($value), 'characters']
|
||||
: [strlen($value), 'bytes']);
|
||||
|
||||
|
|
@ -147,6 +146,7 @@ final class Helpers
|
|||
}
|
||||
|
||||
|
||||
/** @param array{?float, ?float} $range */
|
||||
public static function isInRange(mixed $value, array $range): bool
|
||||
{
|
||||
return ($range[0] === null || $value >= $range[0])
|
||||
|
|
@ -166,6 +166,7 @@ final class Helpers
|
|||
}
|
||||
|
||||
|
||||
/** @return \Closure(mixed): mixed */
|
||||
public static function getCastStrategy(string $type): \Closure
|
||||
{
|
||||
if (Nette\Utils\Validators::isBuiltinType($type)) {
|
||||
|
|
|
|||
32
vendor/nette/schema/src/Schema/Message.php
vendored
32
vendor/nette/schema/src/Schema/Message.php
vendored
|
|
@ -1,16 +1,13 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
use function implode, preg_last_error_msg, preg_replace_callback;
|
||||
use function implode, preg_replace_callback;
|
||||
|
||||
|
||||
final class Message
|
||||
|
|
@ -63,22 +60,15 @@ final class Message
|
|||
/** @deprecated use Message::Deprecated */
|
||||
public const DEPRECATED = self::Deprecated;
|
||||
|
||||
public string $message;
|
||||
public string $code;
|
||||
|
||||
/** @var string[] */
|
||||
public array $path;
|
||||
|
||||
/** @var string[] */
|
||||
public array $variables;
|
||||
|
||||
|
||||
public function __construct(string $message, string $code, array $path, array $variables = [])
|
||||
{
|
||||
$this->message = $message;
|
||||
$this->code = $code;
|
||||
$this->path = $path;
|
||||
$this->variables = $variables;
|
||||
public function __construct(
|
||||
public string $message,
|
||||
public string $code,
|
||||
/** @var list<int|string> */
|
||||
public array $path,
|
||||
/** @var array<string, mixed> */
|
||||
public array $variables = [],
|
||||
) {
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -94,6 +84,6 @@ final class Message
|
|||
return preg_replace_callback('~( ?)%(\w+)%~', function ($m) use ($vars) {
|
||||
[, $space, $key] = $m;
|
||||
return $vars[$key] === null ? '' : $space . $vars[$key];
|
||||
}, $this->message) ?? throw new Nette\InvalidStateException(preg_last_error_msg());
|
||||
}, $this->message);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
10
vendor/nette/schema/src/Schema/Processor.php
vendored
10
vendor/nette/schema/src/Schema/Processor.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -17,6 +15,7 @@ use Nette;
|
|||
*/
|
||||
final class Processor
|
||||
{
|
||||
/** @var list<\Closure(Context): void> */
|
||||
public array $onNewContext = [];
|
||||
private Context $context;
|
||||
private bool $skipDefaults = false;
|
||||
|
|
@ -45,6 +44,7 @@ final class Processor
|
|||
|
||||
/**
|
||||
* Normalizes and validates and merges multiple data. Result is a clean completed data.
|
||||
* @param list<mixed> $dataset
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function processMultiple(Schema $schema, array $dataset): mixed
|
||||
|
|
@ -65,9 +65,7 @@ final class Processor
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
/** @return list<string> */
|
||||
public function getWarnings(): array
|
||||
{
|
||||
$res = [];
|
||||
|
|
|
|||
4
vendor/nette/schema/src/Schema/Schema.php
vendored
4
vendor/nette/schema/src/Schema/Schema.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Schema;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -17,23 +15,16 @@ use Nette;
|
|||
*/
|
||||
class ValidationException extends Nette\InvalidStateException
|
||||
{
|
||||
/** @var Message[] */
|
||||
private array $messages;
|
||||
|
||||
|
||||
/**
|
||||
* @param Message[] $messages
|
||||
*/
|
||||
public function __construct(?string $message, array $messages = [])
|
||||
{
|
||||
parent::__construct($message ?: $messages[0]->toString());
|
||||
$this->messages = $messages;
|
||||
public function __construct(
|
||||
?string $message,
|
||||
/** @var list<Message> */
|
||||
private array $messages = [],
|
||||
) {
|
||||
parent::__construct($message ?? $messages[0]->toString());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
/** @return list<string> */
|
||||
public function getMessages(): array
|
||||
{
|
||||
$res = [];
|
||||
|
|
@ -45,9 +36,7 @@ class ValidationException extends Nette\InvalidStateException
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Message[]
|
||||
*/
|
||||
/** @return list<Message> */
|
||||
public function getMessageObjects(): array
|
||||
{
|
||||
return $this->messages;
|
||||
|
|
|
|||
4
vendor/nette/utils/.phpstorm.meta.php
vendored
4
vendor/nette/utils/.phpstorm.meta.php
vendored
|
|
@ -1,6 +1,4 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
|
|
|
|||
61
vendor/nette/utils/composer.json
vendored
Normal file
61
vendor/nette/utils/composer.json
vendored
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "nette/utils",
|
||||
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
|
||||
"keywords": ["nette", "images", "json", "password", "validation", "utility", "string", "array", "core", "slugify", "utf-8", "unicode", "paginator", "datetime"],
|
||||
"homepage": "https://nette.org",
|
||||
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "8.2 - 8.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.5",
|
||||
"tracy/tracy": "^2.9",
|
||||
"phpstan/phpstan": "^2.1@stable",
|
||||
"phpstan/extension-installer": "^1.4@stable",
|
||||
"nette/phpstan-rules": "^1.0",
|
||||
"jetbrains/phpstorm-attributes": "^1.2"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/finder": "<3",
|
||||
"nette/schema": "<1.2.2"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
|
||||
"ext-json": "to use Nette\\Utils\\Json",
|
||||
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
|
||||
"ext-mbstring": "to use Strings::lower() etc...",
|
||||
"ext-gd": "to use Image",
|
||||
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"],
|
||||
"psr-4": {
|
||||
"Nette\\": "src"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"scripts": {
|
||||
"phpstan": "phpstan analyse",
|
||||
"tester": "tester tests -s"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "4.1-dev"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"phpstan/extension-installer": true
|
||||
}
|
||||
}
|
||||
}
|
||||
60
vendor/nette/utils/license.md
vendored
Normal file
60
vendor/nette/utils/license.md
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
Licenses
|
||||
========
|
||||
|
||||
Good news! You may use Nette Framework under the terms of either
|
||||
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
|
||||
|
||||
The BSD License is recommended for most projects. It is easy to understand and it
|
||||
places almost no restrictions on what you can do with the framework. If the GPL
|
||||
fits better to your project, you can use the framework under this license.
|
||||
|
||||
You don't have to notify anyone which license you are using. You can freely
|
||||
use Nette Framework in commercial projects as long as the copyright header
|
||||
remains intact.
|
||||
|
||||
Please be advised that the name "Nette Framework" is a protected trademark and its
|
||||
usage has some limitations. So please do not use word "Nette" in the name of your
|
||||
project or top-level domain, and choose a name that stands on its own merits.
|
||||
If your stuff is good, it will not take long to establish a reputation for yourselves.
|
||||
|
||||
|
||||
New BSD License
|
||||
---------------
|
||||
|
||||
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of "Nette Framework" nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
This software is provided by the copyright holders and contributors "as is" and
|
||||
any express or implied warranties, including, but not limited to, the implied
|
||||
warranties of merchantability and fitness for a particular purpose are
|
||||
disclaimed. In no event shall the copyright owner or contributors be liable for
|
||||
any direct, indirect, incidental, special, exemplary, or consequential damages
|
||||
(including, but not limited to, procurement of substitute goods or services;
|
||||
loss of use, data, or profits; or business interruption) however caused and on
|
||||
any theory of liability, whether in contract, strict liability, or tort
|
||||
(including negligence or otherwise) arising in any way out of the use of this
|
||||
software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
GNU General Public License
|
||||
--------------------------
|
||||
|
||||
GPL licenses are very very long, so instead of including them here we offer
|
||||
you URLs with full text:
|
||||
|
||||
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
|
||||
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
|
||||
55
vendor/nette/utils/readme.md
vendored
Normal file
55
vendor/nette/utils/readme.md
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
[](https://doc.nette.org/en/utils)
|
||||
|
||||
[](https://packagist.org/packages/nette/utils)
|
||||
[](https://github.com/nette/utils/actions)
|
||||
[](https://coveralls.io/github/nette/utils?branch=master)
|
||||
[](https://github.com/nette/utils/releases)
|
||||
[](https://github.com/nette/utils/blob/master/license.md)
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
In package nette/utils you will find a set of useful classes for everyday use:
|
||||
|
||||
✅ [Arrays](https://doc.nette.org/utils/arrays)<br>
|
||||
✅ [Callback](https://doc.nette.org/utils/callback) - PHP callbacks<br>
|
||||
✅ [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, …<br>
|
||||
✅ [Finder](https://doc.nette.org/utils/finder) - finds files and directories<br>
|
||||
✅ [Floats](https://doc.nette.org/utils/floats) - floating point numbers<br>
|
||||
✅ [Helper Functions](https://doc.nette.org/utils/helpers)<br>
|
||||
✅ [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML<br>
|
||||
✅ [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images<br>
|
||||
✅ [Iterables](https://doc.nette.org/utils/iterables) <br>
|
||||
✅ [JSON](https://doc.nette.org/utils/json) - encoding and decoding<br>
|
||||
✅ [Generating Random Strings](https://doc.nette.org/utils/random)<br>
|
||||
✅ [Paginator](https://doc.nette.org/utils/paginator) - pagination math<br>
|
||||
✅ [PHP Reflection](https://doc.nette.org/utils/reflection)<br>
|
||||
✅ [Strings](https://doc.nette.org/utils/strings) - useful text functions<br>
|
||||
✅ [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements<br>
|
||||
✅ [Type](https://doc.nette.org/utils/type) - PHP data type<br>
|
||||
✅ [Validation](https://doc.nette.org/utils/validators) - validate inputs<br>
|
||||
|
||||
<!---->
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
The recommended way to install is via Composer:
|
||||
|
||||
```
|
||||
composer require nette/utils
|
||||
```
|
||||
|
||||
Nette Utils 4.1 is compatible with PHP 8.2 to 8.5.
|
||||
|
||||
<!---->
|
||||
|
||||
[Support Me](https://github.com/sponsors/dg)
|
||||
--------------------------------------------
|
||||
|
||||
Do you like Nette Utils? Are you looking forward to the new features?
|
||||
|
||||
[](https://github.com/sponsors/dg)
|
||||
|
||||
Thank you!
|
||||
9
vendor/nette/utils/src/HtmlStringable.php
vendored
9
vendor/nette/utils/src/HtmlStringable.php
vendored
|
|
@ -1,19 +1,20 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Represents object convertible to HTML string.
|
||||
*/
|
||||
interface HtmlStringable
|
||||
{
|
||||
/**
|
||||
* Returns string in HTML format
|
||||
* Returns string in HTML format.
|
||||
*/
|
||||
function __toString(): string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,29 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Iterators;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Smarter caching iterator.
|
||||
* Enhanced caching iterator with first/last/counter tracking.
|
||||
*
|
||||
* @template TKey
|
||||
* @template TValue
|
||||
* @extends \CachingIterator<TKey, TValue, \Iterator<TKey, TValue>>
|
||||
* @property-read bool $first
|
||||
* @property-read bool $last
|
||||
* @property-read bool $empty
|
||||
* @property-read bool $odd
|
||||
* @property-read bool $even
|
||||
* @property-read int $counter
|
||||
* @property-read mixed $nextKey
|
||||
* @property-read mixed $nextValue
|
||||
* @property-read TKey $nextKey
|
||||
* @property-read TValue $nextValue
|
||||
*/
|
||||
class CachingIterator extends \CachingIterator implements \Countable
|
||||
{
|
||||
|
|
@ -31,6 +32,7 @@ class CachingIterator extends \CachingIterator implements \Countable
|
|||
private int $counter = 0;
|
||||
|
||||
|
||||
/** @param iterable<TKey, TValue>|\stdClass $iterable */
|
||||
public function __construct(iterable|\stdClass $iterable)
|
||||
{
|
||||
$iterable = $iterable instanceof \stdClass
|
||||
|
|
@ -58,45 +60,30 @@ class CachingIterator extends \CachingIterator implements \Countable
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the iterator empty?
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return $this->counter === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the counter odd?
|
||||
*/
|
||||
public function isOdd(): bool
|
||||
{
|
||||
return $this->counter % 2 === 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the counter even?
|
||||
*/
|
||||
public function isEven(): bool
|
||||
{
|
||||
return $this->counter % 2 === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the counter.
|
||||
*/
|
||||
public function getCounter(): int
|
||||
{
|
||||
return $this->counter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the count of elements.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
$inner = $this->getInnerIterator();
|
||||
|
|
@ -131,18 +118,14 @@ class CachingIterator extends \CachingIterator implements \Countable
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next key.
|
||||
*/
|
||||
/** @return TKey */
|
||||
public function getNextKey(): mixed
|
||||
{
|
||||
return $this->getInnerIterator()->key();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the next element.
|
||||
*/
|
||||
/** @return TValue */
|
||||
public function getNextValue(): mixed
|
||||
{
|
||||
return $this->getInnerIterator()->current();
|
||||
|
|
|
|||
9
vendor/nette/utils/src/Iterators/Mapper.php
vendored
9
vendor/nette/utils/src/Iterators/Mapper.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Iterators;
|
||||
|
||||
|
||||
|
|
@ -15,14 +13,13 @@ namespace Nette\Iterators;
|
|||
*/
|
||||
class Mapper extends \IteratorIterator
|
||||
{
|
||||
/** @var callable */
|
||||
private $callback;
|
||||
private \Closure $callback;
|
||||
|
||||
|
||||
public function __construct(\Traversable $iterator, callable $callback)
|
||||
{
|
||||
parent::__construct($iterator);
|
||||
$this->callback = $callback;
|
||||
$this->callback = $callback(...);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
7
vendor/nette/utils/src/SmartObject.php
vendored
7
vendor/nette/utils/src/SmartObject.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
use Nette\Utils\ObjectHelpers;
|
||||
|
|
@ -22,6 +20,7 @@ use Nette\Utils\ObjectHelpers;
|
|||
trait SmartObject
|
||||
{
|
||||
/**
|
||||
* @param mixed[] $args
|
||||
* @return mixed
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
|
|
@ -47,6 +46,8 @@ trait SmartObject
|
|||
|
||||
|
||||
/**
|
||||
* @param mixed[] $args
|
||||
* @return never
|
||||
* @throws MemberAccessException
|
||||
*/
|
||||
public static function __callStatic(string $name, array $args)
|
||||
|
|
|
|||
6
vendor/nette/utils/src/StaticClass.php
vendored
6
vendor/nette/utils/src/StaticClass.php
vendored
|
|
@ -1,17 +1,15 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Static class.
|
||||
* Prevents instantiation.
|
||||
*/
|
||||
trait StaticClass
|
||||
{
|
||||
|
|
|
|||
6
vendor/nette/utils/src/Translator.php
vendored
6
vendor/nette/utils/src/Translator.php
vendored
|
|
@ -1,17 +1,15 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Localization;
|
||||
|
||||
|
||||
/**
|
||||
* Translator adapter.
|
||||
* Translation provider.
|
||||
*/
|
||||
interface Translator
|
||||
{
|
||||
|
|
|
|||
10
vendor/nette/utils/src/Utils/ArrayHash.php
vendored
10
vendor/nette/utils/src/Utils/ArrayHash.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -14,7 +12,7 @@ use function count, is_array, is_scalar, sprintf;
|
|||
|
||||
|
||||
/**
|
||||
* Provides objects to work as array.
|
||||
* Array-like object with property access.
|
||||
* @template T
|
||||
* @implements \IteratorAggregate<array-key, T>
|
||||
* @implements \ArrayAccess<array-key, T>
|
||||
|
|
@ -39,7 +37,6 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
|
|||
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
* @return \Iterator<array-key, T>
|
||||
*/
|
||||
public function &getIterator(): \Iterator
|
||||
|
|
@ -50,9 +47,6 @@ class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \Iterator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns items count.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count((array) $this);
|
||||
|
|
|
|||
13
vendor/nette/utils/src/Utils/ArrayList.php
vendored
13
vendor/nette/utils/src/Utils/ArrayList.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -14,13 +12,14 @@ use function array_slice, array_splice, count, is_int;
|
|||
|
||||
|
||||
/**
|
||||
* Provides the base class for a generic list (items can be accessed by index).
|
||||
* Generic list with integer indices.
|
||||
* @template T
|
||||
* @implements \IteratorAggregate<int, T>
|
||||
* @implements \ArrayAccess<int, T>
|
||||
*/
|
||||
class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
||||
{
|
||||
/** @var list<T> */
|
||||
private array $list = [];
|
||||
|
||||
|
||||
|
|
@ -41,7 +40,6 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Returns an iterator over all items.
|
||||
* @return \Iterator<int, T>
|
||||
*/
|
||||
public function &getIterator(): \Iterator
|
||||
|
|
@ -52,9 +50,6 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns items count.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->list);
|
||||
|
|
@ -63,7 +58,7 @@ class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Replaces or appends an item.
|
||||
* @param int|null $index
|
||||
* @param ?int $index
|
||||
* @param T $value
|
||||
* @throws Nette\OutOfRangeException
|
||||
*/
|
||||
|
|
|
|||
70
vendor/nette/utils/src/Utils/Arrays.php
vendored
70
vendor/nette/utils/src/Utils/Arrays.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use JetBrains\PhpStorm\Language;
|
||||
|
|
@ -16,7 +14,7 @@ use const PREG_GREP_INVERT, PREG_SPLIT_DELIM_CAPTURE, PREG_SPLIT_NO_EMPTY;
|
|||
|
||||
|
||||
/**
|
||||
* Array tools library.
|
||||
* Array manipulation utilities.
|
||||
*/
|
||||
class Arrays
|
||||
{
|
||||
|
|
@ -72,14 +70,14 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as
|
||||
* the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains
|
||||
* the value from the first array in the case of a key collision.
|
||||
* Recursively merges two arrays. Useful for merging tree structures. Behaves like the + operator:
|
||||
* key/value pairs from the second array are added to the first, with the first array's values taking
|
||||
* precedence on key collisions. Nested arrays are merged recursively instead of replaced.
|
||||
* @template T1
|
||||
* @template T2
|
||||
* @param array<T1> $array1
|
||||
* @param array<T2> $array2
|
||||
* @return array<T1|T2>
|
||||
* @return array<T1|T2|array<mixed>>
|
||||
*/
|
||||
public static function mergeTree(array $array1, array $array2): array
|
||||
{
|
||||
|
|
@ -96,6 +94,7 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Returns zero-indexed position of given array key. Returns null if key is not found.
|
||||
* @param array<mixed> $array
|
||||
*/
|
||||
public static function getKeyOffset(array $array, string|int $key): ?int
|
||||
{
|
||||
|
|
@ -104,9 +103,10 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* @param array<mixed> $array
|
||||
* @deprecated use getKeyOffset()
|
||||
*/
|
||||
public static function searchKey(array $array, $key): ?int
|
||||
public static function searchKey(array $array, string|int $key): ?int
|
||||
{
|
||||
return self::getKeyOffset($array, $key);
|
||||
}
|
||||
|
|
@ -114,10 +114,11 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Tests an array for the presence of value.
|
||||
* @param array<mixed> $array
|
||||
*/
|
||||
public static function contains(array $array, mixed $value): bool
|
||||
{
|
||||
return in_array($value, $array, true);
|
||||
return in_array($value, $array, strict: true);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -125,9 +126,11 @@ class Arrays
|
|||
* Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
|
||||
* @template K of int|string
|
||||
* @template V
|
||||
* @template E
|
||||
* @param array<K, V> $array
|
||||
* @param ?callable(V, K, array<K, V>): bool $predicate
|
||||
* @return ?V
|
||||
* @param ?callable(): E $else
|
||||
* @return ($else is null ? ?V : V|E)
|
||||
*/
|
||||
public static function first(array $array, ?callable $predicate = null, ?callable $else = null): mixed
|
||||
{
|
||||
|
|
@ -142,9 +145,11 @@ class Arrays
|
|||
* Returns the last item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
|
||||
* @template K of int|string
|
||||
* @template V
|
||||
* @template E
|
||||
* @param array<K, V> $array
|
||||
* @param ?callable(V, K, array<K, V>): bool $predicate
|
||||
* @return ?V
|
||||
* @param ?callable(): E $else
|
||||
* @return ($else is null ? ?V : V|E)
|
||||
*/
|
||||
public static function last(array $array, ?callable $predicate = null, ?callable $else = null): mixed
|
||||
{
|
||||
|
|
@ -194,8 +199,10 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array immediately after the $key.
|
||||
* Inserts the contents of the $inserted array into the $array immediately before the $key.
|
||||
* If $key is null (or does not exist), it is inserted at the beginning.
|
||||
* @param array<mixed> $array
|
||||
* @param array<mixed> $inserted
|
||||
*/
|
||||
public static function insertBefore(array &$array, string|int|null $key, array $inserted): void
|
||||
{
|
||||
|
|
@ -207,8 +214,10 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Inserts the contents of the $inserted array into the $array before the $key.
|
||||
* Inserts the contents of the $inserted array into the $array immediately after the $key.
|
||||
* If $key is null (or does not exist), it is inserted at the end.
|
||||
* @param array<mixed> $array
|
||||
* @param array<mixed> $inserted
|
||||
*/
|
||||
public static function insertAfter(array &$array, string|int|null $key, array $inserted): void
|
||||
{
|
||||
|
|
@ -224,6 +233,7 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Renames key in array.
|
||||
* @param array<mixed> $array
|
||||
*/
|
||||
public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool
|
||||
{
|
||||
|
|
@ -260,6 +270,8 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Transforms multidimensional array to flat array.
|
||||
* @param array<mixed> $array
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function flatten(array $array, bool $preserveKeys = false): array
|
||||
{
|
||||
|
|
@ -283,17 +295,19 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Reformats table to associative tree. Path looks like 'field|field[]field->field=field'.
|
||||
* @param string|string[] $path
|
||||
* Transforms a flat array of rows into an associative tree using a path expression like 'field|field[]field->field=field'.
|
||||
* @param array<mixed> $array
|
||||
* @param string|list<string> $path
|
||||
* @return array<mixed>|\stdClass
|
||||
*/
|
||||
public static function associate(array $array, $path): array|\stdClass
|
||||
public static function associate(array $array, string|array $path): array|\stdClass
|
||||
{
|
||||
$parts = is_array($path)
|
||||
? $path
|
||||
: preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
|
||||
|
||||
if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') {
|
||||
throw new Nette\InvalidArgumentException("Invalid path '$path'.");
|
||||
throw new Nette\InvalidArgumentException("Invalid path '" . (is_array($path) ? implode('', $path) : $path) . "'.");
|
||||
}
|
||||
|
||||
$res = $parts[0] === '->' ? new \stdClass : [];
|
||||
|
|
@ -312,6 +326,8 @@ class Arrays
|
|||
$x = $row[$parts[$i]];
|
||||
$row = null;
|
||||
}
|
||||
break; // '=' is always the final operation
|
||||
|
||||
} elseif ($part === '->') {
|
||||
if (isset($parts[++$i])) {
|
||||
if ($x === null) {
|
||||
|
|
@ -337,7 +353,9 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling.
|
||||
* Converts array to associative: items with numeric keys are converted to keys, with $filling as their value.
|
||||
* @param array<mixed> $array
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function normalize(array $array, mixed $filling = null): array
|
||||
{
|
||||
|
|
@ -480,9 +498,10 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Invokes all callbacks and returns array of results.
|
||||
* @param callable[] $callbacks
|
||||
* @param iterable<callable> $callbacks
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function invoke(iterable $callbacks, ...$args): array
|
||||
public static function invoke(iterable $callbacks, mixed ...$args): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($callbacks as $k => $cb) {
|
||||
|
|
@ -495,9 +514,10 @@ class Arrays
|
|||
|
||||
/**
|
||||
* Invokes method on every object in an array and returns array of results.
|
||||
* @param object[] $objects
|
||||
* @param iterable<object> $objects
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function invokeMethod(iterable $objects, string $method, ...$args): array
|
||||
public static function invokeMethod(iterable $objects, string $method, mixed ...$args): array
|
||||
{
|
||||
$res = [];
|
||||
foreach ($objects as $k => $obj) {
|
||||
|
|
@ -511,6 +531,7 @@ class Arrays
|
|||
/**
|
||||
* Copies the elements of the $array array to the $object object and then returns it.
|
||||
* @template T of object
|
||||
* @param iterable<mixed> $array
|
||||
* @param T $object
|
||||
* @return T
|
||||
*/
|
||||
|
|
@ -534,8 +555,7 @@ class Arrays
|
|||
|
||||
|
||||
/**
|
||||
* Returns copy of the $array where every item is converted to string
|
||||
* and prefixed by $prefix and suffixed by $suffix.
|
||||
* Returns a copy of $array where every item is cast to string and wrapped with $prefix and $suffix.
|
||||
* @param string[] $array
|
||||
* @return string[]
|
||||
*/
|
||||
|
|
|
|||
27
vendor/nette/utils/src/Utils/Callback.php
vendored
27
vendor/nette/utils/src/Utils/Callback.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -22,21 +20,24 @@ final class Callback
|
|||
|
||||
/**
|
||||
* Invokes internal PHP function with own error handler.
|
||||
* @param callable-string $function
|
||||
* @param list<mixed> $args
|
||||
* @param callable(string, int): (bool|void|null) $onError
|
||||
*/
|
||||
public static function invokeSafe(string $function, array $args, callable $onError): mixed
|
||||
{
|
||||
$prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool {
|
||||
$prev = set_error_handler(function (int $severity, string $message, string $file, int $line) use ($onError, &$prev, $function): bool {
|
||||
if ($file === __FILE__) {
|
||||
$msg = ini_get('html_errors')
|
||||
? Html::htmlToText($message)
|
||||
: $message;
|
||||
$msg = preg_replace("#^$function\\(.*?\\): #", '', $msg);
|
||||
$msg = (string) preg_replace("#^$function\\(.*?\\): #", '', $msg);
|
||||
if ($onError($msg, $severity) !== false) {
|
||||
return null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $prev ? $prev(...func_get_args()) : false;
|
||||
return $prev ? $prev(...func_get_args()) !== false : false;
|
||||
});
|
||||
|
||||
try {
|
||||
|
|
@ -48,12 +49,12 @@ final class Callback
|
|||
|
||||
|
||||
/**
|
||||
* Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies
|
||||
* that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists.
|
||||
* Checks that $callable is a valid PHP callback and returns it. With $syntax set to true, only verifies
|
||||
* the structural validity without checking whether the class or method actually exists.
|
||||
* @return callable
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function check(mixed $callable, bool $syntax = false)
|
||||
public static function check(mixed $callable, bool $syntax = false): mixed
|
||||
{
|
||||
if (!is_callable($callable, $syntax)) {
|
||||
throw new Nette\InvalidArgumentException(
|
||||
|
|
@ -87,7 +88,7 @@ final class Callback
|
|||
* @param callable $callable type check is escalated to ReflectionException
|
||||
* @throws \ReflectionException if callback is not valid
|
||||
*/
|
||||
public static function toReflection($callable): \ReflectionMethod|\ReflectionFunction
|
||||
public static function toReflection(mixed $callable): \ReflectionMethod|\ReflectionFunction
|
||||
{
|
||||
if ($callable instanceof \Closure) {
|
||||
$callable = self::unwrap($callable);
|
||||
|
|
@ -100,6 +101,7 @@ final class Callback
|
|||
} elseif (is_object($callable) && !$callable instanceof \Closure) {
|
||||
return new ReflectionMethod($callable, '__invoke');
|
||||
} else {
|
||||
assert($callable instanceof \Closure || is_string($callable));
|
||||
return new \ReflectionFunction($callable);
|
||||
}
|
||||
}
|
||||
|
|
@ -116,8 +118,9 @@ final class Callback
|
|||
|
||||
/**
|
||||
* Unwraps closure created by Closure::fromCallable().
|
||||
* @return callable|array{object|class-string, string}|string
|
||||
*/
|
||||
public static function unwrap(\Closure $closure): callable|array
|
||||
public static function unwrap(\Closure $closure): callable|array|string
|
||||
{
|
||||
$r = new \ReflectionFunction($closure);
|
||||
$class = $r->getClosureScopeClass()?->name;
|
||||
|
|
|
|||
10
vendor/nette/utils/src/Utils/DateTime.php
vendored
10
vendor/nette/utils/src/Utils/DateTime.php
vendored
|
|
@ -1,19 +1,17 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use function array_merge, checkdate, implode, is_numeric, is_string, preg_replace_callback, sprintf, time, trim;
|
||||
|
||||
|
||||
/**
|
||||
* DateTime.
|
||||
* Extends PHP's DateTime with strict validation and additional factory methods.
|
||||
*/
|
||||
class DateTime extends \DateTime implements \JsonSerializable
|
||||
{
|
||||
|
|
@ -142,7 +140,7 @@ class DateTime extends \DateTime implements \JsonSerializable
|
|||
}
|
||||
|
||||
|
||||
private function apply(string $datetime, $timezone = null, bool $ctr = false): void
|
||||
private function apply(string $datetime, ?\DateTimeZone $timezone = null, bool $ctr = false): void
|
||||
{
|
||||
$relPart = '';
|
||||
$absPart = preg_replace_callback(
|
||||
|
|
@ -189,7 +187,7 @@ class DateTime extends \DateTime implements \JsonSerializable
|
|||
|
||||
|
||||
/**
|
||||
* You'd better use: (clone $dt)->modify(...)
|
||||
* Returns a modified copy of the object. Use (clone $dt)->modify(...) for better type safety.
|
||||
*/
|
||||
public function modifyClone(string $modify = ''): static
|
||||
{
|
||||
|
|
|
|||
16
vendor/nette/utils/src/Utils/FileInfo.php
vendored
16
vendor/nette/utils/src/Utils/FileInfo.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -19,14 +17,12 @@ use const DIRECTORY_SEPARATOR;
|
|||
*/
|
||||
final class FileInfo extends \SplFileInfo
|
||||
{
|
||||
private readonly string $relativePath;
|
||||
|
||||
|
||||
public function __construct(string $file, string $relativePath = '')
|
||||
{
|
||||
public function __construct(
|
||||
string $file,
|
||||
private readonly string $relativePath = '',
|
||||
) {
|
||||
parent::__construct($file);
|
||||
$this->setInfoClass(static::class);
|
||||
$this->relativePath = $relativePath;
|
||||
$this->setInfoClass(self::class);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
26
vendor/nette/utils/src/Utils/FileSystem.php
vendored
26
vendor/nette/utils/src/Utils/FileSystem.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -52,14 +50,15 @@ final class FileSystem
|
|||
} elseif (is_dir($origin)) {
|
||||
static::createDir($target);
|
||||
foreach (new \FilesystemIterator($target) as $item) {
|
||||
\assert($item instanceof \SplFileInfo);
|
||||
static::delete($item->getPathname());
|
||||
}
|
||||
|
||||
foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
|
||||
if ($item->isDir()) {
|
||||
static::createDir($target . '/' . $iterator->getSubPathName());
|
||||
static::createDir($target . '/' . $iterator->getSubPathname());
|
||||
} else {
|
||||
static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName());
|
||||
static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathname());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -112,6 +111,7 @@ final class FileSystem
|
|||
}
|
||||
} elseif (is_dir($path)) {
|
||||
foreach (new \FilesystemIterator($path) as $item) {
|
||||
\assert($item instanceof \SplFileInfo);
|
||||
static::delete($item->getPathname());
|
||||
}
|
||||
|
||||
|
|
@ -208,7 +208,7 @@ final class FileSystem
|
|||
|
||||
|
||||
/**
|
||||
* Writes the string to a file.
|
||||
* Writes the string to a file. Creates the parent directory if it does not exist. Pass null as $mode to skip chmod.
|
||||
* @throws Nette\IOException on error occurred
|
||||
*/
|
||||
public static function write(string $file, string $content, ?int $mode = 0o666): void
|
||||
|
|
@ -251,6 +251,7 @@ final class FileSystem
|
|||
}
|
||||
} elseif (is_dir($path)) {
|
||||
foreach (new \FilesystemIterator($path) as $item) {
|
||||
\assert($item instanceof \SplFileInfo);
|
||||
static::makeWritable($item->getPathname(), $dirMode, $fileMode);
|
||||
}
|
||||
|
||||
|
|
@ -277,6 +278,19 @@ final class FileSystem
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether the string is a valid cross-platform filename without any path information.
|
||||
*/
|
||||
public static function isValidFilename(string $name): bool
|
||||
{
|
||||
[$stem] = explode('.', $name, 2);
|
||||
return $name !== '' && $name !== '.' && $name !== '..'
|
||||
&& !preg_match('#[\x00-\x1F<>:"|?*\\\/]#', $name) // control and reserved characters
|
||||
&& !str_ends_with($name, '.') && !str_ends_with($name, ' ') // trailing dots/spaces
|
||||
&& !preg_match('#^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$#i', $stem); // Windows reserved device names
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes `..` and `.` and directory separators in path.
|
||||
*/
|
||||
|
|
|
|||
45
vendor/nette/utils/src/Utils/Finder.php
vendored
45
vendor/nette/utils/src/Utils/Finder.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -15,7 +13,7 @@ use const GLOB_NOESCAPE, GLOB_NOSORT, GLOB_ONLYDIR;
|
|||
|
||||
|
||||
/**
|
||||
* Finder allows searching through directory trees using iterator.
|
||||
* Searches for files and directories in directory trees.
|
||||
*
|
||||
* Finder::findFiles('*.php')
|
||||
* ->size('> 10kB')
|
||||
|
|
@ -32,24 +30,25 @@ class Finder implements \IteratorAggregate
|
|||
/** @var string[] */
|
||||
private array $in = [];
|
||||
|
||||
/** @var \Closure[] */
|
||||
/** @var array<\Closure(FileInfo): bool> */
|
||||
private array $filters = [];
|
||||
|
||||
/** @var \Closure[] */
|
||||
/** @var array<\Closure(FileInfo): bool> */
|
||||
private array $descentFilters = [];
|
||||
|
||||
/** @var array<string|self> */
|
||||
private array $appends = [];
|
||||
private bool $childFirst = false;
|
||||
|
||||
/** @var ?callable */
|
||||
private $sort;
|
||||
/** @var ?(\Closure(FileInfo, FileInfo): int) */
|
||||
private ?\Closure $sort = null;
|
||||
private int $maxDepth = -1;
|
||||
private bool $ignoreUnreadableDirs = true;
|
||||
|
||||
|
||||
/**
|
||||
* Begins search for files and directories matching mask.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public static function find(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -60,6 +59,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Begins search for files matching mask.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public static function findFiles(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -70,6 +70,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Begins search for directories matching mask.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public static function findDirectories(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -80,6 +81,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Finds files matching the specified masks.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public function files(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -89,6 +91,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Finds directories matching the specified masks.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public function directories(string|array $masks = ['*']): static
|
||||
{
|
||||
|
|
@ -96,6 +99,7 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/** @param list<string> $masks */
|
||||
private function addMask(array $masks, string $mode): static
|
||||
{
|
||||
foreach ($masks as $mask) {
|
||||
|
|
@ -117,6 +121,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Searches in the given directories. Wildcards are allowed.
|
||||
* @param string|list<string> $paths
|
||||
*/
|
||||
public function in(string|array $paths): static
|
||||
{
|
||||
|
|
@ -128,6 +133,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Searches recursively from the given directories. Wildcards are allowed.
|
||||
* @param string|list<string> $paths
|
||||
*/
|
||||
public function from(string|array $paths): static
|
||||
{
|
||||
|
|
@ -137,6 +143,7 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/** @param list<string> $paths */
|
||||
private function addLocation(array $paths, string $ext): void
|
||||
{
|
||||
foreach ($paths as $path) {
|
||||
|
|
@ -170,12 +177,12 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Set a compare function for sorting directory entries. The function will be called to sort entries from the same directory.
|
||||
* Sets a comparison function for sorting entries within each directory.
|
||||
* @param callable(FileInfo, FileInfo): int $callback
|
||||
*/
|
||||
public function sortBy(callable $callback): static
|
||||
{
|
||||
$this->sort = $callback;
|
||||
$this->sort = $callback(...);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +198,8 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
|
||||
/**
|
||||
* Adds the specified paths or appends a new finder that returns.
|
||||
* Appends the specified file paths to results. Passing null creates and returns a new sub-finder whose results are appended.
|
||||
* @param string|list<string>|null $paths
|
||||
*/
|
||||
public function append(string|array|null $paths = null): static
|
||||
{
|
||||
|
|
@ -209,6 +217,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Skips entries that matches the given masks relative to the ones defined with the in() or from() methods.
|
||||
* @param string|list<string> $masks
|
||||
*/
|
||||
public function exclude(string|array $masks): static
|
||||
{
|
||||
|
|
@ -239,7 +248,7 @@ class Finder implements \IteratorAggregate
|
|||
*/
|
||||
public function filter(callable $callback): static
|
||||
{
|
||||
$this->filters[] = \Closure::fromCallable($callback);
|
||||
$this->filters[] = $callback(...);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +259,7 @@ class Finder implements \IteratorAggregate
|
|||
*/
|
||||
public function descentFilter(callable $callback): static
|
||||
{
|
||||
$this->descentFilters[] = \Closure::fromCallable($callback);
|
||||
$this->descentFilters[] = $callback(...);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
@ -267,6 +276,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB
|
||||
* @param '>'|'>='|'<'|'<='|'='|'=='|'==='|'!='|'!=='|'<>' $operator or predicate string
|
||||
*/
|
||||
public function size(string $operator, ?int $size = null): static
|
||||
{
|
||||
|
|
@ -277,7 +287,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
[, $operator, $size, $unit] = $matches;
|
||||
$units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9];
|
||||
$size *= $units[strtolower($unit)];
|
||||
$size = (float) $size * $units[strtolower($unit)];
|
||||
$operator = $operator ?: '=';
|
||||
}
|
||||
|
||||
|
|
@ -287,6 +297,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23
|
||||
* @param '>'|'>='|'<'|'<='|'='|'=='|'==='|'!='|'!=='|'<>' $operator or predicate string
|
||||
*/
|
||||
public function date(string $operator, string|int|\DateTimeInterface|null $date = null): static
|
||||
{
|
||||
|
|
@ -401,6 +412,7 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/** @param iterable<string> $pathNames */
|
||||
private function convertToFiles(iterable $pathNames, string $relativePath, bool $absolute): \Generator
|
||||
{
|
||||
foreach ($pathNames as $pathName) {
|
||||
|
|
@ -413,6 +425,10 @@ class Finder implements \IteratorAggregate
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param (\Closure(FileInfo): bool)[] $filters
|
||||
* @param array<int, bool> $cache
|
||||
*/
|
||||
private function proveFilters(array $filters, FileInfo $file, array &$cache): bool
|
||||
{
|
||||
foreach ($filters as $filter) {
|
||||
|
|
@ -468,6 +484,7 @@ class Finder implements \IteratorAggregate
|
|||
|
||||
/**
|
||||
* Since glob() does not know ** wildcard, we divide the path into a part for glob and a part for manual traversal.
|
||||
* @return array{string, string, bool}
|
||||
*/
|
||||
private static function splitRecursivePart(string $path): array
|
||||
{
|
||||
|
|
|
|||
4
vendor/nette/utils/src/Utils/Floats.php
vendored
4
vendor/nette/utils/src/Utils/Floats.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
|
|||
14
vendor/nette/utils/src/Utils/Helpers.php
vendored
14
vendor/nette/utils/src/Utils/Helpers.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -14,6 +12,9 @@ use function array_unique, ini_get, levenshtein, max, min, ob_end_clean, ob_get_
|
|||
use const PHP_OS_FAMILY;
|
||||
|
||||
|
||||
/**
|
||||
* Miscellaneous utilities.
|
||||
*/
|
||||
class Helpers
|
||||
{
|
||||
public const IsWindows = PHP_OS_FAMILY === 'Windows';
|
||||
|
|
@ -21,6 +22,7 @@ class Helpers
|
|||
|
||||
/**
|
||||
* Executes a callback and returns the captured output as a string.
|
||||
* @param callable(): void $func
|
||||
*/
|
||||
public static function capture(callable $func): string
|
||||
{
|
||||
|
|
@ -37,7 +39,7 @@ class Helpers
|
|||
|
||||
/**
|
||||
* Returns the last occurred PHP error or an empty string if no error occurred. Unlike error_get_last(),
|
||||
* it is nit affected by the PHP directive html_errors and always returns text, not HTML.
|
||||
* it is not affected by the PHP directive html_errors and always returns text, not HTML.
|
||||
*/
|
||||
public static function getLastError(): string
|
||||
{
|
||||
|
|
@ -59,6 +61,7 @@ class Helpers
|
|||
|
||||
/**
|
||||
* Returns value clamped to the inclusive range of min and max.
|
||||
* @return ($value is float ? float : ($min is float ? float : ($max is float ? float : int)))
|
||||
*/
|
||||
public static function clamp(int|float $value, int|float $min, int|float $max): int|float
|
||||
{
|
||||
|
|
@ -71,7 +74,7 @@ class Helpers
|
|||
|
||||
|
||||
/**
|
||||
* Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding).
|
||||
* Finds the string from $possibilities most similar to $value using Levenshtein distance, or null if none is close enough.
|
||||
* @param string[] $possibilities
|
||||
*/
|
||||
public static function getSuggestion(array $possibilities, string $value): ?string
|
||||
|
|
@ -91,6 +94,7 @@ class Helpers
|
|||
|
||||
/**
|
||||
* Compares two values in the same way that PHP does. Recognizes operators: >, >=, <, <=, =, ==, ===, !=, !==, <>
|
||||
* @param '>'|'>='|'<'|'<='|'='|'=='|'==='|'!='|'!=='|'<>' $operator
|
||||
*/
|
||||
public static function compare(mixed $left, string $operator, mixed $right): bool
|
||||
{
|
||||
|
|
|
|||
261
vendor/nette/utils/src/Utils/Html.php
vendored
261
vendor/nette/utils/src/Utils/Html.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette\HtmlStringable;
|
||||
|
|
@ -15,115 +13,115 @@ use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
|
|||
|
||||
|
||||
/**
|
||||
* HTML helper.
|
||||
* Generates HTML elements with automatic attribute escaping.
|
||||
*
|
||||
* @property string|null $accept
|
||||
* @property string|null $accesskey
|
||||
* @property string|null $action
|
||||
* @property string|null $align
|
||||
* @property string|null $allow
|
||||
* @property string|null $alt
|
||||
* @property bool|null $async
|
||||
* @property string|null $autocapitalize
|
||||
* @property string|null $autocomplete
|
||||
* @property bool|null $autofocus
|
||||
* @property bool|null $autoplay
|
||||
* @property string|null $charset
|
||||
* @property bool|null $checked
|
||||
* @property string|null $cite
|
||||
* @property string|null $class
|
||||
* @property int|null $cols
|
||||
* @property int|null $colspan
|
||||
* @property string|null $content
|
||||
* @property bool|null $contenteditable
|
||||
* @property bool|null $controls
|
||||
* @property string|null $coords
|
||||
* @property string|null $crossorigin
|
||||
* @property string|null $data
|
||||
* @property string|null $datetime
|
||||
* @property string|null $decoding
|
||||
* @property bool|null $default
|
||||
* @property bool|null $defer
|
||||
* @property string|null $dir
|
||||
* @property string|null $dirname
|
||||
* @property bool|null $disabled
|
||||
* @property bool|null $download
|
||||
* @property string|null $draggable
|
||||
* @property string|null $dropzone
|
||||
* @property string|null $enctype
|
||||
* @property string|null $for
|
||||
* @property string|null $form
|
||||
* @property string|null $formaction
|
||||
* @property string|null $formenctype
|
||||
* @property string|null $formmethod
|
||||
* @property bool|null $formnovalidate
|
||||
* @property string|null $formtarget
|
||||
* @property string|null $headers
|
||||
* @property int|null $height
|
||||
* @property bool|null $hidden
|
||||
* @property float|null $high
|
||||
* @property string|null $href
|
||||
* @property string|null $hreflang
|
||||
* @property string|null $id
|
||||
* @property string|null $integrity
|
||||
* @property string|null $inputmode
|
||||
* @property bool|null $ismap
|
||||
* @property string|null $itemprop
|
||||
* @property string|null $kind
|
||||
* @property string|null $label
|
||||
* @property string|null $lang
|
||||
* @property string|null $list
|
||||
* @property bool|null $loop
|
||||
* @property float|null $low
|
||||
* @property float|null $max
|
||||
* @property int|null $maxlength
|
||||
* @property int|null $minlength
|
||||
* @property string|null $media
|
||||
* @property string|null $method
|
||||
* @property float|null $min
|
||||
* @property bool|null $multiple
|
||||
* @property bool|null $muted
|
||||
* @property string|null $name
|
||||
* @property bool|null $novalidate
|
||||
* @property bool|null $open
|
||||
* @property float|null $optimum
|
||||
* @property string|null $pattern
|
||||
* @property string|null $ping
|
||||
* @property string|null $placeholder
|
||||
* @property string|null $poster
|
||||
* @property string|null $preload
|
||||
* @property string|null $radiogroup
|
||||
* @property bool|null $readonly
|
||||
* @property string|null $rel
|
||||
* @property bool|null $required
|
||||
* @property bool|null $reversed
|
||||
* @property int|null $rows
|
||||
* @property int|null $rowspan
|
||||
* @property string|null $sandbox
|
||||
* @property string|null $scope
|
||||
* @property bool|null $selected
|
||||
* @property string|null $shape
|
||||
* @property int|null $size
|
||||
* @property string|null $sizes
|
||||
* @property string|null $slot
|
||||
* @property int|null $span
|
||||
* @property string|null $spellcheck
|
||||
* @property string|null $src
|
||||
* @property string|null $srcdoc
|
||||
* @property string|null $srclang
|
||||
* @property string|null $srcset
|
||||
* @property int|null $start
|
||||
* @property float|null $step
|
||||
* @property string|null $style
|
||||
* @property int|null $tabindex
|
||||
* @property string|null $target
|
||||
* @property string|null $title
|
||||
* @property string|null $translate
|
||||
* @property string|null $type
|
||||
* @property string|null $usemap
|
||||
* @property string|null $value
|
||||
* @property int|null $width
|
||||
* @property string|null $wrap
|
||||
* @property ?string $accept
|
||||
* @property ?string $accesskey
|
||||
* @property ?string $action
|
||||
* @property ?string $align
|
||||
* @property ?string $allow
|
||||
* @property ?string $alt
|
||||
* @property ?bool $async
|
||||
* @property ?string $autocapitalize
|
||||
* @property ?string $autocomplete
|
||||
* @property ?bool $autofocus
|
||||
* @property ?bool $autoplay
|
||||
* @property ?string $charset
|
||||
* @property ?bool $checked
|
||||
* @property ?string $cite
|
||||
* @property ?string $class
|
||||
* @property ?int $cols
|
||||
* @property ?int $colspan
|
||||
* @property ?string $content
|
||||
* @property ?bool $contenteditable
|
||||
* @property ?bool $controls
|
||||
* @property ?string $coords
|
||||
* @property ?string $crossorigin
|
||||
* @property ?string $data
|
||||
* @property ?string $datetime
|
||||
* @property ?string $decoding
|
||||
* @property ?bool $default
|
||||
* @property ?bool $defer
|
||||
* @property ?string $dir
|
||||
* @property ?string $dirname
|
||||
* @property ?bool $disabled
|
||||
* @property ?bool $download
|
||||
* @property ?string $draggable
|
||||
* @property ?string $dropzone
|
||||
* @property ?string $enctype
|
||||
* @property ?string $for
|
||||
* @property ?string $form
|
||||
* @property ?string $formaction
|
||||
* @property ?string $formenctype
|
||||
* @property ?string $formmethod
|
||||
* @property ?bool $formnovalidate
|
||||
* @property ?string $formtarget
|
||||
* @property ?string $headers
|
||||
* @property ?int $height
|
||||
* @property ?bool $hidden
|
||||
* @property ?float $high
|
||||
* @property ?string $href
|
||||
* @property ?string $hreflang
|
||||
* @property ?string $id
|
||||
* @property ?string $integrity
|
||||
* @property ?string $inputmode
|
||||
* @property ?bool $ismap
|
||||
* @property ?string $itemprop
|
||||
* @property ?string $kind
|
||||
* @property ?string $label
|
||||
* @property ?string $lang
|
||||
* @property ?string $list
|
||||
* @property ?bool $loop
|
||||
* @property ?float $low
|
||||
* @property ?float $max
|
||||
* @property ?int $maxlength
|
||||
* @property ?int $minlength
|
||||
* @property ?string $media
|
||||
* @property ?string $method
|
||||
* @property ?float $min
|
||||
* @property ?bool $multiple
|
||||
* @property ?bool $muted
|
||||
* @property ?string $name
|
||||
* @property ?bool $novalidate
|
||||
* @property ?bool $open
|
||||
* @property ?float $optimum
|
||||
* @property ?string $pattern
|
||||
* @property ?string $ping
|
||||
* @property ?string $placeholder
|
||||
* @property ?string $poster
|
||||
* @property ?string $preload
|
||||
* @property ?string $radiogroup
|
||||
* @property ?bool $readonly
|
||||
* @property ?string $rel
|
||||
* @property ?bool $required
|
||||
* @property ?bool $reversed
|
||||
* @property ?int $rows
|
||||
* @property ?int $rowspan
|
||||
* @property ?string $sandbox
|
||||
* @property ?string $scope
|
||||
* @property ?bool $selected
|
||||
* @property ?string $shape
|
||||
* @property ?int $size
|
||||
* @property ?string $sizes
|
||||
* @property ?string $slot
|
||||
* @property ?int $span
|
||||
* @property ?string $spellcheck
|
||||
* @property ?string $src
|
||||
* @property ?string $srcdoc
|
||||
* @property ?string $srclang
|
||||
* @property ?string $srcset
|
||||
* @property ?int $start
|
||||
* @property ?float $step
|
||||
* @property ?string $style
|
||||
* @property ?int $tabindex
|
||||
* @property ?string $target
|
||||
* @property ?string $title
|
||||
* @property ?string $translate
|
||||
* @property ?string $type
|
||||
* @property ?string $usemap
|
||||
* @property ?string $value
|
||||
* @property ?int $width
|
||||
* @property ?string $wrap
|
||||
*
|
||||
* @method self accept(?string $val)
|
||||
* @method self accesskey(?string $val, bool $state = null)
|
||||
|
|
@ -230,20 +228,23 @@ use const ENT_HTML5, ENT_NOQUOTES, ENT_QUOTES;
|
|||
* @method self value(?string $val)
|
||||
* @method self width(?int $val)
|
||||
* @method self wrap(?string $val)
|
||||
*
|
||||
* @implements \IteratorAggregate<int, self|string>
|
||||
* @implements \ArrayAccess<int, self|string>
|
||||
*/
|
||||
class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringable
|
||||
{
|
||||
/** @var array<string, mixed> element's attributes */
|
||||
public array $attrs = [];
|
||||
|
||||
/** void elements */
|
||||
/** @var array<string, int> void elements */
|
||||
public static array $emptyElements = [
|
||||
'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1,
|
||||
'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1,
|
||||
'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1,
|
||||
];
|
||||
|
||||
/** @var array<int, HtmlStringable|string> nodes */
|
||||
/** @var array<int, self|string> nodes */
|
||||
protected array $children = [];
|
||||
|
||||
/** element's name */
|
||||
|
|
@ -254,7 +255,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Constructs new HTML element.
|
||||
* @param array|string $attrs element's attributes or plain text content
|
||||
* @param array<string, mixed>|string|null $attrs element's attributes or plain text content
|
||||
*/
|
||||
public static function el(?string $name = null, array|string|null $attrs = null): static
|
||||
{
|
||||
|
|
@ -345,7 +346,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Is element empty?
|
||||
* Checks whether the element is a void (self-closing) element.
|
||||
*/
|
||||
final public function isEmpty(): bool
|
||||
{
|
||||
|
|
@ -355,6 +356,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Sets multiple attributes.
|
||||
* @param array<string, mixed> $attrs
|
||||
*/
|
||||
public function addAttributes(array $attrs): static
|
||||
{
|
||||
|
|
@ -417,6 +419,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Unsets element's attributes.
|
||||
* @param list<string> $attributes
|
||||
*/
|
||||
public function removeAttributes(array $attributes): static
|
||||
{
|
||||
|
|
@ -429,7 +432,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded setter for element's attribute.
|
||||
* Sets element's attribute via property assignment.
|
||||
*/
|
||||
final public function __set(string $name, mixed $value): void
|
||||
{
|
||||
|
|
@ -438,7 +441,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded getter for element's attribute.
|
||||
* Returns element's attribute via property access.
|
||||
*/
|
||||
final public function &__get(string $name): mixed
|
||||
{
|
||||
|
|
@ -447,7 +450,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded tester for element's attribute.
|
||||
* Checks if element's attribute is set.
|
||||
*/
|
||||
final public function __isset(string $name): bool
|
||||
{
|
||||
|
|
@ -456,7 +459,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded unsetter for element's attribute.
|
||||
* Unsets element's attribute via property unset.
|
||||
*/
|
||||
final public function __unset(string $name): void
|
||||
{
|
||||
|
|
@ -465,7 +468,8 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Overloaded setter for element's attribute.
|
||||
* Sets or returns element's attribute via method call.
|
||||
* @param mixed[] $args
|
||||
*/
|
||||
final public function __call(string $m, array $args): mixed
|
||||
{
|
||||
|
|
@ -496,6 +500,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Special setter for element's attribute.
|
||||
* @param array<string, mixed> $query
|
||||
*/
|
||||
final public function href(string $path, array $query = []): static
|
||||
{
|
||||
|
|
@ -594,6 +599,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Creates and adds a new Html child.
|
||||
* @param array<string, mixed>|string|null $attrs
|
||||
*/
|
||||
final public function create(string $name, array|string|null $attrs = null): static
|
||||
{
|
||||
|
|
@ -621,7 +627,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Inserts (replaces) child node (\ArrayAccess implementation).
|
||||
* @param int|null $index position or null for appending
|
||||
* @param ?int $index position or null for appending
|
||||
* @param Html|string $child Html node or raw HTML string
|
||||
*/
|
||||
final public function offsetSet($index, $child): void
|
||||
|
|
@ -634,7 +640,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
* Returns child node (\ArrayAccess implementation).
|
||||
* @param int $index
|
||||
*/
|
||||
final public function offsetGet($index): HtmlStringable|string
|
||||
final public function offsetGet($index): self|string
|
||||
{
|
||||
return $this->children[$index];
|
||||
}
|
||||
|
|
@ -682,7 +688,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Iterates over elements.
|
||||
* @return \ArrayIterator<int, HtmlStringable|string>
|
||||
* @return \ArrayIterator<int, self|string>
|
||||
*/
|
||||
final public function getIterator(): \ArrayIterator
|
||||
{
|
||||
|
|
@ -692,6 +698,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
/**
|
||||
* Returns all children.
|
||||
* @return array<int, self|string>
|
||||
*/
|
||||
final public function getChildren(): array
|
||||
{
|
||||
|
|
@ -700,7 +707,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
|
||||
|
||||
/**
|
||||
* Renders element's start tag, content and end tag.
|
||||
* Renders element's start tag, content and end tag. Pass indent level to enable pretty-printing.
|
||||
*/
|
||||
final public function render(?int $indent = null): string
|
||||
{
|
||||
|
|
@ -764,10 +771,6 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
*/
|
||||
final public function attributes(): string
|
||||
{
|
||||
if (!is_array($this->attrs)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$s = '';
|
||||
$attrs = $this->attrs;
|
||||
foreach ($attrs as $key => $value) {
|
||||
|
|
@ -780,7 +783,7 @@ class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringab
|
|||
continue;
|
||||
|
||||
} elseif (is_array($value)) {
|
||||
if (strncmp($key, 'data-', 5) === 0) {
|
||||
if (str_starts_with($key, 'data-')) {
|
||||
$value = Json::encode($value);
|
||||
|
||||
} else {
|
||||
|
|
|
|||
173
vendor/nette/utils/src/Utils/Image.php
vendored
173
vendor/nette/utils/src/Utils/Image.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -24,7 +22,7 @@ use const IMG_BMP, IMG_FLIP_BOTH, IMG_FLIP_HORIZONTAL, IMG_FLIP_VERTICAL, IMG_GI
|
|||
* $image->send();
|
||||
* </code>
|
||||
*
|
||||
* @method Image affine(array $affine, ?array $clip = null)
|
||||
* @method Image affine(array<int, float|int> $affine, ?array{x: int, y: int, width: int, height: int} $clip = null)
|
||||
* @method void alphaBlending(bool $enable)
|
||||
* @method void antialias(bool $enable)
|
||||
* @method void arc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color)
|
||||
|
|
@ -41,51 +39,51 @@ use const IMG_BMP, IMG_FLIP_BOTH, IMG_FLIP_HORIZONTAL, IMG_FLIP_VERTICAL, IMG_GI
|
|||
* @method int colorResolve(int $red, int $green, int $blue)
|
||||
* @method int colorResolveAlpha(int $red, int $green, int $blue, int $alpha)
|
||||
* @method void colorSet(int $index, int $red, int $green, int $blue, int $alpha = 0)
|
||||
* @method array colorsForIndex(int $color)
|
||||
* @method array{red: int, green: int, blue: int, alpha: int} colorsForIndex(int $color)
|
||||
* @method int colorsTotal()
|
||||
* @method int colorTransparent(?int $color = null)
|
||||
* @method void convolution(array $matrix, float $div, float $offset)
|
||||
* @method void convolution(array<int, array<int, float>> $matrix, float $div, float $offset)
|
||||
* @method void copy(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH)
|
||||
* @method void copyMerge(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct)
|
||||
* @method void copyMergeGray(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $srcW, int $srcH, int $pct)
|
||||
* @method void copyResampled(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH)
|
||||
* @method void copyResized(Image $src, int $dstX, int $dstY, int $srcX, int $srcY, int $dstW, int $dstH, int $srcW, int $srcH)
|
||||
* @method Image cropAuto(int $mode = IMG_CROP_DEFAULT, float $threshold = .5, ?ImageColor $color = null)
|
||||
* @method Image cropAuto(int $mode = 0, float $threshold = .5, ?ImageColor $color = null)
|
||||
* @method void ellipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color)
|
||||
* @method void fill(int $x, int $y, ImageColor $color)
|
||||
* @method void filledArc(int $centerX, int $centerY, int $width, int $height, int $startAngle, int $endAngle, ImageColor $color, int $style)
|
||||
* @method void filledEllipse(int $centerX, int $centerY, int $width, int $height, ImageColor $color)
|
||||
* @method void filledPolygon(array $points, ImageColor $color)
|
||||
* @method void filledPolygon(array<int, int> $points, ImageColor $color)
|
||||
* @method void filledRectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color)
|
||||
* @method void fillToBorder(int $x, int $y, ImageColor $borderColor, ImageColor $color)
|
||||
* @method void filter(int $filter, ...$args)
|
||||
* @method void flip(int $mode)
|
||||
* @method array ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array $options = [])
|
||||
* @method array<int, int> ftText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontFile, string $text, array<string, mixed> $options = [])
|
||||
* @method void gammaCorrect(float $inputgamma, float $outputgamma)
|
||||
* @method array getClip()
|
||||
* @method array{int, int, int, int} getClip()
|
||||
* @method int getInterpolation()
|
||||
* @method int interlace(?bool $enable = null)
|
||||
* @method bool isTrueColor()
|
||||
* @method void layerEffect(int $effect)
|
||||
* @method void line(int $x1, int $y1, int $x2, int $y2, ImageColor $color)
|
||||
* @method void openPolygon(array $points, ImageColor $color)
|
||||
* @method void openPolygon(array<int, int> $points, ImageColor $color)
|
||||
* @method void paletteCopy(Image $source)
|
||||
* @method void paletteToTrueColor()
|
||||
* @method void polygon(array $points, ImageColor $color)
|
||||
* @method void polygon(array<int, int> $points, ImageColor $color)
|
||||
* @method void rectangle(int $x1, int $y1, int $x2, int $y2, ImageColor $color)
|
||||
* @method mixed resolution(?int $resolutionX = null, ?int $resolutionY = null)
|
||||
* @method Image rotate(float $angle, ImageColor $backgroundColor)
|
||||
* @method void saveAlpha(bool $enable)
|
||||
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED)
|
||||
* @method Image scale(int $newWidth, int $newHeight = -1, int $mode = 3)
|
||||
* @method void setBrush(Image $brush)
|
||||
* @method void setClip(int $x1, int $y1, int $x2, int $y2)
|
||||
* @method void setInterpolation(int $method = IMG_BILINEAR_FIXED)
|
||||
* @method void setInterpolation(int $method = 3)
|
||||
* @method void setPixel(int $x, int $y, ImageColor $color)
|
||||
* @method void setStyle(array $style)
|
||||
* @method void setStyle(array<int, int> $style)
|
||||
* @method void setThickness(int $thickness)
|
||||
* @method void setTile(Image $tile)
|
||||
* @method void trueColorToPalette(bool $dither, int $ncolors)
|
||||
* @method array ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontfile, string $text, array $options = [])
|
||||
* @method array<int, int> ttfText(float $size, float $angle, int $x, int $y, ImageColor $color, string $fontfile, string $text, array<string, mixed> $options = [])
|
||||
* @property-read positive-int $width
|
||||
* @property-read positive-int $height
|
||||
* @property-read \GdImage $imageResource
|
||||
|
|
@ -139,6 +137,7 @@ class Image
|
|||
public const EmptyGIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
|
||||
|
||||
private const Formats = [ImageType::JPEG => 'jpeg', ImageType::PNG => 'png', ImageType::GIF => 'gif', ImageType::WEBP => 'webp', ImageType::AVIF => 'avif', ImageType::BMP => 'bmp'];
|
||||
private const Sentinel = "\0";
|
||||
|
||||
private \GdImage $image;
|
||||
|
||||
|
|
@ -146,6 +145,7 @@ class Image
|
|||
/**
|
||||
* Returns RGB color (0..255) and transparency (0..127).
|
||||
* @deprecated use ImageColor::rgb()
|
||||
* @return array{red: int, green: int, blue: int, alpha: int}
|
||||
*/
|
||||
public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array
|
||||
{
|
||||
|
|
@ -159,11 +159,13 @@ class Image
|
|||
|
||||
|
||||
/**
|
||||
* Reads an image from a file and returns its type in $type.
|
||||
* Reads an image from a file and returns its type in $type. If $warnings is passed, recoverable decoder
|
||||
* warnings are returned in it instead of being raised as a PHP warning.
|
||||
* @param-out ?string $warnings
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
* @throws UnknownImageFileException if file not found or file type is not known
|
||||
*/
|
||||
public static function fromFile(string $file, ?int &$type = null): static
|
||||
public static function fromFile(string $file, ?int &$type = null, ?string &$warnings = self::Sentinel): static
|
||||
{
|
||||
self::ensureExtension();
|
||||
$type = self::detectTypeFromFile($file);
|
||||
|
|
@ -171,16 +173,18 @@ class Image
|
|||
throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found.");
|
||||
}
|
||||
|
||||
return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '$file'.", __METHOD__);
|
||||
return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '$file'.", __METHOD__, $warnings);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads an image from a string and returns its type in $type.
|
||||
* Reads an image from a string and returns its type in $type. If $warnings is passed, recoverable decoder
|
||||
* warnings are returned in it instead of being raised as a PHP warning.
|
||||
* @param-out ?string $warnings
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
* @throws ImageException
|
||||
*/
|
||||
public static function fromString(string $s, ?int &$type = null): static
|
||||
public static function fromString(string $s, ?int &$type = null, ?string &$warnings = self::Sentinel): static
|
||||
{
|
||||
self::ensureExtension();
|
||||
$type = self::detectTypeFromString($s);
|
||||
|
|
@ -188,21 +192,31 @@ class Image
|
|||
throw new UnknownImageFileException('Unknown type of image.');
|
||||
}
|
||||
|
||||
return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__);
|
||||
return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__, $warnings);
|
||||
}
|
||||
|
||||
|
||||
private static function invokeSafe(string $func, string $arg, string $message, string $callee): static
|
||||
/** @param callable-string $func */
|
||||
private static function invokeSafe(
|
||||
string $func,
|
||||
string $arg,
|
||||
string $message,
|
||||
string $callee,
|
||||
?string &$warnings = self::Sentinel,
|
||||
): static
|
||||
{
|
||||
$errors = [];
|
||||
$res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void {
|
||||
$errors[] = $message;
|
||||
});
|
||||
|
||||
$raiseWarning = $warnings === self::Sentinel;
|
||||
$warnings = $errors ? implode(', ', $errors) : null;
|
||||
|
||||
if (!$res) {
|
||||
throw new ImageException($message . ' Errors: ' . implode(', ', $errors));
|
||||
} elseif ($errors) {
|
||||
trigger_error($callee . '(): ' . implode(', ', $errors), E_USER_WARNING);
|
||||
throw new ImageException($message . ' Errors: ' . $warnings);
|
||||
} elseif ($errors && $raiseWarning) {
|
||||
trigger_error($callee . '(): ' . $warnings, E_USER_WARNING);
|
||||
}
|
||||
|
||||
return new static($res);
|
||||
|
|
@ -213,6 +227,7 @@ class Image
|
|||
* Creates a new true color image of the given dimensions. The default color is black.
|
||||
* @param positive-int $width
|
||||
* @param positive-int $height
|
||||
* @param ImageColor|array{red: int, green: int, blue: int, alpha?: int}|null $color
|
||||
* @throws Nette\NotSupportedException if gd extension is not loaded
|
||||
*/
|
||||
public static function fromBlank(int $width, int $height, ImageColor|array|null $color = null): static
|
||||
|
|
@ -224,9 +239,9 @@ class Image
|
|||
|
||||
$image = new static(imagecreatetruecolor($width, $height));
|
||||
if ($color) {
|
||||
$image->alphablending(false);
|
||||
$image->filledrectangle(0, 0, $width - 1, $height - 1, $color);
|
||||
$image->alphablending(true);
|
||||
$image->alphaBlending(false);
|
||||
$image->filledRectangle(0, 0, $width - 1, $height - 1, self::normalizeColor($color));
|
||||
$image->alphaBlending(true);
|
||||
}
|
||||
|
||||
return $image;
|
||||
|
|
@ -235,9 +250,11 @@ class Image
|
|||
|
||||
/**
|
||||
* Returns the type of image from file.
|
||||
* @return ImageType::*|null
|
||||
* @param-out ?int $width
|
||||
* @param-out ?int $height
|
||||
* @return ?ImageType::*
|
||||
*/
|
||||
public static function detectTypeFromFile(string $file, &$width = null, &$height = null): ?int
|
||||
public static function detectTypeFromFile(string $file, mixed &$width = null, mixed &$height = null): ?int
|
||||
{
|
||||
[$width, $height, $type] = Helpers::falseToNull(@getimagesize($file)); // @ - files smaller than 12 bytes causes read error
|
||||
return $type && isset(self::Formats[$type]) ? $type : null;
|
||||
|
|
@ -246,9 +263,11 @@ class Image
|
|||
|
||||
/**
|
||||
* Returns the type of image from string.
|
||||
* @return ImageType::*|null
|
||||
* @param-out ?int $width
|
||||
* @param-out ?int $height
|
||||
* @return ?ImageType::*
|
||||
*/
|
||||
public static function detectTypeFromString(string $s, &$width = null, &$height = null): ?int
|
||||
public static function detectTypeFromString(string $s, mixed &$width = null, mixed &$height = null): ?int
|
||||
{
|
||||
[$width, $height, $type] = Helpers::falseToNull(@getimagesizefromstring($s)); // @ - strings smaller than 12 bytes causes read error
|
||||
return $type && isset(self::Formats[$type]) ? $type : null;
|
||||
|
|
@ -297,6 +316,7 @@ class Image
|
|||
|
||||
|
||||
/**
|
||||
* Checks whether the given image type is supported by the GD extension.
|
||||
* @param ImageType::* $type
|
||||
*/
|
||||
public static function isTypeSupported(int $type): bool
|
||||
|
|
@ -314,7 +334,10 @@ class Image
|
|||
}
|
||||
|
||||
|
||||
/** @return ImageType[] */
|
||||
/**
|
||||
* Returns list of image types supported by the GD extension.
|
||||
* @return ImageType::*[]
|
||||
*/
|
||||
public static function getSupportedTypes(): array
|
||||
{
|
||||
self::ensureExtension();
|
||||
|
|
@ -386,6 +409,10 @@ class Image
|
|||
public function resize(int|string|null $width, int|string|null $height, int $mode = self::OrSmaller): static
|
||||
{
|
||||
if ($mode & self::Cover) {
|
||||
if ($width === null || $height === null) {
|
||||
throw new Nette\InvalidArgumentException('Both width and height must be set for Cover mode.');
|
||||
}
|
||||
|
||||
return $this->resize($width, $height, self::OrBigger)->crop('50%', '50%', $width, $height);
|
||||
}
|
||||
|
||||
|
|
@ -419,12 +446,13 @@ class Image
|
|||
/**
|
||||
* Calculates dimensions of resized image. Width and height accept pixels or percent.
|
||||
* @param int-mask-of<self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly> $mode
|
||||
* @return array{int<1, max>, int<1, max>}
|
||||
*/
|
||||
public static function calculateSize(
|
||||
int $srcWidth,
|
||||
int $srcHeight,
|
||||
$newWidth,
|
||||
$newHeight,
|
||||
int|string|null $newWidth,
|
||||
int|string|null $newHeight,
|
||||
int $mode = self::OrSmaller,
|
||||
): array
|
||||
{
|
||||
|
|
@ -468,19 +496,19 @@ class Image
|
|||
}
|
||||
|
||||
if ($mode & self::OrBigger) {
|
||||
$scale = [max($scale)];
|
||||
$scale = [max($scale ?: [1])];
|
||||
}
|
||||
|
||||
if ($mode & self::ShrinkOnly) {
|
||||
$scale[] = 1;
|
||||
}
|
||||
|
||||
$scale = min($scale);
|
||||
$scale = min($scale ?: [1]);
|
||||
$newWidth = (int) round($srcWidth * $scale);
|
||||
$newHeight = (int) round($srcHeight * $scale);
|
||||
}
|
||||
|
||||
return [max($newWidth, 1), max($newHeight, 1)];
|
||||
return [max((int) $newWidth, 1), max((int) $newHeight, 1)];
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -495,7 +523,7 @@ class Image
|
|||
$this->image = imagecrop($this->image, $r);
|
||||
imagesavealpha($this->image, true);
|
||||
} else {
|
||||
$newImage = static::fromBlank($r['width'], $r['height'], ImageColor::rgb(0, 0, 0, 0))->getImageResource();
|
||||
$newImage = static::fromBlank(max(1, $r['width']), max(1, $r['height']), ImageColor::rgb(0, 0, 0, 0))->getImageResource();
|
||||
imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']);
|
||||
$this->image = $newImage;
|
||||
}
|
||||
|
|
@ -506,6 +534,7 @@ class Image
|
|||
|
||||
/**
|
||||
* Calculates dimensions of cutout in image. Arguments accepts pixels or percent.
|
||||
* @return array{int, int, int, int}
|
||||
*/
|
||||
public static function calculateCutout(
|
||||
int $srcWidth,
|
||||
|
|
@ -516,21 +545,10 @@ class Image
|
|||
int|string $newHeight,
|
||||
): array
|
||||
{
|
||||
if (self::isPercent($newWidth)) {
|
||||
$newWidth = (int) round($srcWidth / 100 * $newWidth);
|
||||
}
|
||||
|
||||
if (self::isPercent($newHeight)) {
|
||||
$newHeight = (int) round($srcHeight / 100 * $newHeight);
|
||||
}
|
||||
|
||||
if (self::isPercent($left)) {
|
||||
$left = (int) round(($srcWidth - $newWidth) / 100 * $left);
|
||||
}
|
||||
|
||||
if (self::isPercent($top)) {
|
||||
$top = (int) round(($srcHeight - $newHeight) / 100 * $top);
|
||||
}
|
||||
$newWidth = (int) (self::isPercent($newWidth) ? round($srcWidth / 100 * $newWidth) : $newWidth);
|
||||
$newHeight = (int) (self::isPercent($newHeight) ? round($srcHeight / 100 * $newHeight) : $newHeight);
|
||||
$left = (int) (self::isPercent($left) ? round(($srcWidth - $newWidth) / 100 * $left) : $left);
|
||||
$top = (int) (self::isPercent($top) ? round(($srcHeight - $newHeight) / 100 * $top) : $top);
|
||||
|
||||
if ($left < 0) {
|
||||
$newWidth += $left;
|
||||
|
|
@ -575,14 +593,8 @@ class Image
|
|||
|
||||
$width = $image->getWidth();
|
||||
$height = $image->getHeight();
|
||||
|
||||
if (self::isPercent($left)) {
|
||||
$left = (int) round(($this->getWidth() - $width) / 100 * $left);
|
||||
}
|
||||
|
||||
if (self::isPercent($top)) {
|
||||
$top = (int) round(($this->getHeight() - $height) / 100 * $top);
|
||||
}
|
||||
$left = (int) (self::isPercent($left) ? round(($this->getWidth() - $width) / 100 * $left) : $left);
|
||||
$top = (int) (self::isPercent($top) ? round(($this->getHeight() - $height) / 100 * $top) : $top);
|
||||
|
||||
$output = $input = $image->image;
|
||||
if ($opacity < 100) {
|
||||
|
|
@ -595,7 +607,7 @@ class Image
|
|||
imagealphablending($output, false);
|
||||
if (!$image->isTrueColor()) {
|
||||
$input = $output;
|
||||
imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127));
|
||||
imagefilledrectangle($output, 0, 0, $width, $height, (int) imagecolorallocatealpha($output, 0, 0, 0, 127));
|
||||
imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height);
|
||||
}
|
||||
|
||||
|
|
@ -626,6 +638,8 @@ class Image
|
|||
|
||||
/**
|
||||
* Calculates the bounding box for a TrueType text. Returns keys left, top, width and height.
|
||||
* @param array<string, mixed> $options
|
||||
* @return array{left: int, top: int, width: int, height: int}
|
||||
*/
|
||||
public static function calculateTextBox(
|
||||
string $text,
|
||||
|
|
@ -647,7 +661,7 @@ class Image
|
|||
|
||||
|
||||
/**
|
||||
* Draw a rectangle.
|
||||
* Draws a rectangle using top-left coordinates and dimensions instead of two corner coordinates.
|
||||
*/
|
||||
public function rectangleWH(int $x, int $y, int $width, int $height, ImageColor $color): void
|
||||
{
|
||||
|
|
@ -658,7 +672,7 @@ class Image
|
|||
|
||||
|
||||
/**
|
||||
* Draw a filled rectangle.
|
||||
* Draws a filled rectangle using top-left coordinates and dimensions instead of two corner coordinates.
|
||||
*/
|
||||
public function filledRectangleWH(int $x, int $y, int $width, int $height, ImageColor $color): void
|
||||
{
|
||||
|
|
@ -670,7 +684,7 @@ class Image
|
|||
|
||||
/**
|
||||
* Saves image to the file. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9).
|
||||
* @param ImageType::*|null $type
|
||||
* @param ?ImageType::* $type
|
||||
* @throws ImageException
|
||||
*/
|
||||
public function save(string $file, ?int $quality = null, ?int $type = null): void
|
||||
|
|
@ -746,6 +760,7 @@ class Image
|
|||
|
||||
/**
|
||||
* Call to undefined method.
|
||||
* @param mixed[] $args
|
||||
* @throws Nette\MemberAccessException
|
||||
*/
|
||||
public function __call(string $name, array $args): mixed
|
||||
|
|
@ -760,6 +775,7 @@ class Image
|
|||
$args[$key] = $value->getImageResource();
|
||||
|
||||
} elseif ($value instanceof ImageColor || (is_array($value) && isset($value['red']))) {
|
||||
/** @var ImageColor|array{red: int, green: int, blue: int, alpha?: int} $value */
|
||||
$args[$key] = $this->resolveColor($value);
|
||||
}
|
||||
}
|
||||
|
|
@ -775,10 +791,11 @@ class Image
|
|||
{
|
||||
ob_start(fn() => '');
|
||||
imagepng($this->image, null, 0);
|
||||
$this->setImageResource(imagecreatefromstring(ob_get_clean()));
|
||||
$this->setImageResource(imagecreatefromstring(ob_get_clean()) ?: throw new Nette\ShouldNotHappenException);
|
||||
}
|
||||
|
||||
|
||||
/** @param-out int|float $num */
|
||||
private static function isPercent(int|string &$num): bool
|
||||
{
|
||||
if (is_string($num) && str_ends_with($num, '%')) {
|
||||
|
|
@ -802,13 +819,31 @@ class Image
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves a color to a GD color index for the current image.
|
||||
* @param ImageColor|array{red: int, green: int, blue: int, alpha?: int} $color
|
||||
*/
|
||||
public function resolveColor(ImageColor|array $color): int
|
||||
{
|
||||
$color = $color instanceof ImageColor ? $color->toRGBA() : array_values($color);
|
||||
$color = self::normalizeColor($color)->toRGBA();
|
||||
return imagecolorallocatealpha($this->image, ...$color) ?: imagecolorresolvealpha($this->image, ...$color);
|
||||
}
|
||||
|
||||
|
||||
/** @param ImageColor|array{red: int, green: int, blue: int, alpha?: int} $color */
|
||||
private static function normalizeColor(ImageColor|array $color): ImageColor
|
||||
{
|
||||
return $color instanceof ImageColor
|
||||
? $color
|
||||
: ImageColor::rgb(
|
||||
$color['red'],
|
||||
$color['green'],
|
||||
$color['blue'],
|
||||
(127 - ($color['alpha'] ?? 0)) / 127,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private static function ensureExtension(): void
|
||||
{
|
||||
if (!extension_loaded('gd')) {
|
||||
|
|
|
|||
11
vendor/nette/utils/src/Utils/ImageColor.php
vendored
11
vendor/nette/utils/src/Utils/ImageColor.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -18,6 +16,9 @@ use function hexdec, ltrim, max, min, round, strlen;
|
|||
*/
|
||||
class ImageColor
|
||||
{
|
||||
/**
|
||||
* Creates a color from RGB components (0..255) and opacity (0..1).
|
||||
*/
|
||||
public static function rgb(int $red, int $green, int $blue, float $opacity = 1): self
|
||||
{
|
||||
return new self($red, $green, $blue, $opacity);
|
||||
|
|
@ -64,6 +65,10 @@ class ImageColor
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns GD-compatible color array [R, G, B, alpha].
|
||||
* @return array{int<0, 255>, int<0, 255>, int<0, 255>, int<0, 127>}
|
||||
*/
|
||||
public function toRGBA(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
|||
4
vendor/nette/utils/src/Utils/ImageType.php
vendored
4
vendor/nette/utils/src/Utils/ImageType.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use const IMAGETYPE_BMP, IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_WEBP;
|
||||
|
|
|
|||
38
vendor/nette/utils/src/Utils/Iterables.php
vendored
38
vendor/nette/utils/src/Utils/Iterables.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -22,6 +20,7 @@ final class Iterables
|
|||
|
||||
/**
|
||||
* Tests for the presence of value.
|
||||
* @param iterable<mixed> $iterable
|
||||
*/
|
||||
public static function contains(iterable $iterable, mixed $value): bool
|
||||
{
|
||||
|
|
@ -36,6 +35,7 @@ final class Iterables
|
|||
|
||||
/**
|
||||
* Tests for the presence of key.
|
||||
* @param iterable<mixed> $iterable
|
||||
*/
|
||||
public static function containsKey(iterable $iterable, mixed $key): bool
|
||||
{
|
||||
|
|
@ -52,9 +52,11 @@ final class Iterables
|
|||
* Returns the first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
|
||||
* @template K
|
||||
* @template V
|
||||
* @template E
|
||||
* @param iterable<K, V> $iterable
|
||||
* @param ?callable(V, K, iterable<K, V>): bool $predicate
|
||||
* @return ?V
|
||||
* @param ?callable(): E $else
|
||||
* @return ($else is null ? ?V : V|E)
|
||||
*/
|
||||
public static function first(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
|
||||
{
|
||||
|
|
@ -71,9 +73,11 @@ final class Iterables
|
|||
* Returns the key of first item (matching the specified predicate if given). If there is no such item, it returns result of invoking $else or null.
|
||||
* @template K
|
||||
* @template V
|
||||
* @template E
|
||||
* @param iterable<K, V> $iterable
|
||||
* @param ?callable(V, K, iterable<K, V>): bool $predicate
|
||||
* @return ?K
|
||||
* @param ?callable(): E $else
|
||||
* @return ($else is null ? ?K : K|E)
|
||||
*/
|
||||
public static function firstKey(iterable $iterable, ?callable $predicate = null, ?callable $else = null): mixed
|
||||
{
|
||||
|
|
@ -87,7 +91,7 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Tests whether at least one element in the iterator passes the test implemented by the provided function.
|
||||
* Tests whether at least one element in the iterable passes the test implemented by the provided function.
|
||||
* @template K
|
||||
* @template V
|
||||
* @param iterable<K, V> $iterable
|
||||
|
|
@ -105,7 +109,7 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Tests whether all elements in the iterator pass the test implemented by the provided function.
|
||||
* Tests whether all elements in the iterable pass the test implemented by the provided function.
|
||||
* @template K
|
||||
* @template V
|
||||
* @param iterable<K, V> $iterable
|
||||
|
|
@ -123,7 +127,7 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Iterator that filters elements according to a given $predicate. Maintains original keys.
|
||||
* Returns a generator that yields only elements matching the given $predicate. Maintains original keys.
|
||||
* @template K
|
||||
* @template V
|
||||
* @param iterable<K, V> $iterable
|
||||
|
|
@ -141,7 +145,7 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Iterator that transforms values by calling $transformer. Maintains original keys.
|
||||
* Returns a generator that transforms values by calling $transformer. Maintains original keys.
|
||||
* @template K
|
||||
* @template V
|
||||
* @template R
|
||||
|
|
@ -158,14 +162,14 @@ final class Iterables
|
|||
|
||||
|
||||
/**
|
||||
* Iterator that transforms keys and values by calling $transformer. If it returns null, the element is skipped.
|
||||
* Returns a generator that transforms keys and values by calling $transformer. If it returns null, the element is skipped.
|
||||
* @template K
|
||||
* @template V
|
||||
* @template ResV
|
||||
* @template ResK
|
||||
* @template ResV
|
||||
* @param iterable<K, V> $iterable
|
||||
* @param callable(V, K, iterable<K, V>): ?array{ResV, ResK} $transformer
|
||||
* @return \Generator<ResV, ResK>
|
||||
* @param callable(V, K, iterable<K, V>): ?array{ResK, ResV} $transformer
|
||||
* @return \Generator<ResK, ResV>
|
||||
*/
|
||||
public static function mapWithKeys(iterable $iterable, callable $transformer): \Generator
|
||||
{
|
||||
|
|
@ -188,9 +192,10 @@ final class Iterables
|
|||
*/
|
||||
public static function repeatable(callable $factory): \IteratorAggregate
|
||||
{
|
||||
return new class ($factory) implements \IteratorAggregate {
|
||||
return new class ($factory(...)) implements \IteratorAggregate {
|
||||
public function __construct(
|
||||
private $factory,
|
||||
/** @var \Closure(): iterable<mixed, mixed> */
|
||||
private \Closure $factory,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -215,7 +220,8 @@ final class Iterables
|
|||
{
|
||||
return new class (self::toIterator($iterable)) implements \IteratorAggregate {
|
||||
public function __construct(
|
||||
private \Iterator $iterator,
|
||||
private readonly \Iterator $iterator,
|
||||
/** @var array<array{mixed, mixed}> */
|
||||
private array $cache = [],
|
||||
) {
|
||||
}
|
||||
|
|
|
|||
10
vendor/nette/utils/src/Utils/Json.php
vendored
10
vendor/nette/utils/src/Utils/Json.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -32,8 +30,8 @@ final class Json
|
|||
|
||||
|
||||
/**
|
||||
* Converts value to JSON format. Use $pretty for easier reading and clarity, $asciiSafe for ASCII output
|
||||
* and $htmlSafe for HTML escaping, $forceObjects enforces the encoding of non-associateve arrays as objects.
|
||||
* Converts value to JSON format. Use $pretty for formatted output, $asciiSafe for ASCII-only output,
|
||||
* $htmlSafe for HTML-safe output, and $forceObjects to encode non-associative arrays as objects.
|
||||
* @throws JsonException
|
||||
*/
|
||||
public static function encode(
|
||||
|
|
@ -66,7 +64,7 @@ final class Json
|
|||
|
||||
|
||||
/**
|
||||
* Parses JSON to PHP value. The $forceArrays enforces the decoding of objects as arrays.
|
||||
* Decodes a JSON string to a PHP value. Use $forceArrays to decode objects as arrays.
|
||||
* @throws JsonException
|
||||
*/
|
||||
public static function decode(string $json, bool|int $forceArrays = false): mixed
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
|
|||
61
vendor/nette/utils/src/Utils/Paginator.php
vendored
61
vendor/nette/utils/src/Utils/Paginator.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -17,17 +15,17 @@ use Nette;
|
|||
*
|
||||
* @property int $page
|
||||
* @property-read int $firstPage
|
||||
* @property-read int|null $lastPage
|
||||
* @property-read ?int $lastPage
|
||||
* @property-read int<0,max> $firstItemOnPage
|
||||
* @property-read int<0,max> $lastItemOnPage
|
||||
* @property int $base
|
||||
* @property-read bool $first
|
||||
* @property-read bool $last
|
||||
* @property-read int<0,max>|null $pageCount
|
||||
* @property-read ?int<0,max> $pageCount
|
||||
* @property positive-int $itemsPerPage
|
||||
* @property int<0,max>|null $itemCount
|
||||
* @property ?int<0,max> $itemCount
|
||||
* @property-read int<0,max> $offset
|
||||
* @property-read int<0,max>|null $countdownOffset
|
||||
* @property-read ?int<0,max> $countdownOffset
|
||||
* @property-read int<0,max> $length
|
||||
*/
|
||||
class Paginator
|
||||
|
|
@ -41,13 +39,10 @@ class Paginator
|
|||
|
||||
private int $page = 1;
|
||||
|
||||
/** @var int<0, max>|null */
|
||||
/** @var ?int<0, max> */
|
||||
private ?int $itemCount = null;
|
||||
|
||||
|
||||
/**
|
||||
* Sets current page number.
|
||||
*/
|
||||
public function setPage(int $page): static
|
||||
{
|
||||
$this->page = $page;
|
||||
|
|
@ -55,27 +50,18 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns current page number.
|
||||
*/
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->base + $this->getPageIndex();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns first page number.
|
||||
*/
|
||||
public function getFirstPage(): int
|
||||
{
|
||||
return $this->base;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns last page number.
|
||||
*/
|
||||
public function getLastPage(): ?int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
|
|
@ -85,7 +71,7 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the sequence number of the first element on the page
|
||||
* Returns the 1-based index of the first item on the current page, or 0 if the page is empty.
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getFirstItemOnPage(): int
|
||||
|
|
@ -97,7 +83,7 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the sequence number of the last element on the page
|
||||
* Returns the 1-based index of the last item on the current page.
|
||||
* @return int<0, max>
|
||||
*/
|
||||
public function getLastItemOnPage(): int
|
||||
|
|
@ -106,9 +92,6 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets first page (base) number.
|
||||
*/
|
||||
public function setBase(int $base): static
|
||||
{
|
||||
$this->base = $base;
|
||||
|
|
@ -116,9 +99,6 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns first page (base) number.
|
||||
*/
|
||||
public function getBase(): int
|
||||
{
|
||||
return $this->base;
|
||||
|
|
@ -138,18 +118,12 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current page the first one?
|
||||
*/
|
||||
public function isFirst(): bool
|
||||
{
|
||||
return $this->getPageIndex() === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the current page the last one?
|
||||
*/
|
||||
public function isLast(): bool
|
||||
{
|
||||
return $this->itemCount === null
|
||||
|
|
@ -159,20 +133,16 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the total number of pages.
|
||||
* @return int<0, max>|null
|
||||
* @return ?int<0, max>
|
||||
*/
|
||||
public function getPageCount(): ?int
|
||||
{
|
||||
return $this->itemCount === null
|
||||
? null
|
||||
: (int) ceil($this->itemCount / $this->itemsPerPage);
|
||||
: max(0, (int) ceil($this->itemCount / $this->itemsPerPage));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the number of items to display on a single page.
|
||||
*/
|
||||
public function setItemsPerPage(int $itemsPerPage): static
|
||||
{
|
||||
$this->itemsPerPage = max(1, $itemsPerPage);
|
||||
|
|
@ -181,7 +151,6 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the number of items to display on a single page.
|
||||
* @return positive-int
|
||||
*/
|
||||
public function getItemsPerPage(): int
|
||||
|
|
@ -190,9 +159,6 @@ class Paginator
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the total number of items.
|
||||
*/
|
||||
public function setItemCount(?int $itemCount = null): static
|
||||
{
|
||||
$this->itemCount = $itemCount === null ? null : max(0, $itemCount);
|
||||
|
|
@ -201,8 +167,7 @@ class Paginator
|
|||
|
||||
|
||||
/**
|
||||
* Returns the total number of items.
|
||||
* @return int<0, max>|null
|
||||
* @return ?int<0, max>
|
||||
*/
|
||||
public function getItemCount(): ?int
|
||||
{
|
||||
|
|
@ -222,7 +187,7 @@ class Paginator
|
|||
|
||||
/**
|
||||
* Returns the absolute index of the first item on current page in countdown paging.
|
||||
* @return int<0, max>|null
|
||||
* @return ?int<0, max>
|
||||
*/
|
||||
public function getCountdownOffset(): ?int
|
||||
{
|
||||
|
|
@ -240,6 +205,6 @@ class Paginator
|
|||
{
|
||||
return $this->itemCount === null
|
||||
? $this->itemsPerPage
|
||||
: min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage);
|
||||
: max(0, min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
507
vendor/nette/utils/src/Utils/Process.php
vendored
Normal file
507
vendor/nette/utils/src/Utils/Process.php
vendored
Normal file
|
|
@ -0,0 +1,507 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a process, which can be started and controlled (reading output, writing input, waiting for completion).
|
||||
*/
|
||||
final class Process
|
||||
{
|
||||
private const PollInterval = 10_000;
|
||||
private const DefaultTimeout = 60;
|
||||
private const StdIn = 0;
|
||||
private const StdOut = 1;
|
||||
private const StdErr = 2;
|
||||
|
||||
/** @var resource */
|
||||
private mixed $process;
|
||||
|
||||
/** @var array<string, mixed> result of proc_get_status() */
|
||||
private array $status = ['running' => true];
|
||||
|
||||
/** @var resource */
|
||||
private mixed $inputPipe;
|
||||
|
||||
/** @var array<int, resource|string[]> pipe resources, or ['file', path, mode] descriptors for discarded output */
|
||||
private array $outputPipes = [];
|
||||
|
||||
/** @var string[] */
|
||||
private array $outputBuffers = [];
|
||||
|
||||
/** @var int[] Number of bytes already read from buffers. */
|
||||
private array $outputBufferOffsets = [];
|
||||
|
||||
/** @var array<int, true> Output IDs whose target resource was supplied by the caller and must not be closed here. */
|
||||
private array $callerOutputs = [];
|
||||
private float $startTime;
|
||||
|
||||
|
||||
/**
|
||||
* Starts an executable with given arguments. Because the arguments are passed as an array, the shell is
|
||||
* never involved, so they need no escaping and there is no risk of shell injection.
|
||||
* @param string $executable Path to the executable binary.
|
||||
* @param list<string> $arguments Arguments passed to the executable.
|
||||
* @param string[]|null $env Environment variables or null to use the same environment as the current process.
|
||||
* @param array<string, mixed> $options Additional options for proc_open(). On Windows the executable is launched directly, without cmd.exe.
|
||||
* @param mixed $stdin Input: string, a readable resource (its content is copied to STDIN), a Process (its STDOUT is piped in), or null (STDIN stays open for writeStdInput()).
|
||||
* @param mixed $stdout Output target: string filename, a writable resource backed by a real OS file descriptor (not php://memory etc.), false to discard, or null to capture into memory.
|
||||
* @param mixed $stderr Error output target (same options as $stdout).
|
||||
* @param string|null $directory Working directory.
|
||||
* @param float|null $timeout Time limit in seconds, checked while waiting for or reading the process; null disables it.
|
||||
*/
|
||||
public static function runExecutable(
|
||||
string $executable,
|
||||
array $arguments = [],
|
||||
?array $env = null,
|
||||
array $options = [],
|
||||
mixed $stdin = '',
|
||||
mixed $stdout = null,
|
||||
mixed $stderr = null,
|
||||
?string $directory = null,
|
||||
?float $timeout = self::DefaultTimeout,
|
||||
): self
|
||||
{
|
||||
return new self([$executable, ...$arguments], $env, $options, $directory, $stdin, $stdout, $stderr, $timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Starts a process from a command string interpreted by the shell (/bin/sh on POSIX, cmd.exe on Windows).
|
||||
* Because the shell parses the string, NEVER pass unescaped user input here - use runExecutable() for that.
|
||||
* @param string $command Shell command to run.
|
||||
* @param string[]|null $env Environment variables or null to use the same environment as the current process.
|
||||
* @param array<string, mixed> $options Options for proc_open(), e.g. ['bypass_shell' => true] on Windows to skip cmd.exe.
|
||||
* @param mixed $stdin Input: string, a readable resource (its content is copied to STDIN), a Process (its STDOUT is piped in), or null (STDIN stays open for writeStdInput()).
|
||||
* @param mixed $stdout Output target: string filename, a writable resource backed by a real OS file descriptor (not php://memory etc.), false to discard, or null to capture into memory.
|
||||
* @param mixed $stderr Error output target (same options as $stdout).
|
||||
* @param string|null $directory Working directory.
|
||||
* @param float|null $timeout Time limit in seconds, checked while waiting for or reading the process; null disables it.
|
||||
*/
|
||||
public static function runCommand(
|
||||
string $command,
|
||||
?array $env = null,
|
||||
array $options = [],
|
||||
mixed $stdin = '',
|
||||
mixed $stdout = null,
|
||||
mixed $stderr = null,
|
||||
?string $directory = null,
|
||||
?float $timeout = self::DefaultTimeout,
|
||||
): self
|
||||
{
|
||||
return new self($command, $env, $options, $directory, $stdin, $stdout, $stderr, $timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param list<string>|string $command
|
||||
* @param array<string, string>|null $env
|
||||
* @param array<string, mixed> $options
|
||||
*/
|
||||
private function __construct(
|
||||
string|array $command,
|
||||
?array $env,
|
||||
array $options,
|
||||
?string $directory,
|
||||
mixed $stdin,
|
||||
mixed $stdout,
|
||||
mixed $stderr,
|
||||
private ?float $timeout,
|
||||
) {
|
||||
$descriptors = [
|
||||
self::StdIn => $this->createInputDescriptor($stdin),
|
||||
self::StdOut => $this->createOutputDescriptor(self::StdOut, $stdout),
|
||||
self::StdErr => $this->createOutputDescriptor(self::StdErr, $stderr),
|
||||
];
|
||||
|
||||
$process = @proc_open($command, $descriptors, $pipes, $directory, $env, $options);
|
||||
if (!is_resource($process)) {
|
||||
throw new ProcessFailedException('Failed to start process: ' . Helpers::getLastError());
|
||||
}
|
||||
|
||||
$this->process = $process;
|
||||
[$this->inputPipe, $this->outputPipes[self::StdOut], $this->outputPipes[self::StdErr]] = $pipes + $descriptors;
|
||||
|
||||
if ($stdin instanceof self) {
|
||||
// the source process hands over its STDOUT pipe; from now on this process owns it
|
||||
unset(
|
||||
$stdin->outputBuffers[self::StdOut],
|
||||
$stdin->outputBufferOffsets[self::StdOut],
|
||||
$stdin->outputPipes[self::StdOut],
|
||||
);
|
||||
}
|
||||
|
||||
$this->writeInitialInput($stdin);
|
||||
$this->startTime = microtime(true);
|
||||
}
|
||||
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->outputBuffers = [];
|
||||
$this->terminate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the process is currently running.
|
||||
*/
|
||||
public function isRunning(): bool
|
||||
{
|
||||
if (!$this->status['running']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->status = proc_get_status($this->process);
|
||||
if (!$this->status['running']) {
|
||||
$this->close();
|
||||
}
|
||||
|
||||
return $this->status['running'];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finishes the process by waiting for its completion. While waiting, the captured output is read
|
||||
* continuously and kept in memory; an optional callback is invoked with each new output/error chunk.
|
||||
*
|
||||
* @param (\Closure(string, string): void)|null $callback
|
||||
*/
|
||||
public function wait(?\Closure $callback = null): void
|
||||
{
|
||||
while ($this->isRunning()) {
|
||||
$this->enforceTimeout();
|
||||
$this->drainPipes();
|
||||
$this->dispatchCallback($callback);
|
||||
usleep(self::PollInterval);
|
||||
}
|
||||
|
||||
$this->dispatchCallback($callback);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads any new data from the captured pipes into the buffers, so a process producing more output
|
||||
* than the OS pipe buffer holds does not block. (On Windows the captured output is a file and never blocks.)
|
||||
*/
|
||||
private function drainPipes(): void
|
||||
{
|
||||
foreach ([self::StdOut, self::StdErr] as $id) {
|
||||
$this->readFromPipe($id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Terminates the running process if it is still running.
|
||||
*/
|
||||
public function terminate(): void
|
||||
{
|
||||
if (!$this->isRunning()) {
|
||||
return;
|
||||
} elseif (Helpers::IsWindows) {
|
||||
exec("taskkill /F /T /PID {$this->getPid()} 2>&1");
|
||||
} else {
|
||||
proc_terminate($this->process, 9); // 9 = SIGKILL: cannot be trapped, so the following proc_close() won't hang
|
||||
}
|
||||
$this->status['running'] = false;
|
||||
$this->close();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the process exit code. If the process is still running, waits until it finishes.
|
||||
*/
|
||||
public function getExitCode(): int
|
||||
{
|
||||
$this->wait();
|
||||
return $this->status['exitcode'] ?? -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the process terminated with exit code 0.
|
||||
*/
|
||||
public function isSuccess(): bool
|
||||
{
|
||||
return $this->getExitCode() === 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits for the process to finish and throws ProcessFailedException if exit code is not zero.
|
||||
*/
|
||||
public function ensureSuccess(): void
|
||||
{
|
||||
$code = $this->getExitCode();
|
||||
if ($code !== 0) {
|
||||
throw new ProcessFailedException("Process failed with non-zero exit code: $code");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the PID of the running process, or null if it is not running.
|
||||
*/
|
||||
public function getPid(): ?int
|
||||
{
|
||||
return $this->isRunning() ? $this->status['pid'] : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits for the process to finish and returns everything it wrote to STDOUT.
|
||||
*/
|
||||
public function getStdOutput(): string
|
||||
{
|
||||
$this->wait();
|
||||
return $this->outputBuffers[self::StdOut] ?? throw new Nette\InvalidStateException('Cannot read output: it is not captured (it was redirected, discarded or piped).');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits for the process to finish and returns everything it wrote to STDERR.
|
||||
*/
|
||||
public function getStdError(): string
|
||||
{
|
||||
$this->wait();
|
||||
return $this->outputBuffers[self::StdErr] ?? throw new Nette\InvalidStateException('Cannot read output: it is not captured (it was redirected, discarded or piped).');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the STDOUT data produced since the previous consumeStdOutput() call.
|
||||
* To read everything incrementally, poll `while ($p->isRunning())` calling this, then call it once more
|
||||
* after the loop; that last call returns whatever the process wrote just before the loop noticed it had exited.
|
||||
*/
|
||||
public function consumeStdOutput(): string
|
||||
{
|
||||
return $this->consumeBuffer(self::StdOut);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the STDERR data produced since the previous consumeStdError() call. See consumeStdOutput().
|
||||
*/
|
||||
public function consumeStdError(): string
|
||||
{
|
||||
return $this->consumeBuffer(self::StdErr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns newly available data from the specified buffer and advances the read pointer.
|
||||
*/
|
||||
private function consumeBuffer(int $id): string
|
||||
{
|
||||
if (!isset($this->outputBuffers[$id])) {
|
||||
throw new Nette\InvalidStateException('Cannot read output: it is not captured (it was redirected, discarded or piped).');
|
||||
} elseif ($this->isRunning()) {
|
||||
$this->enforceTimeout();
|
||||
$this->readFromPipe($id);
|
||||
}
|
||||
return $this->extractNewData($id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the buffered data not returned yet and advances the read pointer.
|
||||
*/
|
||||
private function extractNewData(int $id): string
|
||||
{
|
||||
if (!isset($this->outputBuffers[$id])) {
|
||||
return '';
|
||||
}
|
||||
$res = substr($this->outputBuffers[$id], $this->outputBufferOffsets[$id]);
|
||||
$this->outputBufferOffsets[$id] = strlen($this->outputBuffers[$id]);
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes data into the process' STDIN. If STDIN is closed, throws exception.
|
||||
*/
|
||||
public function writeStdInput(string $string): void
|
||||
{
|
||||
if (!is_resource($this->inputPipe)) {
|
||||
throw new Nette\InvalidStateException('Cannot write to process: STDIN pipe is closed');
|
||||
}
|
||||
$this->writeToPipe($string);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes the whole string to STDIN, handling partial writes. Stops (and lets fwrite() warn) on a broken pipe,
|
||||
* i.e. when the process stopped reading its STDIN.
|
||||
*/
|
||||
private function writeToPipe(string $string): void
|
||||
{
|
||||
$length = strlen($string);
|
||||
for ($written = 0; $written < $length; $written += $bytes) {
|
||||
$bytes = fwrite($this->inputPipe, substr($string, $written));
|
||||
if (!$bytes) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the STDIN pipe, indicating no more data will be sent.
|
||||
*/
|
||||
public function closeStdInput(): void
|
||||
{
|
||||
if (is_resource($this->inputPipe)) {
|
||||
fclose($this->inputPipe);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If a callback is given, invokes it with the output/error produced since the previous call.
|
||||
* @param (\Closure(string, string): void)|null $callback
|
||||
*/
|
||||
private function dispatchCallback(?\Closure $callback): void
|
||||
{
|
||||
if (!$callback) {
|
||||
return;
|
||||
}
|
||||
$output = $this->extractNewData(self::StdOut);
|
||||
$error = $this->extractNewData(self::StdErr);
|
||||
if ($output !== '' || $error !== '') {
|
||||
$callback($output, $error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks if the timeout has expired. If yes, terminates the process.
|
||||
*/
|
||||
private function enforceTimeout(): void
|
||||
{
|
||||
if ($this->timeout !== null && (microtime(true) - $this->startTime) >= $this->timeout) {
|
||||
$this->terminate();
|
||||
throw new ProcessTimeoutException('Process exceeded the time limit of ' . $this->timeout . ' seconds');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads any new data from the specified pipe and appends it to the buffer. Does nothing if the output
|
||||
* is not captured or the pipe is already closed (or handed over to another process).
|
||||
*/
|
||||
private function readFromPipe(int $id): void
|
||||
{
|
||||
if (!isset($this->outputBuffers[$id]) || !is_resource($this->outputPipes[$id] ?? null)) {
|
||||
return;
|
||||
} elseif (Helpers::IsWindows) {
|
||||
fseek($this->outputPipes[$id], strlen($this->outputBuffers[$id]));
|
||||
} else {
|
||||
stream_set_blocking($this->outputPipes[$id], false);
|
||||
}
|
||||
$this->outputBuffers[$id] .= stream_get_contents($this->outputPipes[$id]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends the initial input to the process: writes and closes a string or stream input,
|
||||
* or leaves STDIN open when input is null (until closeStdInput()) or another Process (fed by that process).
|
||||
* The input type was already validated by createInputDescriptor().
|
||||
*
|
||||
* Note: a string or stream input is written upfront, so if it is large and the process does not read it
|
||||
* while filling its own output, this can block; in that case pass null and feed STDIN via writeStdInput().
|
||||
*/
|
||||
private function writeInitialInput(mixed $input): void
|
||||
{
|
||||
if ($input === null || $input instanceof self) {
|
||||
// STDIN stays open
|
||||
|
||||
} elseif (is_string($input)) {
|
||||
$this->writeToPipe($input);
|
||||
$this->closeStdInput();
|
||||
|
||||
} elseif (is_resource($input)) {
|
||||
stream_copy_to_stream($input, $this->inputPipe);
|
||||
$this->closeStdInput();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the input and determines the STDIN descriptor based on its type.
|
||||
*/
|
||||
private function createInputDescriptor(mixed $input): mixed
|
||||
{
|
||||
if ($input === null || is_string($input) || is_resource($input)) {
|
||||
return ['pipe', 'r'];
|
||||
} elseif (!$input instanceof self) {
|
||||
throw new Nette\InvalidArgumentException('Input must be string, resource, Process or null, ' . get_debug_type($input) . ' given.');
|
||||
} elseif (Helpers::IsWindows) {
|
||||
throw new Nette\NotSupportedException('Process piping is not supported on Windows.');
|
||||
} elseif (!isset($input->outputBuffers[self::StdOut]) || !is_resource($input->outputPipes[self::StdOut] ?? null)) {
|
||||
throw new Nette\InvalidStateException('Cannot pipe from the given process: its STDOUT must be captured (it must not be redirected elsewhere).');
|
||||
}
|
||||
return $input->outputPipes[self::StdOut];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines the descriptor for STDOUT or STDERR based on the specified output target.
|
||||
*/
|
||||
private function createOutputDescriptor(int $id, mixed $output): mixed
|
||||
{
|
||||
if (is_resource($output)) {
|
||||
$this->callerOutputs[$id] = true;
|
||||
return $output;
|
||||
|
||||
} elseif (is_string($output)) {
|
||||
return FileSystem::open($output, 'w');
|
||||
|
||||
} elseif ($output === false) {
|
||||
return ['file', Helpers::IsWindows ? 'NUL' : '/dev/null', 'w'];
|
||||
|
||||
} elseif ($output === null) {
|
||||
$this->outputBuffers[$id] = '';
|
||||
$this->outputBufferOffsets[$id] = 0;
|
||||
// On Windows anonymous pipes are blocking and cannot be polled without freezing the process,
|
||||
// so captured output is backed by a temporary file that can be read non-blockingly (needed for timeouts).
|
||||
return Helpers::IsWindows ? tmpfile() : ['pipe', 'w'];
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException('Output must be string, resource, bool or null, ' . get_debug_type($output) . ' given.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes all pipes and the process resource.
|
||||
*/
|
||||
private function close(): void
|
||||
{
|
||||
$this->drainPipes();
|
||||
$this->closeStdInput();
|
||||
$this->closeOutputPipes();
|
||||
proc_close($this->process);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the output pipes that this class opened; resources supplied by the caller are left untouched.
|
||||
* (The temporary file backing captured output on Windows is removed by fclose() itself.)
|
||||
*/
|
||||
private function closeOutputPipes(): void
|
||||
{
|
||||
foreach ($this->outputPipes as $id => $pipe) {
|
||||
if (is_resource($pipe) && !isset($this->callerOutputs[$id])) {
|
||||
fclose($pipe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
vendor/nette/utils/src/Utils/Random.php
vendored
4
vendor/nette/utils/src/Utils/Random.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
|
|||
36
vendor/nette/utils/src/Utils/Reflection.php
vendored
36
vendor/nette/utils/src/Utils/Reflection.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -35,10 +33,14 @@ final class Reflection
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the default value of a parameter. Resolves constants and class constants used as default values.
|
||||
* @throws \ReflectionException if the constant cannot be resolved
|
||||
*/
|
||||
public static function getParameterDefaultValue(\ReflectionParameter $param): mixed
|
||||
{
|
||||
if ($param->isDefaultValueConstant()) {
|
||||
$const = $orig = $param->getDefaultValueConstantName();
|
||||
$const = $orig = $param->getDefaultValueConstantName() ?? throw new Nette\ShouldNotHappenException;
|
||||
$pair = explode('::', $const);
|
||||
if (isset($pair[1])) {
|
||||
$pair[0] = Type::resolve($pair[0], $param);
|
||||
|
|
@ -68,6 +70,7 @@ final class Reflection
|
|||
|
||||
/**
|
||||
* Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait.
|
||||
* @return \ReflectionClass<object>
|
||||
*/
|
||||
public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass
|
||||
{
|
||||
|
|
@ -130,6 +133,9 @@ final class Reflection
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a human-readable string representation of a reflection object.
|
||||
*/
|
||||
public static function toString(\Reflector $ref): string
|
||||
{
|
||||
if ($ref instanceof \ReflectionClass) {
|
||||
|
|
@ -151,6 +157,7 @@ final class Reflection
|
|||
/**
|
||||
* Expands the name of the class to full name in the given context of given class.
|
||||
* Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context.
|
||||
* @param \ReflectionClass<object> $context
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function expandClassName(string $name, \ReflectionClass $context): string
|
||||
|
|
@ -189,7 +196,11 @@ final class Reflection
|
|||
}
|
||||
|
||||
|
||||
/** @return array<string, class-string> of [alias => class] */
|
||||
/**
|
||||
* Returns the use statements from the file where the class is defined.
|
||||
* @param \ReflectionClass<object> $class
|
||||
* @return array<string, class-string> Map of alias to fully qualified class name
|
||||
*/
|
||||
public static function getUseStatements(\ReflectionClass $class): array
|
||||
{
|
||||
if ($class->isAnonymous()) {
|
||||
|
|
@ -201,7 +212,7 @@ final class Reflection
|
|||
if ($class->isInternal()) {
|
||||
$cache[$name] = [];
|
||||
} else {
|
||||
$code = file_get_contents($class->getFileName());
|
||||
$code = (string) file_get_contents((string) $class->getFileName());
|
||||
$cache = self::parseUseStatements($code, $name) + $cache;
|
||||
}
|
||||
}
|
||||
|
|
@ -212,6 +223,7 @@ final class Reflection
|
|||
|
||||
/**
|
||||
* Parses PHP code to [class => [alias => class, ...]]
|
||||
* @return array<string, array<string, string>>
|
||||
*/
|
||||
private static function parseUseStatements(string $code, ?string $forClass = null): array
|
||||
{
|
||||
|
|
@ -256,8 +268,8 @@ final class Reflection
|
|||
$name = ltrim($name, '\\');
|
||||
if (self::fetch($tokens, '{')) {
|
||||
while ($suffix = self::fetch($tokens, $nameTokens)) {
|
||||
if (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
|
||||
if (self::fetch($tokens, T_AS) && ($alias = self::fetch($tokens, T_STRING))) {
|
||||
$uses[$alias] = $name . $suffix;
|
||||
} else {
|
||||
$tmp = explode('\\', $suffix);
|
||||
$uses[end($tmp)] = $name . $suffix;
|
||||
|
|
@ -267,8 +279,8 @@ final class Reflection
|
|||
break;
|
||||
}
|
||||
}
|
||||
} elseif (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name;
|
||||
} elseif (self::fetch($tokens, T_AS) && ($alias = self::fetch($tokens, T_STRING))) {
|
||||
$uses[$alias] = $name;
|
||||
|
||||
} else {
|
||||
$tmp = explode('\\', $name);
|
||||
|
|
@ -301,6 +313,10 @@ final class Reflection
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param \PhpToken[] $tokens
|
||||
* @param string|int|int[] $take
|
||||
*/
|
||||
private static function fetch(array &$tokens, string|int|array $take): ?string
|
||||
{
|
||||
$res = null;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use function explode, is_string, str_contains;
|
||||
|
|
@ -18,9 +16,11 @@ use function explode, is_string, str_contains;
|
|||
*/
|
||||
final class ReflectionMethod extends \ReflectionMethod
|
||||
{
|
||||
private \ReflectionClass $originalClass;
|
||||
/** @var \ReflectionClass<object> */
|
||||
private readonly \ReflectionClass $originalClass;
|
||||
|
||||
|
||||
/** @param class-string|object $objectOrMethod */
|
||||
public function __construct(object|string $objectOrMethod, ?string $method = null)
|
||||
{
|
||||
if (is_string($objectOrMethod) && str_contains($objectOrMethod, '::')) {
|
||||
|
|
@ -31,6 +31,7 @@ final class ReflectionMethod extends \ReflectionMethod
|
|||
}
|
||||
|
||||
|
||||
/** @return \ReflectionClass<object> */
|
||||
public function getOriginalClass(): \ReflectionClass
|
||||
{
|
||||
return $this->originalClass;
|
||||
|
|
|
|||
79
vendor/nette/utils/src/Utils/Strings.php
vendored
79
vendor/nette/utils/src/Utils/Strings.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use JetBrains\PhpStorm\Language;
|
||||
|
|
@ -59,7 +57,8 @@ class Strings
|
|||
throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
|
||||
}
|
||||
|
||||
return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
|
||||
$res = iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code));
|
||||
return $res === false ? throw new Nette\ShouldNotHappenException : $res;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -73,11 +72,11 @@ class Strings
|
|||
}
|
||||
|
||||
$tmp = iconv('UTF-8', 'UTF-32BE//IGNORE', $c);
|
||||
if (!$tmp) {
|
||||
if ($tmp === false || $tmp === '') {
|
||||
throw new Nette\InvalidArgumentException('Invalid UTF-8 character "' . ($c === '' ? '' : '\x' . strtoupper(bin2hex($c))) . '".');
|
||||
}
|
||||
|
||||
return unpack('N', $tmp)[1];
|
||||
return unpack('N', $tmp)[1] ?? throw new Nette\ShouldNotHappenException;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -124,7 +123,8 @@ class Strings
|
|||
$start += self::length($s); // unifies iconv_substr behavior with mb_substr
|
||||
}
|
||||
|
||||
return iconv_substr($s, $start, $length, 'UTF-8');
|
||||
$res = iconv_substr($s, $start, $length, 'UTF-8');
|
||||
return $res === false ? throw new Nette\InvalidStateException('iconv_substr() failed.') : $res;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -135,7 +135,7 @@ class Strings
|
|||
public static function normalize(string $s): string
|
||||
{
|
||||
// convert to compressed normal form (NFC)
|
||||
if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) {
|
||||
if (class_exists('Normalizer', autoload: false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) {
|
||||
$s = $n;
|
||||
}
|
||||
|
||||
|
|
@ -201,14 +201,23 @@ class Strings
|
|||
$s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔
|
||||
}
|
||||
|
||||
$s = \Transliterator::create('Any-Latin; Latin-ASCII')->transliterate($s);
|
||||
$s = \Transliterator::create('Any-Latin; Latin-ASCII')?->transliterate($s)
|
||||
?? throw new Nette\InvalidStateException('Transliterator::transliterate() failed.');
|
||||
|
||||
// use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ
|
||||
if ($iconv === 'glibc') {
|
||||
$s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
if ($s === false) {
|
||||
throw new Nette\InvalidStateException('iconv() failed.');
|
||||
}
|
||||
|
||||
$s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters
|
||||
} elseif ($iconv === 'libiconv') {
|
||||
$s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
|
||||
if ($s === false) {
|
||||
throw new Nette\InvalidStateException('iconv() failed.');
|
||||
}
|
||||
} else { // null or 'unknown' (#216)
|
||||
$s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars
|
||||
}
|
||||
|
|
@ -323,7 +332,7 @@ class Strings
|
|||
*/
|
||||
public static function compare(string $left, string $right, ?int $length = null): bool
|
||||
{
|
||||
if (class_exists('Normalizer', false)) {
|
||||
if (class_exists('Normalizer', autoload: false)) {
|
||||
$left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster
|
||||
$right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster
|
||||
}
|
||||
|
|
@ -347,6 +356,10 @@ class Strings
|
|||
public static function findPrefix(array $strings): string
|
||||
{
|
||||
$first = array_shift($strings);
|
||||
if ($first === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
for ($i = 0; $i < strlen($first); $i++) {
|
||||
foreach ($strings as $s) {
|
||||
if (!isset($s[$i]) || $first[$i] !== $s[$i]) {
|
||||
|
|
@ -370,8 +383,8 @@ class Strings
|
|||
public static function length(string $s): int
|
||||
{
|
||||
return match (true) {
|
||||
extension_loaded('mbstring') => mb_strlen($s, 'UTF-8'),
|
||||
extension_loaded('iconv') => iconv_strlen($s, 'UTF-8'),
|
||||
extension_loaded('mbstring') => (int) mb_strlen($s, 'UTF-8'),
|
||||
extension_loaded('iconv') => (int) iconv_strlen($s, 'UTF-8'),
|
||||
default => strlen(@utf8_decode($s)), // deprecated
|
||||
};
|
||||
}
|
||||
|
|
@ -420,7 +433,10 @@ class Strings
|
|||
throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.');
|
||||
}
|
||||
|
||||
return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s)));
|
||||
$tmp = iconv('UTF-8', 'UTF-32BE', $s);
|
||||
return $tmp === false
|
||||
? throw new Nette\InvalidStateException('iconv() failed.')
|
||||
: (string) iconv('UTF-32LE', 'UTF-8', strrev($tmp));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -498,7 +514,8 @@ class Strings
|
|||
|
||||
|
||||
/**
|
||||
* Divides the string into arrays according to the regular expression. Expressions in parentheses will be captured and returned as well.
|
||||
* Splits the string by a regular expression. Expressions in parentheses will be captured and returned as well.
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function split(
|
||||
string $subject,
|
||||
|
|
@ -523,8 +540,9 @@ class Strings
|
|||
|
||||
|
||||
/**
|
||||
* Searches the string for the part matching the regular expression and returns
|
||||
* an array with the found expression and individual subexpressions, or `null`.
|
||||
* Searches the string for the first match of the regular expression and returns
|
||||
* an array with the found expression and individual subexpressions, or null.
|
||||
* @return ?array<string>
|
||||
*/
|
||||
public static function match(
|
||||
string $subject,
|
||||
|
|
@ -545,6 +563,7 @@ class Strings
|
|||
$pattern .= 'u';
|
||||
}
|
||||
|
||||
$m = [];
|
||||
if ($offset > strlen($subject)) {
|
||||
return null;
|
||||
} elseif (!self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])) {
|
||||
|
|
@ -558,9 +577,9 @@ class Strings
|
|||
|
||||
|
||||
/**
|
||||
* Searches the string for all occurrences matching the regular expression and
|
||||
* returns an array of arrays containing the found expression and each subexpression.
|
||||
* @return ($lazy is true ? \Generator<int, array> : array[])
|
||||
* Searches the string for all occurrences matching the regular expression and returns
|
||||
* an array of arrays containing the found expression and each subexpression.
|
||||
* @return ($lazy is true ? \Generator<int, array<string>> : list<array<string>>)
|
||||
*/
|
||||
public static function matchAll(
|
||||
string $subject,
|
||||
|
|
@ -583,10 +602,12 @@ class Strings
|
|||
$flags = PREG_OFFSET_CAPTURE | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0);
|
||||
return (function () use ($utf8, $captureOffset, $flags, $subject, $pattern, $offset) {
|
||||
$counter = 0;
|
||||
$m = [];
|
||||
while (
|
||||
$offset <= strlen($subject) - ($counter ? 1 : 0)
|
||||
&& self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])
|
||||
) {
|
||||
/** @var list<array{string, int}> $m */
|
||||
$offset = $m[0][1] + max(1, strlen($m[0][0]));
|
||||
if (!$captureOffset) {
|
||||
$m = array_map(fn($item) => $item[0], $m);
|
||||
|
|
@ -606,6 +627,7 @@ class Strings
|
|||
? $captureOffset
|
||||
: ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0);
|
||||
|
||||
$m = [];
|
||||
self::pcre('preg_match_all', [
|
||||
$pattern, $subject, &$m,
|
||||
($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER),
|
||||
|
|
@ -618,7 +640,8 @@ class Strings
|
|||
|
||||
|
||||
/**
|
||||
* Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`.
|
||||
* Replaces all occurrences matching the regular expression $pattern, which can be a string or array in the form `pattern => replacement`.
|
||||
* @param string|array<string, string> $pattern
|
||||
*/
|
||||
public static function replace(
|
||||
string $subject,
|
||||
|
|
@ -638,7 +661,7 @@ class Strings
|
|||
|
||||
$flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0);
|
||||
if ($utf8) {
|
||||
$pattern .= 'u';
|
||||
$pattern = is_array($pattern) ? array_map(fn($item) => $item . 'u', $pattern) : $pattern . 'u';
|
||||
if ($captureOffset) {
|
||||
$replacement = fn($m) => $replacement(self::bytesToChars($subject, [$m])[0]);
|
||||
}
|
||||
|
|
@ -659,6 +682,10 @@ class Strings
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param list<array<array{string, int}>> $groups
|
||||
* @return list<array<array{string, int}>>
|
||||
*/
|
||||
private static function bytesToChars(string $s, array $groups): array
|
||||
{
|
||||
$lastBytes = $lastChars = 0;
|
||||
|
|
@ -679,8 +706,12 @@ class Strings
|
|||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public static function pcre(string $func, array $args)
|
||||
/**
|
||||
* @param callable-string $func
|
||||
* @param list<mixed> $args
|
||||
* @internal
|
||||
*/
|
||||
public static function pcre(string $func, array $args): mixed
|
||||
{
|
||||
$res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void {
|
||||
// compile-time error, not detectable by preg_last_error
|
||||
|
|
@ -688,7 +719,7 @@ class Strings
|
|||
});
|
||||
|
||||
if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars
|
||||
&& ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true))
|
||||
&& ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], strict: true))
|
||||
) {
|
||||
throw new RegexpException(preg_last_error_msg()
|
||||
. ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code);
|
||||
|
|
|
|||
82
vendor/nette/utils/src/Utils/Type.php
vendored
82
vendor/nette/utils/src/Utils/Type.php
vendored
|
|
@ -1,16 +1,14 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
use function array_map, array_search, array_splice, count, explode, implode, is_a, is_resource, is_string, strcasecmp, strtolower, substr, trim;
|
||||
use function array_map, array_search, array_splice, array_values, count, explode, implode, is_a, is_resource, is_string, strcasecmp, strtolower, substr, trim;
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -18,9 +16,9 @@ use function array_map, array_search, array_splice, count, explode, implode, is_
|
|||
*/
|
||||
final readonly class Type
|
||||
{
|
||||
/** @var array<int, string|self> */
|
||||
/** @var list<string|self> */
|
||||
private array $types;
|
||||
private bool $simple;
|
||||
private ?string $singleName;
|
||||
private string $kind; // | &
|
||||
|
||||
|
||||
|
|
@ -40,7 +38,12 @@ final readonly class Type
|
|||
}
|
||||
|
||||
|
||||
private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject): self|string
|
||||
/** @return ($asObject is true ? self : self|string) */
|
||||
private static function fromReflectionType(
|
||||
\ReflectionType $type,
|
||||
\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of,
|
||||
bool $asObject,
|
||||
): self|string
|
||||
{
|
||||
if ($type instanceof \ReflectionNamedType) {
|
||||
$name = self::resolve($type->getName(), $of);
|
||||
|
|
@ -107,34 +110,40 @@ final readonly class Type
|
|||
*/
|
||||
public static function resolve(
|
||||
string $type,
|
||||
\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of,
|
||||
\ReflectionFunction|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $of,
|
||||
): string
|
||||
{
|
||||
$lower = strtolower($type);
|
||||
if ($of instanceof \ReflectionFunction) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
$class = $of->getDeclaringClass();
|
||||
if ($class === null) {
|
||||
return $type;
|
||||
} elseif ($lower === 'self') {
|
||||
return $of->getDeclaringClass()->name;
|
||||
return $class->name;
|
||||
} elseif ($lower === 'static') {
|
||||
return ($of instanceof ReflectionMethod ? $of->getOriginalClass() : $of->getDeclaringClass())->name;
|
||||
} elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) {
|
||||
return $of->getDeclaringClass()->getParentClass()->name;
|
||||
return ($of instanceof ReflectionMethod ? $of->getOriginalClass() : $class)->name;
|
||||
} elseif ($lower === 'parent' && $class->getParentClass()) {
|
||||
return $class->getParentClass()->name;
|
||||
} else {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @param array<string|self> $types */
|
||||
private function __construct(array $types, string $kind = '|')
|
||||
{
|
||||
$o = array_search('null', $types, strict: true);
|
||||
if ($o !== false) { // null as last
|
||||
array_splice($types, $o, 1);
|
||||
array_splice($types, (int) $o, 1);
|
||||
$types[] = 'null';
|
||||
}
|
||||
|
||||
$this->types = $types;
|
||||
$this->simple = is_string($types[0]) && ($types[1] ?? 'null') === 'null';
|
||||
$this->types = array_values($types);
|
||||
$this->singleName = is_string($types[0]) && ($types[1] ?? 'null') === 'null' ? $types[0] : null;
|
||||
$this->kind = count($types) > 1 ? $kind : '';
|
||||
}
|
||||
|
||||
|
|
@ -142,8 +151,8 @@ final readonly class Type
|
|||
public function __toString(): string
|
||||
{
|
||||
$multi = count($this->types) > 1;
|
||||
if ($this->simple) {
|
||||
return ($multi ? '?' : '') . $this->types[0];
|
||||
if ($this->singleName !== null) {
|
||||
return ($multi ? '?' : '') . $this->singleName;
|
||||
}
|
||||
|
||||
$res = [];
|
||||
|
|
@ -155,7 +164,7 @@ final readonly class Type
|
|||
|
||||
|
||||
/**
|
||||
* Returns a type that accepts both the current type and the given type.
|
||||
* Returns a union type that accepts both the current type and the given type.
|
||||
*/
|
||||
public function with(string|self $type): self
|
||||
{
|
||||
|
|
@ -173,7 +182,7 @@ final readonly class Type
|
|||
|
||||
/**
|
||||
* Returns the array of subtypes that make up the compound type as strings.
|
||||
* @return array<int, string|string[]>
|
||||
* @return list<string|array<string|array<mixed>>>
|
||||
*/
|
||||
public function getNames(): array
|
||||
{
|
||||
|
|
@ -182,8 +191,8 @@ final readonly class Type
|
|||
|
||||
|
||||
/**
|
||||
* Returns the array of subtypes that make up the compound type as Type objects:
|
||||
* @return self[]
|
||||
* Returns the array of subtypes that make up the compound type as Type objects.
|
||||
* @return list<self>
|
||||
*/
|
||||
public function getTypes(): array
|
||||
{
|
||||
|
|
@ -196,9 +205,7 @@ final readonly class Type
|
|||
*/
|
||||
public function getSingleName(): ?string
|
||||
{
|
||||
return $this->simple
|
||||
? $this->types[0]
|
||||
: null;
|
||||
return $this->singleName;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -221,36 +228,36 @@ final readonly class Type
|
|||
|
||||
|
||||
/**
|
||||
* Returns true whether it is a simple type. Single nullable types are also considered to be simple types.
|
||||
* Checks whether it is a simple (non-compound) type. Single nullable types such as ?int are also considered simple.
|
||||
*/
|
||||
public function isSimple(): bool
|
||||
{
|
||||
return $this->simple;
|
||||
return $this->singleName !== null;
|
||||
}
|
||||
|
||||
|
||||
#[\Deprecated('use isSimple()')]
|
||||
public function isSingle(): bool
|
||||
{
|
||||
return $this->simple;
|
||||
return $this->singleName !== null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether the type is both a simple and a PHP built-in type.
|
||||
* Checks whether it is a simple PHP built-in type (int, string, bool, etc.).
|
||||
*/
|
||||
public function isBuiltin(): bool
|
||||
{
|
||||
return $this->simple && Validators::isBuiltinType($this->types[0]);
|
||||
return $this->singleName !== null && Validators::isBuiltinType($this->singleName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true whether the type is both a simple and a class name.
|
||||
* Checks whether it is a simple class or interface name (not a built-in type).
|
||||
*/
|
||||
public function isClass(): bool
|
||||
{
|
||||
return $this->simple && !Validators::isBuiltinType($this->types[0]);
|
||||
return $this->singleName !== null && !Validators::isBuiltinType($this->singleName);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -259,12 +266,12 @@ final readonly class Type
|
|||
*/
|
||||
public function isClassKeyword(): bool
|
||||
{
|
||||
return $this->simple && Validators::isClassKeyword($this->types[0]);
|
||||
return $this->singleName !== null && Validators::isClassKeyword($this->singleName);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter.
|
||||
* Checks whether a value of the given type could be assigned to this type.
|
||||
*/
|
||||
public function allows(string|self $type): bool
|
||||
{
|
||||
|
|
@ -279,6 +286,7 @@ final readonly class Type
|
|||
}
|
||||
|
||||
|
||||
/** @param array<string> $givenTypes */
|
||||
private function allowsAny(array $givenTypes): bool
|
||||
{
|
||||
return $this->isUnion()
|
||||
|
|
@ -287,13 +295,17 @@ final readonly class Type
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array<string> $ourTypes
|
||||
* @param array<string> $givenTypes
|
||||
*/
|
||||
private function allowsAll(array $ourTypes, array $givenTypes): bool
|
||||
{
|
||||
return Arrays::every(
|
||||
$ourTypes,
|
||||
fn($ourType) => Arrays::some(
|
||||
fn(string $ourType) => Arrays::some(
|
||||
$givenTypes,
|
||||
fn($givenType) => Validators::isBuiltinType($ourType)
|
||||
fn(string $givenType) => Validators::isBuiltinType($ourType)
|
||||
? strcasecmp($ourType, $givenType) === 0
|
||||
: is_a($givenType, $ourType, allow_string: true),
|
||||
),
|
||||
|
|
|
|||
23
vendor/nette/utils/src/Utils/Validators.php
vendored
23
vendor/nette/utils/src/Utils/Validators.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
@ -26,7 +24,7 @@ class Validators
|
|||
'never' => 1, 'true' => 1,
|
||||
];
|
||||
|
||||
/** @var array<string,?callable> */
|
||||
/** @var array<string, ?(callable(mixed): bool)> */
|
||||
protected static $validators = [
|
||||
// PHP types
|
||||
'array' => 'is_array',
|
||||
|
|
@ -76,7 +74,7 @@ class Validators
|
|||
'type' => [self::class, 'isType'],
|
||||
];
|
||||
|
||||
/** @var array<string,callable> */
|
||||
/** @var array<string, callable(mixed): int> */
|
||||
protected static $counters = [
|
||||
'string' => 'strlen',
|
||||
'unicode' => [Strings::class, 'length'],
|
||||
|
|
@ -114,22 +112,22 @@ class Validators
|
|||
|
||||
|
||||
/**
|
||||
* Verifies that element $key in array is of expected types separated by pipe.
|
||||
* Verifies that item $key in array exists and is of expected types separated by pipe.
|
||||
* @param mixed[] $array
|
||||
* @throws AssertionException
|
||||
*/
|
||||
public static function assertField(
|
||||
array $array,
|
||||
$key,
|
||||
int|string $key,
|
||||
?string $expected = null,
|
||||
string $label = "item '%' in array",
|
||||
): void
|
||||
{
|
||||
if (!array_key_exists($key, $array)) {
|
||||
throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.');
|
||||
throw new AssertionException('Missing ' . str_replace('%', (string) $key, $label) . '.');
|
||||
|
||||
} elseif ($expected) {
|
||||
static::assert($array[$key], $expected, str_replace('%', $key, $label));
|
||||
static::assert($array[$key], $expected, str_replace('%', (string) $key, $label));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +157,7 @@ class Validators
|
|||
if (!static::$validators[$type]($value)) {
|
||||
continue;
|
||||
}
|
||||
} catch (\TypeError $e) {
|
||||
} catch (\TypeError) {
|
||||
continue;
|
||||
}
|
||||
} elseif ($type === 'pattern') {
|
||||
|
|
@ -197,7 +195,7 @@ class Validators
|
|||
|
||||
/**
|
||||
* Finds whether all values are of expected types separated by pipe.
|
||||
* @param mixed[] $values
|
||||
* @param iterable<mixed> $values
|
||||
*/
|
||||
public static function everyIs(iterable $values, string $expected): bool
|
||||
{
|
||||
|
|
@ -261,7 +259,7 @@ class Validators
|
|||
|
||||
/**
|
||||
* Checks if the value is 0, '', false or null.
|
||||
* @return ($value is 0|''|false|null ? true : false)
|
||||
* @return ($value is 0|0.0|''|false|null ? true : false)
|
||||
*/
|
||||
public static function isNone(mixed $value): bool
|
||||
{
|
||||
|
|
@ -290,6 +288,7 @@ class Validators
|
|||
/**
|
||||
* Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null).
|
||||
* Numbers, strings and DateTime objects can be compared.
|
||||
* @param array{int|float|string|\DateTimeInterface|null, int|float|string|\DateTimeInterface|null} $range
|
||||
*/
|
||||
public static function isInRange(mixed $value, array $range): bool
|
||||
{
|
||||
|
|
|
|||
20
vendor/nette/utils/src/Utils/exceptions.php
vendored
20
vendor/nette/utils/src/Utils/exceptions.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
|
||||
|
|
@ -48,3 +46,19 @@ class RegexpException extends \Exception
|
|||
class AssertionException extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The process failed to run successfully.
|
||||
*/
|
||||
class ProcessFailedException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The process execution exceeded its timeout limit.
|
||||
*/
|
||||
class ProcessTimeoutException extends \RuntimeException
|
||||
{
|
||||
}
|
||||
|
|
|
|||
4
vendor/nette/utils/src/compatibility.php
vendored
4
vendor/nette/utils/src/compatibility.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\Utils;
|
||||
|
||||
use Nette;
|
||||
|
|
|
|||
4
vendor/nette/utils/src/exceptions.php
vendored
4
vendor/nette/utils/src/exceptions.php
vendored
|
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette;
|
||||
|
||||
|
||||
|
|
|
|||
15
vendor/psr/event-dispatcher/.editorconfig
vendored
Normal file
15
vendor/psr/event-dispatcher/.editorconfig
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
; This file is for unifying the coding style for different editors and IDEs.
|
||||
; More information at http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
2
vendor/psr/event-dispatcher/.gitignore
vendored
Normal file
2
vendor/psr/event-dispatcher/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
/vendor/
|
||||
composer.lock
|
||||
6
vendor/psr/event-dispatcher/README.md
vendored
Normal file
6
vendor/psr/event-dispatcher/README.md
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
PSR Event Dispatcher
|
||||
====================
|
||||
|
||||
This repository holds the interfaces related to [PSR-14](http://www.php-fig.org/psr/psr-14/).
|
||||
|
||||
Note that this is not an Event Dispatcher implementation of its own. It is merely interfaces that describe the components of an Event Dispatcher. See the specification for more details.
|
||||
26
vendor/psr/event-dispatcher/composer.json
vendored
Normal file
26
vendor/psr/event-dispatcher/composer.json
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "psr/event-dispatcher",
|
||||
"description": "Standard interfaces for event handling.",
|
||||
"type": "library",
|
||||
"keywords": ["psr", "psr-14", "events"],
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Psr\\EventDispatcher\\": "src/"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.0.x-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
5
vendor/symfony/deprecation-contracts/CHANGELOG.md
vendored
Normal file
5
vendor/symfony/deprecation-contracts/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
The changelog is maintained for all Symfony contracts at the following URL:
|
||||
https://github.com/symfony/contracts/blob/main/CHANGELOG.md
|
||||
26
vendor/symfony/deprecation-contracts/README.md
vendored
Normal file
26
vendor/symfony/deprecation-contracts/README.md
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
Symfony Deprecation Contracts
|
||||
=============================
|
||||
|
||||
A generic function and convention to trigger deprecation notices.
|
||||
|
||||
This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices.
|
||||
|
||||
By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component,
|
||||
the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments.
|
||||
|
||||
The function requires at least 3 arguments:
|
||||
- the name of the Composer package that is triggering the deprecation
|
||||
- the version of the package that introduced the deprecation
|
||||
- the message of the deprecation
|
||||
- more arguments can be provided: they will be inserted in the message using `printf()` formatting
|
||||
|
||||
Example:
|
||||
```php
|
||||
trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin');
|
||||
```
|
||||
|
||||
This will generate the following message:
|
||||
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
|
||||
|
||||
While not recommended, the deprecation notices can be completely ignored by declaring an empty
|
||||
`function trigger_deprecation() {}` in your application.
|
||||
35
vendor/symfony/deprecation-contracts/composer.json
vendored
Normal file
35
vendor/symfony/deprecation-contracts/composer.json
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"type": "library",
|
||||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"function.php"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.7-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
"url": "https://github.com/symfony/contracts"
|
||||
}
|
||||
}
|
||||
}
|
||||
2
vendor/symfony/polyfill-php80/Php80.php
vendored
2
vendor/symfony/polyfill-php80/Php80.php
vendored
|
|
@ -60,7 +60,7 @@ final class Php80
|
|||
public static function get_resource_id($res): int
|
||||
{
|
||||
if (!\is_resource($res) && null === @get_resource_type($res)) {
|
||||
throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
|
||||
throw new \TypeError(\sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
|
||||
}
|
||||
|
||||
return (int) $res;
|
||||
|
|
|
|||
25
vendor/symfony/polyfill-php80/README.md
vendored
Normal file
25
vendor/symfony/polyfill-php80/README.md
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
Symfony Polyfill / Php80
|
||||
========================
|
||||
|
||||
This component provides features added to PHP 8.0 core:
|
||||
|
||||
- [`Stringable`](https://php.net/stringable) interface
|
||||
- [`fdiv`](https://php.net/fdiv)
|
||||
- [`ValueError`](https://php.net/valueerror) class
|
||||
- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class
|
||||
- `FILTER_VALIDATE_BOOL` constant
|
||||
- [`get_debug_type`](https://php.net/get_debug_type)
|
||||
- [`PhpToken`](https://php.net/phptoken) class
|
||||
- [`preg_last_error_msg`](https://php.net/preg_last_error_msg)
|
||||
- [`str_contains`](https://php.net/str_contains)
|
||||
- [`str_starts_with`](https://php.net/str_starts_with)
|
||||
- [`str_ends_with`](https://php.net/str_ends_with)
|
||||
- [`get_resource_id`](https://php.net/get_resource_id)
|
||||
|
||||
More information can be found in the
|
||||
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
This library is released under the [MIT license](LICENSE).
|
||||
37
vendor/symfony/polyfill-php80/composer.json
vendored
Normal file
37
vendor/symfony/polyfill-php80/composer.json
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"type": "library",
|
||||
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
|
||||
"keywords": ["polyfill", "shim", "compatibility", "portable"],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Ion Bazan",
|
||||
"email": "ion.bazan@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Polyfill\\Php80\\": "" },
|
||||
"files": [ "bootstrap.php" ],
|
||||
"classmap": [ "Resources/stubs" ]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue