maj
This commit is contained in:
		
							
								
								
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										63
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,3 +1,66 @@
 | 
			
		||||
# v1.7.25
 | 
			
		||||
## 11/16/2021
 | 
			
		||||
 | 
			
		||||
1. [](#new)
 | 
			
		||||
    * Updated phpstan to v1.0
 | 
			
		||||
    * Added `FlexObject::getDiff()` to see difference to the saved object
 | 
			
		||||
2. [](#improved)
 | 
			
		||||
    * Use Symfony `dump` instead of PHP's `vardump` in side the `{{ vardump(x) }}` Twig vardump function
 | 
			
		||||
    * Added `route` and `request` to `onPagesInitialized` event
 | 
			
		||||
    * Improved page cloning, added method `Page::initialize()`
 | 
			
		||||
    * Improved `FlexObject::getChanges()`: return changed lists and arrays as whole instead of just changed keys/values
 | 
			
		||||
    * Improved form validation JSON responses to contain list of failed fields with their error messages
 | 
			
		||||
    * Improved redirects: send redirect response in JSON if the request was in JSON
 | 
			
		||||
3. [](#bugfix)
 | 
			
		||||
    * Fixed path traversal vulnerability when using `bin/grav server`
 | 
			
		||||
    * Fixed unescaped error messages in JSON error responses
 | 
			
		||||
    * Fixed `|t(variable)` twig filter in admin
 | 
			
		||||
    * Fixed `FlexObject::getChanges()` always returning empty array
 | 
			
		||||
    * Fixed form validation exceptions to use `400 Bad Request` instead of `500 Internal Server Error`
 | 
			
		||||
 | 
			
		||||
# v1.7.24
 | 
			
		||||
## 10/26/2021
 | 
			
		||||
 | 
			
		||||
1. [](#new)
 | 
			
		||||
    * Added support for image watermarks
 | 
			
		||||
    * Added support to disable a form, making it readonly
 | 
			
		||||
2. [](#improved)
 | 
			
		||||
    * Flex `$user->authorize()` now checks user groups before `admin.super`, allowing deny rules to work properly
 | 
			
		||||
3. [](#bugfix)
 | 
			
		||||
    * Fixed a bug in `PermissionsReader` in PHP 7.3
 | 
			
		||||
    * Fixed `session_store_active` language option (#3464)
 | 
			
		||||
    * Fixed deprecated warnings on `ArrayAccess` in PHP 8.1
 | 
			
		||||
    * Fixed XSS detection with `:`
 | 
			
		||||
 | 
			
		||||
# v1.7.23
 | 
			
		||||
## 09/29/2021
 | 
			
		||||
 | 
			
		||||
1. [](#new)
 | 
			
		||||
    * Added method `Pages::referrerRoute()` to get the referrer route and language
 | 
			
		||||
    * Added true unique `Utils::uniqueId()` / `{{ unique_id() }}` utilities  with length, prefix, and suffix support
 | 
			
		||||
    * Added `UserObject::isMyself()` method to check if flex user is currently logged in
 | 
			
		||||
    * Added support for custom form field options validation with `validate: options: key|ignore`
 | 
			
		||||
2. [](#improved)
 | 
			
		||||
   * Replaced GPL `SVG-Sanitizer` with MIT licensed `DOM-Sanitizer`
 | 
			
		||||
   * `Uri::referrer()` now accepts third parameter, if set to `true`, it returns route without base or language code [#3411](https://github.com/getgrav/grav/issues/3411)
 | 
			
		||||
   * Updated vendor libs with latest
 | 
			
		||||
   * Updated with latest language strings via Crowdin.com
 | 
			
		||||
3. [](#bugfix)
 | 
			
		||||
    * Fixed `Folder::move()` throwing an error when target folder is changed by only appending characters to the end [#3445](https://github.com/getgrav/grav/issues/3445)
 | 
			
		||||
    * Fixed some phpstan issues (all code back to level 1, Framework level 3)
 | 
			
		||||
    * Fixed form reset causing image uploads to fail when using Flex
 | 
			
		||||
 | 
			
		||||
# v1.7.22
 | 
			
		||||
## 09/16/2021
 | 
			
		||||
 | 
			
		||||
1. [](#new)
 | 
			
		||||
    * Register plugin autoloaders into plugin objects
 | 
			
		||||
2. [](#improved)
 | 
			
		||||
    * Improve Twig 2 compatibility
 | 
			
		||||
    * Update to customized version of Twig DeferredExtension (Twig 1/2 compatible)
 | 
			
		||||
3. [](#bugfix)
 | 
			
		||||
    * Fixed conflicting `$_original` variable in `Flex Pages`
 | 
			
		||||
 | 
			
		||||
# v1.7.21
 | 
			
		||||
## 09/14/2021
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,10 @@
 | 
			
		||||
        "ext-dom": "*",
 | 
			
		||||
        "ext-libxml": "*",
 | 
			
		||||
        "symfony/polyfill-mbstring": "~1.20",
 | 
			
		||||
        "symfony/polyfill-iconv": "^1.20",
 | 
			
		||||
        "symfony/polyfill-php74": "^1.20",
 | 
			
		||||
        "symfony/polyfill-php80": "^1.20",
 | 
			
		||||
        "symfony/polyfill-iconv": "^1.23",
 | 
			
		||||
        "symfony/polyfill-php74": "^1.23",
 | 
			
		||||
        "symfony/polyfill-php80": "^1.23",
 | 
			
		||||
        "symfony/polyfill-php81": "^1.23",
 | 
			
		||||
        "psr/simple-cache": "^1.0",
 | 
			
		||||
        "psr/http-message": "^1.0",
 | 
			
		||||
        "psr/http-server-middleware": "^1.0",
 | 
			
		||||
@@ -55,17 +56,16 @@
 | 
			
		||||
        "miljar/php-exif": "^0.6",
 | 
			
		||||
        "composer/ca-bundle": "^1.2",
 | 
			
		||||
        "dragonmantank/cron-expression": "^1.2",
 | 
			
		||||
        "phive/twig-extensions-deferred": "^1.0",
 | 
			
		||||
        "willdurand/negotiation": "^3.0",
 | 
			
		||||
        "itsgoingd/clockwork": "^5.0",
 | 
			
		||||
        "enshrined/svg-sanitize": "~0.13",
 | 
			
		||||
        "symfony/http-client": "^4.4",
 | 
			
		||||
        "composer/semver": "^1.4"
 | 
			
		||||
        "composer/semver": "^1.4",
 | 
			
		||||
        "rhukster/dom-sanitizer": "^1.0"
 | 
			
		||||
    },
 | 
			
		||||
    "require-dev": {
 | 
			
		||||
        "codeception/codeception": "^4.1",
 | 
			
		||||
        "phpstan/phpstan": "^0.12",
 | 
			
		||||
        "phpstan/phpstan-deprecation-rules": "^0.12",
 | 
			
		||||
        "phpstan/phpstan": "^1.0",
 | 
			
		||||
        "phpstan/phpstan-deprecation-rules": "^1.0",
 | 
			
		||||
        "phpunit/php-code-coverage": "~9.2",
 | 
			
		||||
        "getgrav/markdowndocs": "^2.0",
 | 
			
		||||
        "codeception/module-asserts": "^1.3",
 | 
			
		||||
@@ -93,7 +93,8 @@
 | 
			
		||||
    },
 | 
			
		||||
    "autoload": {
 | 
			
		||||
        "psr-4": {
 | 
			
		||||
            "Grav\\": "system/src/Grav"
 | 
			
		||||
            "Grav\\": "system/src/Grav",
 | 
			
		||||
            "Twig\\": "system/src/Twig"
 | 
			
		||||
        },
 | 
			
		||||
        "files": [
 | 
			
		||||
            "system/defines.php"
 | 
			
		||||
@@ -107,8 +108,8 @@
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "api-17": "vendor/bin/phpdoc-md generate system/src > user/pages/14.api/default.17.md",
 | 
			
		||||
        "post-create-project-cmd": "bin/grav install",
 | 
			
		||||
        "phpstan": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/phpstan.neon --memory-limit=480M system/src",
 | 
			
		||||
        "phpstan-framework": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/phpstan.neon --memory-limit=480M system/src/Grav/Framework system/src/Grav/Events system/src/Grav/Installer",
 | 
			
		||||
        "phpstan": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/phpstan.neon --memory-limit=520M system/src",
 | 
			
		||||
        "phpstan-framework": "vendor/bin/phpstan analyse -l 3 -c ./tests/phpstan/phpstan.neon --memory-limit=480M system/src/Grav/Framework system/src/Grav/Events system/src/Grav/Installer",
 | 
			
		||||
        "phpstan-plugins": "vendor/bin/phpstan analyse -l 1 -c ./tests/phpstan/plugins.neon --memory-limit=400M user/plugins",
 | 
			
		||||
        "test": "vendor/bin/codecept run unit",
 | 
			
		||||
        "test-windows": "vendor\\bin\\codecept run unit"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										663
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										663
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1446,6 +1446,10 @@ form:
 | 
			
		||||
              title: PLUGIN_ADMIN.ADVANCED
 | 
			
		||||
              underline: true
 | 
			
		||||
 | 
			
		||||
            gpm_section:
 | 
			
		||||
              type: section
 | 
			
		||||
              title: PLUGIN_ADMIN.GPM_SECTION
 | 
			
		||||
 | 
			
		||||
            gpm.releases:
 | 
			
		||||
              type: toggle
 | 
			
		||||
              label: PLUGIN_ADMIN.GPM_RELEASES
 | 
			
		||||
@@ -1455,23 +1459,6 @@ form:
 | 
			
		||||
                stable: PLUGIN_ADMIN.STABLE
 | 
			
		||||
                testing: PLUGIN_ADMIN.TESTING
 | 
			
		||||
 | 
			
		||||
            gpm.proxy_url:
 | 
			
		||||
              type: text
 | 
			
		||||
              size: medium
 | 
			
		||||
              placeholder: "e.g. 127.0.0.1:3128"
 | 
			
		||||
              label: PLUGIN_ADMIN.PROXY_URL
 | 
			
		||||
              help: PLUGIN_ADMIN.PROXY_URL_HELP
 | 
			
		||||
 | 
			
		||||
            gpm.method:
 | 
			
		||||
              type: toggle
 | 
			
		||||
              label: PLUGIN_ADMIN.GPM_METHOD
 | 
			
		||||
              highlight: auto
 | 
			
		||||
              help: PLUGIN_ADMIN.GPM_METHOD_HELP
 | 
			
		||||
              options:
 | 
			
		||||
                auto: PLUGIN_ADMIN.AUTO
 | 
			
		||||
                fopen: PLUGIN_ADMIN.FOPEN
 | 
			
		||||
                curl: PLUGIN_ADMIN.CURL
 | 
			
		||||
 | 
			
		||||
            gpm.official_gpm_only:
 | 
			
		||||
              type: toggle
 | 
			
		||||
              label: PLUGIN_ADMIN.GPM_OFFICIAL_ONLY
 | 
			
		||||
@@ -1484,17 +1471,80 @@ form:
 | 
			
		||||
              validate:
 | 
			
		||||
                type: bool
 | 
			
		||||
 | 
			
		||||
            gpm.verify_peer:
 | 
			
		||||
            http_section:
 | 
			
		||||
              type: section
 | 
			
		||||
              title: PLUGIN_ADMIN.HTTP_SECTION
 | 
			
		||||
 | 
			
		||||
            http.method:
 | 
			
		||||
              type: toggle
 | 
			
		||||
              label: PLUGIN_ADMIN.GPM_VERIFY_PEER
 | 
			
		||||
              label: PLUGIN_ADMIN.GPM_METHOD
 | 
			
		||||
              highlight: auto
 | 
			
		||||
              help: PLUGIN_ADMIN.GPM_METHOD_HELP
 | 
			
		||||
              options:
 | 
			
		||||
                auto: PLUGIN_ADMIN.AUTO
 | 
			
		||||
                fopen: PLUGIN_ADMIN.FOPEN
 | 
			
		||||
                curl: PLUGIN_ADMIN.CURL
 | 
			
		||||
 | 
			
		||||
            http.enable_proxy:
 | 
			
		||||
              type: toggle
 | 
			
		||||
              label: PLUGIN_ADMIN.SSL_ENABLE_PROXY
 | 
			
		||||
              highlight: 1
 | 
			
		||||
              help: PLUGIN_ADMIN.GPM_VERIFY_PEER_HELP
 | 
			
		||||
              options:
 | 
			
		||||
                1: PLUGIN_ADMIN.YES
 | 
			
		||||
                0: PLUGIN_ADMIN.NO
 | 
			
		||||
              default: false
 | 
			
		||||
              validate:
 | 
			
		||||
                type: bool
 | 
			
		||||
 | 
			
		||||
            http.proxy_url:
 | 
			
		||||
              type: text
 | 
			
		||||
              size: medium
 | 
			
		||||
              placeholder: "e.g. 127.0.0.1:3128"
 | 
			
		||||
              label: PLUGIN_ADMIN.PROXY_URL
 | 
			
		||||
              help: PLUGIN_ADMIN.PROXY_URL_HELP
 | 
			
		||||
 | 
			
		||||
            http.proxy_cert_path:
 | 
			
		||||
              type: text
 | 
			
		||||
              size: medium
 | 
			
		||||
              placeholder: "e.g. /Users/bob/certs/"
 | 
			
		||||
              label: PLUGIN_ADMIN.PROXY_CERT
 | 
			
		||||
              help: PLUGIN_ADMIN.PROXY_CERT_HELP
 | 
			
		||||
 | 
			
		||||
            http.verify_peer:
 | 
			
		||||
              type: toggle
 | 
			
		||||
              label: PLUGIN_ADMIN.SSL_VERIFY_PEER
 | 
			
		||||
              highlight: 1
 | 
			
		||||
              help: PLUGIN_ADMIN.SSL_VERIFY_PEER_HELP
 | 
			
		||||
              options:
 | 
			
		||||
                1: PLUGIN_ADMIN.YES
 | 
			
		||||
                0: PLUGIN_ADMIN.NO
 | 
			
		||||
              validate:
 | 
			
		||||
                type: bool
 | 
			
		||||
 | 
			
		||||
            http.verify_host:
 | 
			
		||||
              type: toggle
 | 
			
		||||
              label: PLUGIN_ADMIN.SSL_VERIFY_HOST
 | 
			
		||||
              highlight: 1
 | 
			
		||||
              help: PLUGIN_ADMIN.SSL_VERIFY_HOST_HELP
 | 
			
		||||
              options:
 | 
			
		||||
                1: PLUGIN_ADMIN.YES
 | 
			
		||||
                0: PLUGIN_ADMIN.NO
 | 
			
		||||
              validate:
 | 
			
		||||
                type: bool
 | 
			
		||||
 | 
			
		||||
            http.concurrent_connections:
 | 
			
		||||
              type: number
 | 
			
		||||
              size: x-small
 | 
			
		||||
              label: PLUGIN_ADMIN.HTTP_CONNECTIONS
 | 
			
		||||
              help: PLUGIN_ADMIN.HTTP_CONNECTIONS_HELP
 | 
			
		||||
              validate:
 | 
			
		||||
                min: 1
 | 
			
		||||
                max: 20
 | 
			
		||||
 | 
			
		||||
            misc_section:
 | 
			
		||||
              type: section
 | 
			
		||||
              title: PLUGIN_ADMIN.MISC_SECTION
 | 
			
		||||
 | 
			
		||||
            reverse_proxy_setup:
 | 
			
		||||
              type: toggle
 | 
			
		||||
              label: PLUGIN_ADMIN.REVERSE_PROXY
 | 
			
		||||
 
 | 
			
		||||
@@ -96,7 +96,7 @@ cache:
 | 
			
		||||
  purge_at: '0 4 * * *'                          # How often to purge old file cache (using new scheduler)
 | 
			
		||||
  clear_at: '0 3 * * *'                           # How often to clear cache (using new scheduler)
 | 
			
		||||
  clear_job_type: 'standard'                     # Type to clear when processing the scheduled clear job `standard`|`all`
 | 
			
		||||
  clear_images_by_default: false                  # By default grav does not include processed images in cache clear, this can be enabled
 | 
			
		||||
  clear_images_by_default: false                 # By default grav does not include processed images in cache clear, this can be enabled
 | 
			
		||||
  cli_compatibility: false                       # Ensures only non-volatile drivers are used (file, redis, memcache, etc.)
 | 
			
		||||
  lifetime: 604800                               # Lifetime of cached data in seconds (0 = infinite)
 | 
			
		||||
  gzip: false                                    # GZip compress the page output
 | 
			
		||||
@@ -162,6 +162,12 @@ images:
 | 
			
		||||
    retina_scale: 1                              # scale to adjust auto-sizes for better handling of HiDPI resolutions
 | 
			
		||||
  defaults:
 | 
			
		||||
    loading: auto                                # Let browser pick [auto|lazy|eager]
 | 
			
		||||
  watermark:
 | 
			
		||||
    image: 'system://images/watermark.png'       # Path to a watermark image
 | 
			
		||||
    position_y: 'center'                         # top|center|bottom
 | 
			
		||||
    position_x: 'center'                         # left|center|right
 | 
			
		||||
    scale: 33                                    # percentage of watermark scale
 | 
			
		||||
    watermark_all: false                         # automatically watermark all images
 | 
			
		||||
 | 
			
		||||
media:
 | 
			
		||||
  enable_media_timestamp: false                  # Enable media timestamps
 | 
			
		||||
@@ -184,11 +190,17 @@ session:
 | 
			
		||||
 | 
			
		||||
gpm:
 | 
			
		||||
  releases: stable                               # Set to either 'stable' or 'testing'
 | 
			
		||||
  proxy_url:                                     # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
 | 
			
		||||
  method: 'auto'                                 # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
 | 
			
		||||
  verify_peer: true                              # Sometimes on some systems (Windows most commonly) GPM is unable to connect because the SSL certificate cannot be verified. Disabling this setting might help.
 | 
			
		||||
  official_gpm_only: true                        # By default GPM direct-install will only allow URLs via the official GPM proxy to ensure security
 | 
			
		||||
 | 
			
		||||
http:
 | 
			
		||||
  method: auto                                   # Either 'curl', 'fopen' or 'auto'. 'auto' will try fopen first and if not available cURL
 | 
			
		||||
  enable_proxy: true                             # Enable proxy server configuration
 | 
			
		||||
  proxy_url:                                     # Configure a manual proxy URL for GPM (eg 127.0.0.1:3128)
 | 
			
		||||
  proxy_cert_path:                               # Local path to proxy certificate folder containing pem files
 | 
			
		||||
  concurrent_connections: 5                      # Concurrent HTTP connections when multiplexing
 | 
			
		||||
  verify_peer: true                              # Enable/Disable SSL verification of peer certificates
 | 
			
		||||
  verify_host: true                              # Enable/Disable SSL verification of host certificates
 | 
			
		||||
 | 
			
		||||
accounts:
 | 
			
		||||
  type: regular                                  # EXPERIMENTAL: Account type: regular or flex
 | 
			
		||||
  storage: file                                  # EXPERIMENTAL: Flex storage type: file or folder
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@
 | 
			
		||||
 | 
			
		||||
// Some standard defines
 | 
			
		||||
define('GRAV', true);
 | 
			
		||||
define('GRAV_VERSION', '1.7.21');
 | 
			
		||||
define('GRAV_VERSION', '1.7.25');
 | 
			
		||||
define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
 | 
			
		||||
define('GRAV_TESTING', false);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								system/images/watermark.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								system/images/watermark.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 94 KiB  | 
@@ -51,6 +51,7 @@ GRAV:
 | 
			
		||||
    VALIDATION_FAIL: '<b>فشل التحقق من صحة:</b>'
 | 
			
		||||
    INVALID_INPUT: 'إدخال غير صحيح في'
 | 
			
		||||
    MISSING_REQUIRED_FIELD: 'حقل مطلوب مفقود:'
 | 
			
		||||
    XSS_ISSUES: "مشاكل XSS محتملة تم اكتشافها في حقل '%s' '"
 | 
			
		||||
  MONTHS_OF_THE_YEAR:
 | 
			
		||||
    - 'كانون الثاني'
 | 
			
		||||
    - 'شباط'
 | 
			
		||||
@@ -72,6 +73,8 @@ GRAV:
 | 
			
		||||
    - 'الجمعة'
 | 
			
		||||
    - 'السبت'
 | 
			
		||||
    - 'الأحد'
 | 
			
		||||
  YES: "نعم"
 | 
			
		||||
  NO: "لا"
 | 
			
		||||
  CRON:
 | 
			
		||||
    EVERY: كل
 | 
			
		||||
    EVERY_HOUR: كل ساعة
 | 
			
		||||
@@ -80,3 +83,11 @@ GRAV:
 | 
			
		||||
    EVERY_DAY_OF_MONTH: كل يوم في الشهر
 | 
			
		||||
    EVERY_MONTH: ' كل شهر'
 | 
			
		||||
    TEXT_PERIOD: كل <b />
 | 
			
		||||
    TEXT_MINS: ' في <b /> دقيقة(دقائق) بعد الساعة'
 | 
			
		||||
    TEXT_TIME: ' في <b />:<b />'
 | 
			
		||||
    TEXT_DOW: ' في <b />'
 | 
			
		||||
    TEXT_MONTH: ' من <b />'
 | 
			
		||||
    TEXT_DOM: ' في <b />'
 | 
			
		||||
    ERROR1: الوسم %s غير مدعوم!
 | 
			
		||||
    ERROR2: عدد عناصر غير صالح.
 | 
			
		||||
    ERROR4: تعبير غير معروف
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ GRAV:
 | 
			
		||||
    BAD_DATE: Data invàlida
 | 
			
		||||
    AGO: abans
 | 
			
		||||
    FROM_NOW: des d'ara
 | 
			
		||||
    JUST_NOW: Ara mateix
 | 
			
		||||
    SECOND: segon
 | 
			
		||||
    MINUTE: minut
 | 
			
		||||
    HOUR: hora
 | 
			
		||||
@@ -48,6 +49,7 @@ GRAV:
 | 
			
		||||
    VALIDATION_FAIL: '<b>Ha fallat la validació:</b>'
 | 
			
		||||
    INVALID_INPUT: 'Entrada no vàlida a'
 | 
			
		||||
    MISSING_REQUIRED_FIELD: 'Falta camp obligatori:'
 | 
			
		||||
    XSS_ISSUES: "Detectats potencials problemes XSS al camp '%s'"
 | 
			
		||||
  MONTHS_OF_THE_YEAR:
 | 
			
		||||
    - 'Gener'
 | 
			
		||||
    - 'Febrer'
 | 
			
		||||
@@ -69,3 +71,17 @@ GRAV:
 | 
			
		||||
    - 'Divendres'
 | 
			
		||||
    - 'Dissabte'
 | 
			
		||||
    - 'Diumenge'
 | 
			
		||||
  YES: "Sí"
 | 
			
		||||
  NO: "No"
 | 
			
		||||
  CRON:
 | 
			
		||||
    EVERY: cada
 | 
			
		||||
    EVERY_HOUR: cada hora
 | 
			
		||||
    EVERY_MINUTE: cada minut
 | 
			
		||||
    EVERY_DAY_OF_WEEK: cada dia de la setmana
 | 
			
		||||
    EVERY_DAY_OF_MONTH: cada dia del mes
 | 
			
		||||
    EVERY_MONTH: cada mes
 | 
			
		||||
    TEXT_PERIOD: Cada <b />
 | 
			
		||||
    ERROR1: L'etiqueta %s no està suportada!
 | 
			
		||||
    ERROR2: Nombre d'elements incorrecte
 | 
			
		||||
    ERROR3: El jquery_element s'ha d'establir a la configuració de jqCron
 | 
			
		||||
    ERROR4: Expressió no reconeguda
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,7 @@ GRAV:
 | 
			
		||||
    '/(quiz)zes$/i': '\1'
 | 
			
		||||
    '/(alias|status)es$/i': '\1'
 | 
			
		||||
    '/([octop|vir])i$/i': '\1us'
 | 
			
		||||
    '/(n)ews$/i': '\1ouvelles'
 | 
			
		||||
  INFLECTOR_UNCOUNTABLE:
 | 
			
		||||
    - 'équipement'
 | 
			
		||||
    - 'information'
 | 
			
		||||
@@ -58,10 +59,10 @@ GRAV:
 | 
			
		||||
    MONTH: mois
 | 
			
		||||
    YEAR: année
 | 
			
		||||
    DECADE: décennie
 | 
			
		||||
    SEC: s
 | 
			
		||||
    MIN: m
 | 
			
		||||
    HR: h
 | 
			
		||||
    WK: sem
 | 
			
		||||
    SEC: sec.
 | 
			
		||||
    MIN: min.
 | 
			
		||||
    HR: hr.
 | 
			
		||||
    WK: sem.
 | 
			
		||||
    MO: m
 | 
			
		||||
    YR: an
 | 
			
		||||
    DEC: déc
 | 
			
		||||
@@ -84,6 +85,7 @@ GRAV:
 | 
			
		||||
    VALIDATION_FAIL: '<b>La validation a échoué :</b>'
 | 
			
		||||
    INVALID_INPUT: 'Saisie non valide'
 | 
			
		||||
    MISSING_REQUIRED_FIELD: 'Champ obligatoire manquant :'
 | 
			
		||||
    XSS_ISSUES: "Erreurs XSS probablement détectées dans le champ '%s'"
 | 
			
		||||
  MONTHS_OF_THE_YEAR:
 | 
			
		||||
    - 'janvier'
 | 
			
		||||
    - 'février'
 | 
			
		||||
@@ -105,6 +107,8 @@ GRAV:
 | 
			
		||||
    - 'vendredi'
 | 
			
		||||
    - 'samedi'
 | 
			
		||||
    - 'dimanche'
 | 
			
		||||
  YES: "Oui"
 | 
			
		||||
  NO: "Non"
 | 
			
		||||
  CRON:
 | 
			
		||||
    EVERY: chaque
 | 
			
		||||
    EVERY_HOUR: toutes les heures
 | 
			
		||||
@@ -118,7 +122,7 @@ GRAV:
 | 
			
		||||
    TEXT_DOW: ' sur <b/>'
 | 
			
		||||
    TEXT_MONTH: ' de <b />'
 | 
			
		||||
    TEXT_DOM: ' sur <b/>'
 | 
			
		||||
    ERROR1: La balise %s n'est pas supportée!
 | 
			
		||||
    ERROR1: La balise %s n'est pas prise en charge !
 | 
			
		||||
    ERROR2: Nombre invalide d'éléments
 | 
			
		||||
    ERROR3: L'élément jquery_element doit être défini dans les paramètres jqCron
 | 
			
		||||
    ERROR4: Expression non reconnue
 | 
			
		||||
 
 | 
			
		||||
@@ -104,6 +104,7 @@ GRAV:
 | 
			
		||||
    VALIDATION_FAIL: '<b>Fallou a validación:</b>'
 | 
			
		||||
    INVALID_INPUT: 'Entrada incorrecta en'
 | 
			
		||||
    MISSING_REQUIRED_FIELD: 'Falta un campo requirido:'
 | 
			
		||||
    XSS_ISSUES: "Detectáronse posibles problemas XSS no campo '% s'"
 | 
			
		||||
  MONTHS_OF_THE_YEAR:
 | 
			
		||||
    - 'xaneiro'
 | 
			
		||||
    - 'febreiro'
 | 
			
		||||
@@ -125,6 +126,8 @@ GRAV:
 | 
			
		||||
    - 'venres'
 | 
			
		||||
    - 'sábado'
 | 
			
		||||
    - 'domingo'
 | 
			
		||||
  YES: "Si"
 | 
			
		||||
  NO: "Non"
 | 
			
		||||
  CRON:
 | 
			
		||||
    EVERY: cada
 | 
			
		||||
    EVERY_HOUR: Cada hora
 | 
			
		||||
 
 | 
			
		||||
@@ -3,26 +3,72 @@ GRAV:
 | 
			
		||||
  FRONTMATTER_ERROR_PAGE: "---\ntitle: %1$s\n---\n\n# Error: Frontmatter tidak valid\n\nLokasi: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
 | 
			
		||||
  INFLECTOR_PLURALS:
 | 
			
		||||
    '/(quiz)$/i': '\1zes'
 | 
			
		||||
    '/^(ox)$/i': '\1en'
 | 
			
		||||
    '/([m|l])ouse$/i': '\1ice'
 | 
			
		||||
    '/(matr|vert|ind)ix|ex$/i': '\1ices'
 | 
			
		||||
    '/(x|ch|ss|sh)$/i': '\1es'
 | 
			
		||||
    '/([^aeiouy]|qu)ies$/i': '\1y'
 | 
			
		||||
    '/([^aeiouy]|qu)y$/i': '\1ies'
 | 
			
		||||
    '/(hive)$/i': '\1s'
 | 
			
		||||
    '/(?:([^f])fe|([lr])f)$/i': '\1\2ves'
 | 
			
		||||
    '/sis$/i': 'ses'
 | 
			
		||||
    '/([ti])um$/i': '\1a'
 | 
			
		||||
    '/(buffal|tomat)o$/i': '\1oes'
 | 
			
		||||
    '/(bu)s$/i': '\1ses'
 | 
			
		||||
    '/(alias|status)/i': '\1es'
 | 
			
		||||
    '/(octop|vir)us$/i': '\1i'
 | 
			
		||||
    '/(ax|test)is$/i': '\1es'
 | 
			
		||||
    '/s$/i': 's'
 | 
			
		||||
    '/$/': 's'
 | 
			
		||||
  INFLECTOR_SINGULAR:
 | 
			
		||||
    '/(quiz)zes$/i': '\1'
 | 
			
		||||
    '/(matr)ices$/i': '\1ix'
 | 
			
		||||
    '/(vert|ind)ices$/i': '\1ex'
 | 
			
		||||
    '/^(ox)en/i': '\1'
 | 
			
		||||
    '/(alias|status)es$/i': '\1'
 | 
			
		||||
    '/([octop|vir])i$/i': '\1us'
 | 
			
		||||
    '/(cris|ax|test)es$/i': '\1is'
 | 
			
		||||
    '/(shoe)s$/i': '\1'
 | 
			
		||||
    '/(o)es$/i': '\1'
 | 
			
		||||
    '/(bus)es$/i': '\1'
 | 
			
		||||
    '/([m|l])ice$/i': '\1ouse'
 | 
			
		||||
    '/(x|ch|ss|sh)es$/i': '\1'
 | 
			
		||||
    '/(m)ovies$/i': '\1ovie'
 | 
			
		||||
    '/(s)eries$/i': '\1eries'
 | 
			
		||||
    '/([^aeiouy]|qu)ies$/i': '\1y'
 | 
			
		||||
    '/([lr])ves$/i': '\1f'
 | 
			
		||||
    '/(tive)s$/i': '\1'
 | 
			
		||||
    '/(hive)s$/i': '\1'
 | 
			
		||||
    '/([^f])ves$/i': '\1fe'
 | 
			
		||||
    '/(^analy)ses$/i': '\1sis'
 | 
			
		||||
    '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2sis'
 | 
			
		||||
    '/([ti])a$/i': '\1um'
 | 
			
		||||
    '/(n)ews$/i': '\1ews'
 | 
			
		||||
  INFLECTOR_UNCOUNTABLE:
 | 
			
		||||
    - 'peralatan'
 | 
			
		||||
    - 'informasi'
 | 
			
		||||
    - 'nasi'
 | 
			
		||||
    - 'uang'
 | 
			
		||||
    - 'spesies'
 | 
			
		||||
    - 'rangkaian'
 | 
			
		||||
    - 'ikan'
 | 
			
		||||
    - 'domba'
 | 
			
		||||
    - 'Peralatan'
 | 
			
		||||
    - 'Informasi '
 | 
			
		||||
    - 'Nasi'
 | 
			
		||||
    - 'Uang'
 | 
			
		||||
    - 'Jenis'
 | 
			
		||||
    - 'Seri'
 | 
			
		||||
    - 'Ikan'
 | 
			
		||||
    - 'Domba'
 | 
			
		||||
  INFLECTOR_IRREGULAR:
 | 
			
		||||
    'person': 'orang-orang'
 | 
			
		||||
    'man': 'laki-laki'
 | 
			
		||||
    'child': 'anak-anak'
 | 
			
		||||
    'sex': 'jenis kelamin'
 | 
			
		||||
    'person': 'Orang-orang'
 | 
			
		||||
    'man': 'Pria'
 | 
			
		||||
    'child': 'Balita'
 | 
			
		||||
    'sex': 'Jenis Kelamin'
 | 
			
		||||
    'move': 'pindahkan'
 | 
			
		||||
  INFLECTOR_ORDINALS:
 | 
			
		||||
    'default': 'ke'
 | 
			
		||||
    'first': 'pertama'
 | 
			
		||||
    'second': 'nd'
 | 
			
		||||
    'third': 'rd'
 | 
			
		||||
  NICETIME:
 | 
			
		||||
    NO_DATE_PROVIDED: Tanggal tidak tersedia
 | 
			
		||||
    NO_DATE_PROVIDED: Tidak ada tanggal yang disediakan
 | 
			
		||||
    BAD_DATE: Format tanggal salah
 | 
			
		||||
    AGO: yang lalu
 | 
			
		||||
    FROM_NOW: dari saat ini
 | 
			
		||||
    FROM_NOW: dari sekarang
 | 
			
		||||
    JUST_NOW: baru saja
 | 
			
		||||
    SECOND: detik
 | 
			
		||||
    MINUTE: menit
 | 
			
		||||
@@ -32,12 +78,12 @@ GRAV:
 | 
			
		||||
    MONTH: bulan
 | 
			
		||||
    YEAR: tahun
 | 
			
		||||
    DECADE: dekade
 | 
			
		||||
    SEC: dtk
 | 
			
		||||
    MIN: mnt
 | 
			
		||||
    HR: j
 | 
			
		||||
    WK: mng
 | 
			
		||||
    MO: bln
 | 
			
		||||
    YR: thn
 | 
			
		||||
    SEC: detik
 | 
			
		||||
    MIN: menit
 | 
			
		||||
    HR: ' jam'
 | 
			
		||||
    WK: minggu
 | 
			
		||||
    MO: bulan
 | 
			
		||||
    YR: tahun
 | 
			
		||||
    DEC: desimal
 | 
			
		||||
    SECOND_PLURAL: detik
 | 
			
		||||
    MINUTE_PLURAL: menit
 | 
			
		||||
@@ -47,17 +93,18 @@ GRAV:
 | 
			
		||||
    MONTH_PLURAL: bulan
 | 
			
		||||
    YEAR_PLURAL: tahun
 | 
			
		||||
    DECADE_PLURAL: dekade
 | 
			
		||||
    SEC_PLURAL: dtk
 | 
			
		||||
    MIN_PLURAL: mnt
 | 
			
		||||
    HR_PLURAL: j
 | 
			
		||||
    WK_PLURAL: mgg
 | 
			
		||||
    MO_PLURAL: bln
 | 
			
		||||
    YR_PLURAL: thn
 | 
			
		||||
    SEC_PLURAL: detik
 | 
			
		||||
    MIN_PLURAL: menit
 | 
			
		||||
    HR_PLURAL: jam
 | 
			
		||||
    WK_PLURAL: minggu
 | 
			
		||||
    MO_PLURAL: bulan
 | 
			
		||||
    YR_PLURAL: tahun
 | 
			
		||||
    DEC_PLURAL: dekade
 | 
			
		||||
  FORM:
 | 
			
		||||
    VALIDATION_FAIL: '<b>Validasi gagal:</b>'
 | 
			
		||||
    INVALID_INPUT: 'Input tidak valid di'
 | 
			
		||||
    MISSING_REQUIRED_FIELD: 'Data yang diperlukan belum terisi:'
 | 
			
		||||
    XSS_ISSUES: "Isu berpotensial XSS terdeteksi dalam baris %s"
 | 
			
		||||
  MONTHS_OF_THE_YEAR:
 | 
			
		||||
    - 'Januari'
 | 
			
		||||
    - 'Februari'
 | 
			
		||||
@@ -76,22 +123,25 @@ GRAV:
 | 
			
		||||
    - 'Selasa'
 | 
			
		||||
    - 'Rabu'
 | 
			
		||||
    - 'Kamis'
 | 
			
		||||
    - 'Jumat'
 | 
			
		||||
    - 'Jum''at'
 | 
			
		||||
    - 'Sabtu'
 | 
			
		||||
    - 'Minggu'
 | 
			
		||||
  YES: "Ya"
 | 
			
		||||
  NO: "Tidak"
 | 
			
		||||
  CRON:
 | 
			
		||||
    EVERY: Setiap
 | 
			
		||||
    EVERY_HOUR: Setiap jam
 | 
			
		||||
    EVERY_MINUTE: Setiap menit
 | 
			
		||||
    EVERY_DAY_OF_WEEK: Setiap hari selama seminggu
 | 
			
		||||
    EVERY_DAY_OF_MONTH: pada tanggal setiap bulannya
 | 
			
		||||
    EVERY_DAY_OF_MONTH: Setiap hari dalam sebulan
 | 
			
		||||
    EVERY_MONTH: setiap bulan
 | 
			
		||||
    TEXT_PERIOD: Setiap <b />
 | 
			
		||||
    TEXT_MINS: 'dalam <b />  menit setelah jam yang lalu'
 | 
			
		||||
    TEXT_TIME: ' pada <b />:<b />'
 | 
			
		||||
    TEXT_DOW: ' pada <b />'
 | 
			
		||||
    TEXT_MONTH: ' pada <b />'
 | 
			
		||||
    TEXT_DOM: ' pada <b />'
 | 
			
		||||
    ERROR1: Tag %s tidak didukung!
 | 
			
		||||
    ERROR2: Jumlah elemen tidak valid
 | 
			
		||||
    ERROR3: jquery_element harus ditetapkan ke pengaturan jqCron
 | 
			
		||||
    ERROR4: Ekspresi tidak dikenali
 | 
			
		||||
    ERROR2: Jumlah elemen yang buruk
 | 
			
		||||
    ERROR3: jquery_element harus diatur ke dalam pengaturan jqCron
 | 
			
		||||
    ERROR4: Ekspresi tidak dikenal
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										147
									
								
								system/languages/mn.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								system/languages/mn.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
---
 | 
			
		||||
GRAV:
 | 
			
		||||
  FRONTMATTER_ERROR_PAGE: "---\nГарчиг: %1$s\n---\n\n# Алдаа: Буруу Формат\n\nЗам: `%2$s`\n\n**%3$s**\n\n```\n%4$s\n```"
 | 
			
		||||
  INFLECTOR_PLURALS:
 | 
			
		||||
    '/(quiz)$/i': '\1зүүд'
 | 
			
		||||
    '/^(ox)$/i': '\1ууд'
 | 
			
		||||
    '/([m|l])ouse$/i': '\1ууд'
 | 
			
		||||
    '/(matr|vert|ind)ix|ex$/i': '\1иксүүд'
 | 
			
		||||
    '/(x|ch|ss|sh)$/i': '\1үүд'
 | 
			
		||||
    '/([^aeiouy]|qu)ies$/i': '\1үүд'
 | 
			
		||||
    '/([^aeiouy]|qu)y$/i': '\1үүд'
 | 
			
		||||
    '/(hive)$/i': '\1үүд'
 | 
			
		||||
    '/(?:([^f])fe|([lr])f)$/i': '\1\2үүд'
 | 
			
		||||
    '/sis$/i': 'үүд'
 | 
			
		||||
    '/([ti])um$/i': '\1үүд'
 | 
			
		||||
    '/(buffal|tomat)o$/i': '\1үүд'
 | 
			
		||||
    '/(bu)s$/i': '\1үүд'
 | 
			
		||||
    '/(alias|status)/i': '\1үүд'
 | 
			
		||||
    '/(octop|vir)us$/i': '\1үүд'
 | 
			
		||||
    '/(ax|test)is$/i': '\1үүд'
 | 
			
		||||
    '/s$/i': 'үүд'
 | 
			
		||||
    '/$/': 'үүд'
 | 
			
		||||
  INFLECTOR_SINGULAR:
 | 
			
		||||
    '/(quiz)zes$/i': '\1'
 | 
			
		||||
    '/(matr)ices$/i': '\1икс'
 | 
			
		||||
    '/(vert|ind)ices$/i': '\1икс'
 | 
			
		||||
    '/^(ox)en/i': '\1'
 | 
			
		||||
    '/(alias|status)es$/i': '\1'
 | 
			
		||||
    '/([octop|vir])i$/i': '\1'
 | 
			
		||||
    '/(cris|ax|test)es$/i': '\1'
 | 
			
		||||
    '/(shoe)s$/i': '\1'
 | 
			
		||||
    '/(o)es$/i': '\1'
 | 
			
		||||
    '/(bus)es$/i': '\1'
 | 
			
		||||
    '/([m|l])ice$/i': '\1'
 | 
			
		||||
    '/(x|ch|ss|sh)es$/i': '\1'
 | 
			
		||||
    '/(m)ovies$/i': '\1'
 | 
			
		||||
    '/(s)eries$/i': '\1'
 | 
			
		||||
    '/([^aeiouy]|qu)ies$/i': '\1үүд'
 | 
			
		||||
    '/([lr])ves$/i': '\1'
 | 
			
		||||
    '/(tive)s$/i': '\1'
 | 
			
		||||
    '/(hive)s$/i': '\1'
 | 
			
		||||
    '/([^f])ves$/i': '\1'
 | 
			
		||||
    '/(^analy)ses$/i': '\1'
 | 
			
		||||
    '/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i': '\1\2үүд'
 | 
			
		||||
    '/([ti])a$/i': '\1'
 | 
			
		||||
    '/(n)ews$/i': '\1'
 | 
			
		||||
  INFLECTOR_UNCOUNTABLE:
 | 
			
		||||
    - 'тоног төхөөрөмж'
 | 
			
		||||
    - 'Мэдээлэл'
 | 
			
		||||
    - 'будаа'
 | 
			
		||||
    - 'мөнгө'
 | 
			
		||||
    - 'төрөл зүйл'
 | 
			
		||||
    - 'цуврал'
 | 
			
		||||
    - 'загас'
 | 
			
		||||
    - 'хонь'
 | 
			
		||||
  INFLECTOR_IRREGULAR:
 | 
			
		||||
    'person': 'хүмүүс'
 | 
			
		||||
    'man': 'эрчүүд'
 | 
			
		||||
    'child': 'хүүхэд'
 | 
			
		||||
    'sex': 'хүйс'
 | 
			
		||||
    'move': 'хөдөлгөөн'
 | 
			
		||||
  INFLECTOR_ORDINALS:
 | 
			
		||||
    'default': 'th'
 | 
			
		||||
    'first': 'st'
 | 
			
		||||
    'second': 'nd'
 | 
			
		||||
    'third': 'rd'
 | 
			
		||||
  NICETIME:
 | 
			
		||||
    NO_DATE_PROVIDED: Огноо алга
 | 
			
		||||
    BAD_DATE: Буруу огноо
 | 
			
		||||
    AGO: өмнө 
 | 
			
		||||
    FROM_NOW: одооноос
 | 
			
		||||
    JUST_NOW: дөнгөж сая
 | 
			
		||||
    SECOND: секунд
 | 
			
		||||
    MINUTE: минут
 | 
			
		||||
    HOUR: цаг
 | 
			
		||||
    DAY: өдөр
 | 
			
		||||
    WEEK: долоо хоног
 | 
			
		||||
    MONTH: сар
 | 
			
		||||
    YEAR: он
 | 
			
		||||
    DECADE: арван жил
 | 
			
		||||
    SEC: сек
 | 
			
		||||
    MIN: мин
 | 
			
		||||
    HR: цаг
 | 
			
		||||
    WK: д.х.
 | 
			
		||||
    MO: сар
 | 
			
		||||
    YR: он
 | 
			
		||||
    DEC: арван жил
 | 
			
		||||
    SECOND_PLURAL: секунд
 | 
			
		||||
    MINUTE_PLURAL: минут
 | 
			
		||||
    HOUR_PLURAL: цаг
 | 
			
		||||
    DAY_PLURAL: өдрүүд
 | 
			
		||||
    WEEK_PLURAL: долоо хоногууд
 | 
			
		||||
    MONTH_PLURAL: сарууд
 | 
			
		||||
    YEAR_PLURAL: онууд
 | 
			
		||||
    DECADE_PLURAL: арван жилүүд
 | 
			
		||||
    SEC_PLURAL: сек.-үүд
 | 
			
		||||
    MIN_PLURAL: мин.-ууд
 | 
			
		||||
    HR_PLURAL: цагууд
 | 
			
		||||
    WK_PLURAL: д.х.-ууд
 | 
			
		||||
    MO_PLURAL: сарууд
 | 
			
		||||
    YR_PLURAL: жилүүд
 | 
			
		||||
    DEC_PLURAL: арван жилүүд
 | 
			
		||||
  FORM:
 | 
			
		||||
    VALIDATION_FAIL: '<b>Баталгаажуулалт амжилтгүй боллоо:</b>'
 | 
			
		||||
    INVALID_INPUT: 'Буруу өгөгдөл дараахид'
 | 
			
		||||
    MISSING_REQUIRED_FIELD: 'Шаардлагатай талбар дутуу байна:'
 | 
			
		||||
    XSS_ISSUES: "'%s' талбарт XSS -ийн болзошгүй асуудлууд илэрсэн"
 | 
			
		||||
  MONTHS_OF_THE_YEAR:
 | 
			
		||||
    - '1-р сар'
 | 
			
		||||
    - '2-р сар'
 | 
			
		||||
    - '3-р сар'
 | 
			
		||||
    - '4-р сар'
 | 
			
		||||
    - '5 сар'
 | 
			
		||||
    - '6 сар'
 | 
			
		||||
    - '7 сар'
 | 
			
		||||
    - '8 сар'
 | 
			
		||||
    - '9 сар'
 | 
			
		||||
    - '10 сар'
 | 
			
		||||
    - '11 сар'
 | 
			
		||||
    - '12 сар'
 | 
			
		||||
  DAYS_OF_THE_WEEK:
 | 
			
		||||
    - 'Даваа гараг'
 | 
			
		||||
    - 'Мягмар гараг'
 | 
			
		||||
    - 'Лхагва гараг'
 | 
			
		||||
    - 'Пүрэв гараг'
 | 
			
		||||
    - 'Баасан гараг'
 | 
			
		||||
    - 'Бямба гараг'
 | 
			
		||||
    - 'Ням гараг'
 | 
			
		||||
  YES: "Тийм"
 | 
			
		||||
  NO: "Үгүй"
 | 
			
		||||
  CRON:
 | 
			
		||||
    EVERY: бүрийн
 | 
			
		||||
    EVERY_HOUR: цаг бүрийн
 | 
			
		||||
    EVERY_MINUTE: минут бүрийн
 | 
			
		||||
    EVERY_DAY_OF_WEEK: долоо хоногийн өдөр болгонд
 | 
			
		||||
    EVERY_DAY_OF_MONTH: сарын өдөр болгонд
 | 
			
		||||
    EVERY_MONTH: сар болгон
 | 
			
		||||
    TEXT_PERIOD: Бүрийн  <b />
 | 
			
		||||
    TEXT_MINS: '  <b /> энэ сүүлийн цагийн минутад'
 | 
			
		||||
    TEXT_TIME: '  <b />:<b /> -д'
 | 
			
		||||
    TEXT_DOW: '  <b /> -д'
 | 
			
		||||
    TEXT_MONTH: '  <b /> -ын'
 | 
			
		||||
    TEXT_DOM: '  <b /> -т'
 | 
			
		||||
    ERROR1: '%s -н утга нь дэмжигддэггүй!'
 | 
			
		||||
    ERROR2: Элементүүдийн тоо хэмжээ буруу
 | 
			
		||||
    ERROR3: jquery_element нь jqCron тохиргоонд хийгдсэн байх ёстой
 | 
			
		||||
    ERROR4: Танигдаагүй илэрхийлэл
 | 
			
		||||
@@ -104,6 +104,7 @@ GRAV:
 | 
			
		||||
    VALIDATION_FAIL: '<b>Falha na validação:</b>'
 | 
			
		||||
    INVALID_INPUT: 'Dados inseridos são inválidos em'
 | 
			
		||||
    MISSING_REQUIRED_FIELD: 'Campo obrigatório em falta:'
 | 
			
		||||
    XSS_ISSUES: "Potenciais problemas de XSS detectados no campo '%s'"
 | 
			
		||||
  MONTHS_OF_THE_YEAR:
 | 
			
		||||
    - 'Janeiro'
 | 
			
		||||
    - 'Fevereiro'
 | 
			
		||||
@@ -125,6 +126,8 @@ GRAV:
 | 
			
		||||
    - 'Sexta-feira'
 | 
			
		||||
    - 'Sábado'
 | 
			
		||||
    - 'Domingo'
 | 
			
		||||
  YES: "Sim"
 | 
			
		||||
  NO: "Não"
 | 
			
		||||
  CRON:
 | 
			
		||||
    EVERY: cada
 | 
			
		||||
    EVERY_HOUR: cada hora
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								system/languages/si.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								system/languages/si.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
---
 | 
			
		||||
GRAV:
 | 
			
		||||
  INFLECTOR_SINGULAR:
 | 
			
		||||
    '/(quiz)zes$/i': '\1'
 | 
			
		||||
    '/^(ox)en/i': '\1'
 | 
			
		||||
    '/(alias|status)es$/i': '\1'
 | 
			
		||||
    '/(o)es$/i': '\1'
 | 
			
		||||
    '/(bus)es$/i': '\1'
 | 
			
		||||
    '/(x|ch|ss|sh)es$/i': '\1'
 | 
			
		||||
@@ -82,6 +82,8 @@ GRAV:
 | 
			
		||||
    - 'Cuma'
 | 
			
		||||
    - 'Cumartesi'
 | 
			
		||||
    - 'Pazar'
 | 
			
		||||
  YES: "Evet"
 | 
			
		||||
  NO: "Hayır"
 | 
			
		||||
  CRON:
 | 
			
		||||
    EVERY: her
 | 
			
		||||
    EVERY_HOUR: saatte bir
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,9 @@ GRAV:
 | 
			
		||||
    YR_PLURAL: 年
 | 
			
		||||
    DEC_PLURAL: 十年
 | 
			
		||||
  FORM:
 | 
			
		||||
    MISSING_REQUIRED_FIELD: 遺漏必填欄位:
 | 
			
		||||
    VALIDATION_FAIL: '<b>確驗證失敗:</b>'
 | 
			
		||||
    INVALID_INPUT: '無效輸入:'
 | 
			
		||||
    MISSING_REQUIRED_FIELD: '遺漏必填欄位:'
 | 
			
		||||
  MONTHS_OF_THE_YEAR:
 | 
			
		||||
    - '一月'
 | 
			
		||||
    - '二月'
 | 
			
		||||
@@ -60,3 +62,16 @@ GRAV:
 | 
			
		||||
    - '星期五'
 | 
			
		||||
    - '星期六'
 | 
			
		||||
    - '星期日'
 | 
			
		||||
  CRON:
 | 
			
		||||
    EVERY: 每
 | 
			
		||||
    EVERY_HOUR: 每小時
 | 
			
		||||
    EVERY_MINUTE: 每分鐘
 | 
			
		||||
    EVERY_DAY_OF_WEEK: 每一天
 | 
			
		||||
    EVERY_DAY_OF_MONTH: 每一天
 | 
			
		||||
    EVERY_MONTH: 每個月
 | 
			
		||||
    TEXT_PERIOD: 每 <b />
 | 
			
		||||
    TEXT_MINS: ' 的 <b /> 分'
 | 
			
		||||
    TEXT_TIME: ' <b />:<b />'
 | 
			
		||||
    TEXT_DOW: ' 的 <b />'
 | 
			
		||||
    TEXT_MONTH: ' 的 <b />'
 | 
			
		||||
    TEXT_DOM: ' 的 <b />'
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,25 @@ if (PHP_SAPI !== 'cli-server') {
 | 
			
		||||
 | 
			
		||||
$_SERVER['PHP_CLI_ROUTER'] = true;
 | 
			
		||||
 | 
			
		||||
if (is_file($_SERVER['DOCUMENT_ROOT'] . DIRECTORY_SEPARATOR . $_SERVER['SCRIPT_NAME'])) {
 | 
			
		||||
    return false;
 | 
			
		||||
$root = $_SERVER['DOCUMENT_ROOT'];
 | 
			
		||||
$path = $_SERVER['SCRIPT_NAME'];
 | 
			
		||||
if ($path !== '/index.php' && is_file($root . $path)) {
 | 
			
		||||
    if (!(
 | 
			
		||||
        // Block all direct access to files and folders beginning with a dot
 | 
			
		||||
        strpos($path, '/.') !== false
 | 
			
		||||
        // Block all direct access for these folders
 | 
			
		||||
        || preg_match('`^/(\.git|cache|bin|logs|backup|webserver-configs|tests)/`ui', $path)
 | 
			
		||||
        // Block access to specific file types for these system folders
 | 
			
		||||
        || preg_match('`^/(system|vendor)/(.*)\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$`ui', $path)
 | 
			
		||||
        // Block access to specific file types for these user folders
 | 
			
		||||
        || preg_match('`^/(user)/(.*)\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$`ui', $path)
 | 
			
		||||
        // Block all direct access to .md files
 | 
			
		||||
        || preg_match('`\.md$`ui', $path)
 | 
			
		||||
        // Block access to specific files in the root folder
 | 
			
		||||
        || preg_match('`^/(LICENSE\.txt|composer\.lock|composer\.json|\.htaccess)$`ui', $path)
 | 
			
		||||
    )) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$grav_index = 'index.php';
 | 
			
		||||
 
 | 
			
		||||
@@ -92,6 +92,10 @@ abstract class BaseAsset extends PropertyObject
 | 
			
		||||
     */
 | 
			
		||||
    public function init($asset, $options)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$asset) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $config = Grav::instance()['config'];
 | 
			
		||||
        $uri = Grav::instance()['uri'];
 | 
			
		||||
 | 
			
		||||
@@ -259,6 +263,6 @@ abstract class BaseAsset extends PropertyObject
 | 
			
		||||
     */
 | 
			
		||||
    protected function cssRewrite($file, $dir, $local)
 | 
			
		||||
    {
 | 
			
		||||
        return;
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -254,7 +254,7 @@ class Pipeline extends PropertyObject
 | 
			
		||||
                $old_url = ltrim($old_url, '/');
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $new_url = ($local ? $this->base_url: '') . $old_url;
 | 
			
		||||
            $new_url = ($local ? $this->base_url : '') . $old_url;
 | 
			
		||||
 | 
			
		||||
            return str_replace($matches[2], $new_url, $matches[0]);
 | 
			
		||||
        }, $file);
 | 
			
		||||
 
 | 
			
		||||
@@ -68,8 +68,6 @@ trait AssetUtilsTrait
 | 
			
		||||
    protected function gatherLinks(array $assets, $css = true)
 | 
			
		||||
    {
 | 
			
		||||
        $buffer = '';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        foreach ($assets as $id => $asset) {
 | 
			
		||||
            $local = true;
 | 
			
		||||
 | 
			
		||||
@@ -135,7 +133,7 @@ trait AssetUtilsTrait
 | 
			
		||||
 | 
			
		||||
        $imports = [];
 | 
			
		||||
 | 
			
		||||
        $file = (string)preg_replace_callback($regex, function ($matches) use (&$imports) {
 | 
			
		||||
        $file = (string)preg_replace_callback($regex, static function ($matches) use (&$imports) {
 | 
			
		||||
            $imports[] = $matches[0];
 | 
			
		||||
 | 
			
		||||
            return '';
 | 
			
		||||
@@ -200,7 +198,7 @@ trait AssetUtilsTrait
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->timestamp) {
 | 
			
		||||
            if (Utils::contains($asset, '?') || $querystring) {
 | 
			
		||||
            if ($querystring || Utils::contains($asset, '?')) {
 | 
			
		||||
                $querystring .=  '&' . $this->timestamp;
 | 
			
		||||
            } else {
 | 
			
		||||
                $querystring .= '?' . $this->timestamp;
 | 
			
		||||
 
 | 
			
		||||
@@ -144,9 +144,8 @@ class Backups
 | 
			
		||||
    public static function getTotalBackupsSize()
 | 
			
		||||
    {
 | 
			
		||||
        $backups = static::getAvailableBackups();
 | 
			
		||||
        $size = array_sum(array_column($backups, 'size'));
 | 
			
		||||
 | 
			
		||||
        return $size ?? 0;
 | 
			
		||||
        return array_sum(array_column($backups, 'size'));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -222,7 +221,7 @@ class Backups
 | 
			
		||||
            $backup_root = rtrim(GRAV_ROOT . $backup_root, '/');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!file_exists($backup_root)) {
 | 
			
		||||
        if (!$backup_root || !file_exists($backup_root)) {
 | 
			
		||||
            throw new RuntimeException("Backup location: {$backup_root} does not exist...");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -141,7 +141,7 @@ class Cache extends Getters
 | 
			
		||||
        $uniqueness = substr(md5($uri->rootUrl(true) . $this->config->key() . GRAV_VERSION), 2, 8);
 | 
			
		||||
 | 
			
		||||
        // Cache key allows us to invalidate all cache on configuration changes.
 | 
			
		||||
        $this->key = ($prefix ? $prefix : 'g') . '-' . $uniqueness;
 | 
			
		||||
        $this->key = ($prefix ?: 'g') . '-' . $uniqueness;
 | 
			
		||||
        $this->cache_dir = $grav['locator']->findResource('cache://doctrine/' . $uniqueness, true, true);
 | 
			
		||||
        $this->driver_setting = $this->config->get('system.cache.driver');
 | 
			
		||||
        $this->driver = $this->getCacheDriver();
 | 
			
		||||
@@ -618,11 +618,7 @@ class Cache extends Getters
 | 
			
		||||
     */
 | 
			
		||||
    public function isVolatileDriver($setting)
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'])) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
        return in_array($setting, ['apc', 'apcu', 'xcache', 'wincache'], true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,7 @@ class Blueprint extends BlueprintForm
 | 
			
		||||
    /** @var string|null */
 | 
			
		||||
    protected $scope;
 | 
			
		||||
 | 
			
		||||
    /** @var BlueprintSchema */
 | 
			
		||||
    /** @var BlueprintSchema|null */
 | 
			
		||||
    protected $blueprintSchema;
 | 
			
		||||
 | 
			
		||||
    /** @var object|null */
 | 
			
		||||
@@ -54,7 +54,7 @@ class Blueprint extends BlueprintForm
 | 
			
		||||
     */
 | 
			
		||||
    public function __clone()
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->blueprintSchema) {
 | 
			
		||||
        if (null !== $this->blueprintSchema) {
 | 
			
		||||
            $this->blueprintSchema = clone $this->blueprintSchema;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -56,6 +56,15 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
 | 
			
		||||
        return $this->types[$name] ?? [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $name
 | 
			
		||||
     * @return array|null
 | 
			
		||||
     */
 | 
			
		||||
    public function getNestedRules(string $name)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getNested($name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validate data against blueprints.
 | 
			
		||||
     *
 | 
			
		||||
@@ -74,7 +83,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!empty($messages)) {
 | 
			
		||||
            throw (new ValidationException())->setMessages($messages);
 | 
			
		||||
            throw (new ValidationException('', 400))->setMessages($messages);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -190,7 +199,7 @@ class BlueprintSchema extends BlueprintSchemaBase implements ExportInterface
 | 
			
		||||
                /** @var Config $config */
 | 
			
		||||
                $config = Grav::instance()['config'];
 | 
			
		||||
                if (!$config->get('system.strict_mode.blueprint_strict_compat', true)) {
 | 
			
		||||
                    throw new RuntimeException(sprintf('%s is not defined in blueprints', $key));
 | 
			
		||||
                    throw new RuntimeException(sprintf('%s is not defined in blueprints', $key), 400);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                user_error(sprintf('Having extra key %s in your data is deprecated with blueprint having \'validation: strict\'', $key), E_USER_DEPRECATED);
 | 
			
		||||
 
 | 
			
		||||
@@ -264,7 +264,7 @@ class Data implements DataInterface, ArrayAccess, \Countable, JsonSerializable,
 | 
			
		||||
     */
 | 
			
		||||
    public function blueprints()
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->blueprints) {
 | 
			
		||||
        if (null === $this->blueprints) {
 | 
			
		||||
            $this->blueprints = new Blueprint();
 | 
			
		||||
        } elseif (is_callable($this->blueprints)) {
 | 
			
		||||
            // Lazy load blueprints.
 | 
			
		||||
 
 | 
			
		||||
@@ -608,7 +608,7 @@ class Validation
 | 
			
		||||
     */
 | 
			
		||||
    public static function typeColor($value, array $params, array $field)
 | 
			
		||||
    {
 | 
			
		||||
        return preg_match('/^\#[0-9a-fA-F]{3}[0-9a-fA-F]{3}?$/u', $value);
 | 
			
		||||
        return (bool)preg_match('/^\#[0-9a-fA-F]{3}[0-9a-fA-F]{3}?$/u', $value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -781,14 +781,22 @@ class Validation
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If creating new values is allowed, no further checks are needed.
 | 
			
		||||
        if (!empty($field['selectize']['create'])) {
 | 
			
		||||
        $validateOptions = $field['validate']['options'] ?? null;
 | 
			
		||||
        if (!empty($field['selectize']['create']) || $validateOptions === 'ignore') {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $options = $field['options'] ?? [];
 | 
			
		||||
        $use = $field['use'] ?? 'values';
 | 
			
		||||
 | 
			
		||||
        if (empty($field['selectize']) || empty($field['multiple'])) {
 | 
			
		||||
        if ($validateOptions) {
 | 
			
		||||
            // Use custom options structure.
 | 
			
		||||
            foreach ($options as &$option) {
 | 
			
		||||
                $option = $option[$validateOptions] ?? null;
 | 
			
		||||
            }
 | 
			
		||||
            unset($option);
 | 
			
		||||
            $options = array_values($options);
 | 
			
		||||
        } elseif (empty($field['selectize']) || empty($field['multiple'])) {
 | 
			
		||||
            $options = array_keys($options);
 | 
			
		||||
        }
 | 
			
		||||
        if ($use === 'keys') {
 | 
			
		||||
@@ -1189,7 +1197,7 @@ class Validation
 | 
			
		||||
     */
 | 
			
		||||
    public static function filterItem_List($value, $params)
 | 
			
		||||
    {
 | 
			
		||||
        return array_values(array_filter($value, function ($v) {
 | 
			
		||||
        return array_values(array_filter($value, static function ($v) {
 | 
			
		||||
            return !empty($v);
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -10,16 +10,18 @@
 | 
			
		||||
namespace Grav\Common\Data;
 | 
			
		||||
 | 
			
		||||
use Grav\Common\Grav;
 | 
			
		||||
use JsonSerializable;
 | 
			
		||||
use RuntimeException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class ValidationException
 | 
			
		||||
 * @package Grav\Common\Data
 | 
			
		||||
 */
 | 
			
		||||
class ValidationException extends RuntimeException
 | 
			
		||||
class ValidationException extends RuntimeException implements JsonSerializable
 | 
			
		||||
{
 | 
			
		||||
    /** @var array */
 | 
			
		||||
    protected $messages = [];
 | 
			
		||||
    protected $escape = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param array $messages
 | 
			
		||||
@@ -32,21 +34,34 @@ class ValidationException extends RuntimeException
 | 
			
		||||
        $language = Grav::instance()['language'];
 | 
			
		||||
        $this->message = $language->translate('GRAV.FORM.VALIDATION_FAIL', null, true) . ' ' . $this->message;
 | 
			
		||||
 | 
			
		||||
        foreach ($messages as $variable => &$list) {
 | 
			
		||||
        foreach ($messages as $list) {
 | 
			
		||||
            $list = array_unique($list);
 | 
			
		||||
            foreach ($list as $message) {
 | 
			
		||||
                $this->message .= "<br/>$message";
 | 
			
		||||
                $this->message .= '<br/>' . htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setSimpleMessage(bool $escape = true): void
 | 
			
		||||
    {
 | 
			
		||||
        $first = reset($this->messages);
 | 
			
		||||
        $message = reset($first);
 | 
			
		||||
 | 
			
		||||
        $this->message = $escape ? htmlspecialchars($message, ENT_QUOTES | ENT_HTML5, 'UTF-8') : $message;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function getMessages()
 | 
			
		||||
    public function getMessages(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->messages;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function jsonSerialize(): array
 | 
			
		||||
    {
 | 
			
		||||
        return ['validation' => $this->messages];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -332,7 +332,7 @@ class Debugger
 | 
			
		||||
            return new Response(404, $headers, json_encode($response));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $data = is_array($data) ? array_map(function ($item) {
 | 
			
		||||
        $data = is_array($data) ? array_map(static function ($item) {
 | 
			
		||||
            return $item->toArray();
 | 
			
		||||
        }, $data) : $data->toArray();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -197,7 +197,7 @@ abstract class Folder
 | 
			
		||||
     * Shift first directory out of the path.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $path
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @return string|null
 | 
			
		||||
     */
 | 
			
		||||
    public static function shift(&$path)
 | 
			
		||||
    {
 | 
			
		||||
@@ -371,7 +371,7 @@ abstract class Folder
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (strpos($target, $source) === 0) {
 | 
			
		||||
        if (strpos($target, $source . '/') === 0) {
 | 
			
		||||
            throw new RuntimeException('Cannot move folder to itself');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -417,7 +417,8 @@ abstract class Folder
 | 
			
		||||
 | 
			
		||||
        if (!$success) {
 | 
			
		||||
            $error = error_get_last();
 | 
			
		||||
            throw new RuntimeException($error['message']);
 | 
			
		||||
 | 
			
		||||
            throw new RuntimeException($error['message'] ?? 'Unknown error');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Make sure that the change will be detected when caching.
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,9 @@ class ZipArchiver extends Archiver
 | 
			
		||||
            throw new InvalidArgumentException('ZipArchiver: Zip PHP module not installed...');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!file_exists($source)) {
 | 
			
		||||
        // Get real path for our folder
 | 
			
		||||
        $rootPath = realpath($source);
 | 
			
		||||
        if (!$rootPath) {
 | 
			
		||||
            throw new InvalidArgumentException('ZipArchiver: ' . $source . ' cannot be found...');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -66,9 +68,6 @@ class ZipArchiver extends Archiver
 | 
			
		||||
            throw new InvalidArgumentException('ZipArchiver:' . $this->archive_file . ' cannot be created...');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Get real path for our folder
 | 
			
		||||
        $rootPath = realpath($source);
 | 
			
		||||
 | 
			
		||||
        $files = $this->getArchiveFiles($rootPath);
 | 
			
		||||
 | 
			
		||||
        $status && $status([
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ abstract class FlexObject extends \Grav\Framework\Flex\FlexObject implements Med
 | 
			
		||||
 | 
			
		||||
        // Handle media fields.
 | 
			
		||||
        $settings = $this->getFieldSettings($name);
 | 
			
		||||
        if ($settings['media_field'] ?? false === true) {
 | 
			
		||||
        if (($settings['media_field'] ?? false) === true) {
 | 
			
		||||
            return $this->parseFileProperty($value, $settings);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,6 @@ use Grav\Common\Page\Header;
 | 
			
		||||
use Grav\Common\Page\Interfaces\PageCollectionInterface;
 | 
			
		||||
use Grav\Common\Page\Interfaces\PageInterface;
 | 
			
		||||
use Grav\Common\Utils;
 | 
			
		||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
 | 
			
		||||
use Grav\Framework\Flex\Pages\FlexPageCollection;
 | 
			
		||||
use Collator;
 | 
			
		||||
use InvalidArgumentException;
 | 
			
		||||
@@ -159,7 +158,7 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
 | 
			
		||||
     */
 | 
			
		||||
    public function addPage(PageInterface $page)
 | 
			
		||||
    {
 | 
			
		||||
        if (!$page instanceof FlexObjectInterface) {
 | 
			
		||||
        if (!$page instanceof PageObject) {
 | 
			
		||||
            throw new InvalidArgumentException('$page is not a flex page.');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -400,8 +399,8 @@ class PageCollection extends FlexPageCollection implements PageCollectionInterfa
 | 
			
		||||
            $i = count($manual);
 | 
			
		||||
            $new_list = [];
 | 
			
		||||
            foreach ($list as $key => $dummy) {
 | 
			
		||||
                $child = $this[$key];
 | 
			
		||||
                $order = array_search($child->slug, $manual, true);
 | 
			
		||||
                $child = $this[$key] ?? null;
 | 
			
		||||
                $order = $child ? array_search($child->slug, $manual, true) : false;
 | 
			
		||||
                if ($order === false) {
 | 
			
		||||
                    $order = $i++;
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -109,6 +109,10 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $element = parent::get($key);
 | 
			
		||||
        if (null === $element) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isset($params)) {
 | 
			
		||||
            $element = $element->getTranslation(ltrim($params, '.'));
 | 
			
		||||
        }
 | 
			
		||||
@@ -331,7 +335,10 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
 | 
			
		||||
     */
 | 
			
		||||
    protected function filterByParent(array $filters)
 | 
			
		||||
    {
 | 
			
		||||
        return parent::filterBy($filters);
 | 
			
		||||
        /** @var static $index */
 | 
			
		||||
        $index = parent::filterBy($filters);
 | 
			
		||||
 | 
			
		||||
        return $index;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -547,6 +554,9 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
 | 
			
		||||
        $filters = array_filter($filters, static function($val) { return $val !== null && $val !== ''; });
 | 
			
		||||
 | 
			
		||||
        if ($page) {
 | 
			
		||||
            $status = 'success';
 | 
			
		||||
            $msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND';
 | 
			
		||||
 | 
			
		||||
            if ($page->root() && (!$filter_type || in_array('root', $filter_type, true))) {
 | 
			
		||||
                if ($field) {
 | 
			
		||||
                    $response[] = [
 | 
			
		||||
@@ -586,9 +596,6 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $status = 'success';
 | 
			
		||||
            $msg = 'PLUGIN_ADMIN.PAGE_ROUTE_FOUND';
 | 
			
		||||
 | 
			
		||||
            /** @var PageIndex $children */
 | 
			
		||||
            $children = $page->children()->getIndex();
 | 
			
		||||
            $selectedChildren = $children->filterBy($filters, true);
 | 
			
		||||
@@ -673,12 +680,13 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
 | 
			
		||||
                    $child_count = $tmp->count();
 | 
			
		||||
                    $count = $filters ? $tmp->filterBy($filters, true)->count() : null;
 | 
			
		||||
                    $route = $child->getRoute();
 | 
			
		||||
                    $route = $route ? ($route->toString(false) ?: '/') : '';
 | 
			
		||||
                    $payload = [
 | 
			
		||||
                        'item-key' => htmlspecialchars(basename($child->rawRoute() ?? $child->getKey())),
 | 
			
		||||
                        'icon' => $icon,
 | 
			
		||||
                        'title' => htmlspecialchars($child->menu()),
 | 
			
		||||
                        'route' => [
 | 
			
		||||
                            'display' => htmlspecialchars(($route ? ($route->toString(false) ?: '/') : null) ?? ''),
 | 
			
		||||
                            'display' => htmlspecialchars($route) ?: null,
 | 
			
		||||
                            'raw' => htmlspecialchars($child->rawRoute()),
 | 
			
		||||
                        ],
 | 
			
		||||
                        'modified' => $this->jsDate($child->modified()),
 | 
			
		||||
@@ -713,7 +721,7 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
 | 
			
		||||
            $response = Utils::arrayFlatten($sorted);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return [$status, $msg ?? 'PLUGIN_ADMIN.NO_ROUTE_PROVIDED', $response, $path];
 | 
			
		||||
        return [$status, $msg, $response, $path];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -834,12 +842,11 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove item from the list.
 | 
			
		||||
     *
 | 
			
		||||
     * @param PageInterface|string|null $key
 | 
			
		||||
     *
 | 
			
		||||
     * @return $this
 | 
			
		||||
     * @param string $key
 | 
			
		||||
     * @return PageObject|null
 | 
			
		||||
     * @throws InvalidArgumentException
 | 
			
		||||
     */
 | 
			
		||||
    public function remove($key = null)
 | 
			
		||||
    public function remove($key)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getCollection()->remove($key);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -104,12 +104,12 @@ class PageObject extends FlexPageObject
 | 
			
		||||
     */
 | 
			
		||||
    public function getRoute($query = []): ?Route
 | 
			
		||||
    {
 | 
			
		||||
        $route = $this->route();
 | 
			
		||||
        if (null === $route) {
 | 
			
		||||
        $path = $this->route();
 | 
			
		||||
        if (null === $path) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $route = RouteFactory::createFromString($route);
 | 
			
		||||
        $route = RouteFactory::createFromString($path);
 | 
			
		||||
        if ($lang = $route->getLanguage()) {
 | 
			
		||||
            $grav = Grav::instance();
 | 
			
		||||
            if (!$grav['config']->get('system.languages.include_default_lang')) {
 | 
			
		||||
@@ -311,7 +311,7 @@ class PageObject extends FlexPageObject
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Reset original after save events have all been called.
 | 
			
		||||
        $this->_original = null;
 | 
			
		||||
        $this->_originalObject = null;
 | 
			
		||||
 | 
			
		||||
        return $instance;
 | 
			
		||||
    }
 | 
			
		||||
@@ -441,7 +441,8 @@ class PageObject extends FlexPageObject
 | 
			
		||||
 | 
			
		||||
        // Add missing siblings into the end of the list, keeping the previous ordering between them.
 | 
			
		||||
        foreach ($siblings as $sibling) {
 | 
			
		||||
            $basename = preg_replace('|^\d+\.|', '', $sibling->getProperty('folder'));
 | 
			
		||||
            $folder = (string)$sibling->getProperty('folder');
 | 
			
		||||
            $basename = preg_replace('|^\d+\.|', '', $folder);
 | 
			
		||||
            if (!in_array($basename, $ordering, true)) {
 | 
			
		||||
                $ordering[] = $basename;
 | 
			
		||||
            }
 | 
			
		||||
@@ -451,7 +452,8 @@ class PageObject extends FlexPageObject
 | 
			
		||||
        $ordering = array_flip(array_values($ordering));
 | 
			
		||||
        $count = count($ordering);
 | 
			
		||||
        foreach ($siblings as $sibling) {
 | 
			
		||||
            $basename = preg_replace('|^\d+\.|', '', $sibling->getProperty('folder'));
 | 
			
		||||
            $folder = (string)$sibling->getProperty('folder');
 | 
			
		||||
            $basename = preg_replace('|^\d+\.|', '', $folder);
 | 
			
		||||
            $newOrder = $ordering[$basename] ?? null;
 | 
			
		||||
            $newOrder = null !== $newOrder ? $newOrder + 1 : (int)$sibling->order() + $count;
 | 
			
		||||
            $sibling->order($newOrder);
 | 
			
		||||
 
 | 
			
		||||
@@ -103,7 +103,10 @@ trait PageLegacyTrait
 | 
			
		||||
        $parent = $this->parent();
 | 
			
		||||
        $collection = $parent ? $parent->collection('content', false) : null;
 | 
			
		||||
        if (null !== $path && $collection instanceof PageCollectionInterface) {
 | 
			
		||||
            return $collection->adjacentSibling($path, $direction);
 | 
			
		||||
            $child = $collection->adjacentSibling($path, $direction);
 | 
			
		||||
            if ($child instanceof PageInterface) {
 | 
			
		||||
                return $child;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -92,7 +92,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
 | 
			
		||||
                } else {
 | 
			
		||||
                    $user = parent::find($query, $field);
 | 
			
		||||
                }
 | 
			
		||||
                if ($user) {
 | 
			
		||||
                if ($user instanceof UserObject) {
 | 
			
		||||
                    return $user;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -123,7 +123,7 @@ class UserCollection extends FlexCollection implements UserCollectionInterface
 | 
			
		||||
     * @param string $key
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    protected function filterUsername(string $key)
 | 
			
		||||
    protected function filterUsername(string $key): string
 | 
			
		||||
    {
 | 
			
		||||
        $storage = $this->getFlexDirectory()->getStorage();
 | 
			
		||||
        if (method_exists($storage, 'normalizeKey')) {
 | 
			
		||||
 
 | 
			
		||||
@@ -62,7 +62,7 @@ class UserIndex extends FlexIndex implements UserCollectionInterface
 | 
			
		||||
     * @param FlexStorageInterface $storage
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage)
 | 
			
		||||
    public static function updateObjectMeta(array &$meta, array $data, FlexStorageInterface $storage): void
 | 
			
		||||
    {
 | 
			
		||||
        // Username can also be number and stored as such.
 | 
			
		||||
        $key = (string)($data['username'] ?? $meta['key'] ?? $meta['storage_key']);
 | 
			
		||||
@@ -187,7 +187,7 @@ class UserIndex extends FlexIndex implements UserCollectionInterface
 | 
			
		||||
     * @param array $updated
 | 
			
		||||
     * @param array $removed
 | 
			
		||||
     */
 | 
			
		||||
    protected static function onChanges(array $entries, array $added, array $updated, array $removed)
 | 
			
		||||
    protected static function onChanges(array $entries, array $added, array $updated, array $removed): void
 | 
			
		||||
    {
 | 
			
		||||
        $message = sprintf('Flex: User index updated, %d objects (%d added, %d updated, %d removed).', count($entries), count($added), count($updated), count($removed));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -230,6 +230,16 @@ class UserObject extends FlexObject implements UserInterface, Countable
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function isMyself(): bool
 | 
			
		||||
    {
 | 
			
		||||
        $me = $this->getActiveUser();
 | 
			
		||||
 | 
			
		||||
        return $me && $me->authenticated && $this->username === $me->username;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks user authorization to the action.
 | 
			
		||||
     *
 | 
			
		||||
@@ -264,6 +274,7 @@ class UserObject extends FlexObject implements UserInterface, Countable
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check custom application access.
 | 
			
		||||
        $authorizeCallable = static::$authorizeCallable;
 | 
			
		||||
        if ($authorizeCallable instanceof Closure) {
 | 
			
		||||
            $authorizeCallable->bindTo($this);
 | 
			
		||||
@@ -280,13 +291,14 @@ class UserObject extends FlexObject implements UserInterface, Countable
 | 
			
		||||
            return $authorized;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If specific rule isn't hit, check if user is super user.
 | 
			
		||||
        if ($access->authorize('admin.super') === true) {
 | 
			
		||||
            return true;
 | 
			
		||||
        // Check group access.
 | 
			
		||||
        $authorized = $this->getGroups()->authorize($action, $scope);
 | 
			
		||||
        if (is_bool($authorized)) {
 | 
			
		||||
            return $authorized;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Check group access.
 | 
			
		||||
        return $this->getGroups()->authorize($action, $scope);
 | 
			
		||||
        // If any specific rule isn't hit, check if user is a superuser.
 | 
			
		||||
        return $access->authorize('admin.super') === true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,143 +1,3 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @package    Grav\Common\GPM
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright  Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
 | 
			
		||||
 * @license    MIT License; see LICENSE file for details.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Grav\Common\GPM;
 | 
			
		||||
 | 
			
		||||
use Exception;
 | 
			
		||||
use Grav\Common\Utils;
 | 
			
		||||
use Grav\Common\Grav;
 | 
			
		||||
use Symfony\Component\HttpClient\CurlHttpClient;
 | 
			
		||||
use Symfony\Component\HttpClient\Exception\TransportException;
 | 
			
		||||
use Symfony\Component\HttpClient\HttpClient;
 | 
			
		||||
use Symfony\Component\HttpClient\HttpOptions;
 | 
			
		||||
use Symfony\Component\HttpClient\NativeHttpClient;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
 | 
			
		||||
use function call_user_func;
 | 
			
		||||
use function defined;
 | 
			
		||||
use function function_exists;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class Response
 | 
			
		||||
 * @package Grav\Common\GPM
 | 
			
		||||
 */
 | 
			
		||||
class Response
 | 
			
		||||
{
 | 
			
		||||
    /** @var callable    The callback for the progress, either a function or callback in array notation */
 | 
			
		||||
    public static $callback = null;
 | 
			
		||||
    /** @var string[] */
 | 
			
		||||
    private static $headers = [
 | 
			
		||||
        'User-Agent' => 'Grav CMS'
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes a request to the URL by using the preferred method
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $uri URL to call
 | 
			
		||||
     * @param array $overrides An array of parameters for both `curl` and `fopen`
 | 
			
		||||
     * @param callable|null $callback Either a function or callback in array notation
 | 
			
		||||
     * @return string The response of the request
 | 
			
		||||
     * @throws TransportExceptionInterface
 | 
			
		||||
     */
 | 
			
		||||
    public static function get($uri = '', $overrides = [], $callback = null)
 | 
			
		||||
    {
 | 
			
		||||
        if (empty($uri)) {
 | 
			
		||||
            throw new TransportException('missing URI');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check if this function is available, if so use it to stop any timeouts
 | 
			
		||||
        try {
 | 
			
		||||
            if (Utils::functionExists('set_time_limit')) {
 | 
			
		||||
                @set_time_limit(0);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception $e) {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $config = Grav::instance()['config'];
 | 
			
		||||
        $referer = defined('GRAV_CLI') ? 'grav_cli' : Grav::instance()['uri']->rootUrl(true);
 | 
			
		||||
        $options = new HttpOptions();
 | 
			
		||||
 | 
			
		||||
        // Set default Headers
 | 
			
		||||
        $options->setHeaders(array_merge([ 'Referer' => $referer ], self::$headers));
 | 
			
		||||
 | 
			
		||||
        // Disable verify Peer if required
 | 
			
		||||
        $verify_peer = $config->get('system.gpm.verify_peer', true);
 | 
			
		||||
        if ($verify_peer !== true) {
 | 
			
		||||
            $options->verifyPeer($verify_peer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set proxy url if provided
 | 
			
		||||
        $proxy_url = $config->get('system.gpm.proxy_url', false);
 | 
			
		||||
        if ($proxy_url) {
 | 
			
		||||
            $options->setProxy($proxy_url);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Use callback if provided
 | 
			
		||||
        if ($callback) {
 | 
			
		||||
            self::$callback = $callback;
 | 
			
		||||
            $options->setOnProgress([Response::class, 'progress']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $preferred_method = $config->get('system.gpm.method', 'auto');
 | 
			
		||||
 | 
			
		||||
        $settings = array_merge_recursive($options->toArray(), $overrides);
 | 
			
		||||
 | 
			
		||||
        switch ($preferred_method) {
 | 
			
		||||
            case 'curl':
 | 
			
		||||
                $client = new CurlHttpClient($settings);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'fopen':
 | 
			
		||||
            case 'native':
 | 
			
		||||
                $client = new NativeHttpClient($settings);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                $client = HttpClient::create($settings);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $response = $client->request('GET', $uri);
 | 
			
		||||
 | 
			
		||||
        return $response->getContent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Is this a remote file or not
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $file
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public static function isRemote($file)
 | 
			
		||||
    {
 | 
			
		||||
        return (bool) filter_var($file, FILTER_VALIDATE_URL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Progress normalized for cURL and Fopen
 | 
			
		||||
     * Accepts a variable length of arguments passed in by stream method
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public static function progress(int $bytes_transferred, int $filesize, array $info)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        if ($bytes_transferred > 0) {
 | 
			
		||||
            $percent = $filesize <= 0 ? 0 : (int)(($bytes_transferred * 100) / $filesize);
 | 
			
		||||
 | 
			
		||||
            $progress = [
 | 
			
		||||
                'code'        => $info['http_code'],
 | 
			
		||||
                'filesize'    => $filesize,
 | 
			
		||||
                'transferred' => $bytes_transferred,
 | 
			
		||||
                'percent'     => $percent < 100 ? $percent : 100
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            if (self::$callback !== null) {
 | 
			
		||||
                call_user_func(self::$callback, $progress);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
// Create alias for the deprecated class.
 | 
			
		||||
class_alias(\Grav\Common\HTTP\Response::class, \Grav\Common\GPM\Response::class);
 | 
			
		||||
 
 | 
			
		||||
@@ -69,6 +69,7 @@ abstract class Getters implements ArrayAccess, Countable
 | 
			
		||||
     * @param int|string $offset
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetExists($offset)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->gettersVariable) {
 | 
			
		||||
@@ -84,6 +85,7 @@ abstract class Getters implements ArrayAccess, Countable
 | 
			
		||||
     * @param int|string $offset
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetGet($offset)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->gettersVariable) {
 | 
			
		||||
@@ -99,6 +101,7 @@ abstract class Getters implements ArrayAccess, Countable
 | 
			
		||||
     * @param int|string $offset
 | 
			
		||||
     * @param mixed $value
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetSet($offset, $value)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->gettersVariable) {
 | 
			
		||||
@@ -112,6 +115,7 @@ abstract class Getters implements ArrayAccess, Countable
 | 
			
		||||
    /**
 | 
			
		||||
     * @param int|string $offset
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetUnset($offset)
 | 
			
		||||
    {
 | 
			
		||||
        if ($this->gettersVariable) {
 | 
			
		||||
 
 | 
			
		||||
@@ -135,7 +135,7 @@ class Grav extends Container
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public static function resetInstance()
 | 
			
		||||
    public static function resetInstance(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (self::$instance) {
 | 
			
		||||
            // @phpstan-ignore-next-line
 | 
			
		||||
@@ -242,7 +242,7 @@ class Grav extends Container
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function process()
 | 
			
		||||
    public function process(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (isset($this->initialized['process'])) {
 | 
			
		||||
            return;
 | 
			
		||||
@@ -464,6 +464,10 @@ class Grav extends Container
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($uri->extension() === 'json') {
 | 
			
		||||
            return new Response(200, ['Content-Type' => 'application/json'], json_encode(['code' => $code, 'redirect' => $url], JSON_THROW_ON_ERROR));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return new Response($code, ['Location' => $url]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -474,7 +478,7 @@ class Grav extends Container
 | 
			
		||||
     * @param int    $code  Redirection code (30x)
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function redirectLangSafe($route, $code = null)
 | 
			
		||||
    public function redirectLangSafe($route, $code = null): void
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this['uri']->isExternal($route)) {
 | 
			
		||||
            $this->redirect($this['pages']->route($route), $code);
 | 
			
		||||
@@ -489,7 +493,7 @@ class Grav extends Container
 | 
			
		||||
     * @param ResponseInterface|null $response
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function header(ResponseInterface $response = null)
 | 
			
		||||
    public function header(ResponseInterface $response = null): void
 | 
			
		||||
    {
 | 
			
		||||
        if (null === $response) {
 | 
			
		||||
            /** @var PageInterface $page */
 | 
			
		||||
@@ -514,7 +518,7 @@ class Grav extends Container
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function setLocale()
 | 
			
		||||
    public function setLocale(): void
 | 
			
		||||
    {
 | 
			
		||||
        // Initialize Locale if set and configured.
 | 
			
		||||
        if ($this['language']->enabled() && $this['config']->get('system.languages.override_locale')) {
 | 
			
		||||
@@ -575,7 +579,7 @@ class Grav extends Container
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function shutdown()
 | 
			
		||||
    public function shutdown(): void
 | 
			
		||||
    {
 | 
			
		||||
        // Prevent user abort allowing onShutdown event to run without interruptions.
 | 
			
		||||
        if (function_exists('ignore_user_abort')) {
 | 
			
		||||
@@ -694,7 +698,7 @@ class Grav extends Container
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    protected function registerServices()
 | 
			
		||||
    protected function registerServices(): void
 | 
			
		||||
    {
 | 
			
		||||
        foreach (self::$diMap as $serviceKey => $serviceClass) {
 | 
			
		||||
            if (is_int($serviceKey)) {
 | 
			
		||||
@@ -761,12 +765,10 @@ class Grav extends Container
 | 
			
		||||
            // unsupported media type, try to download it...
 | 
			
		||||
            if ($uri_extension) {
 | 
			
		||||
                $extension = $uri_extension;
 | 
			
		||||
            } elseif (isset($path_parts['extension'])) {
 | 
			
		||||
                $extension = $path_parts['extension'];
 | 
			
		||||
            } else {
 | 
			
		||||
                if (isset($path_parts['extension'])) {
 | 
			
		||||
                    $extension = $path_parts['extension'];
 | 
			
		||||
                } else {
 | 
			
		||||
                    $extension = null;
 | 
			
		||||
                }
 | 
			
		||||
                $extension = null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if ($extension) {
 | 
			
		||||
@@ -776,11 +778,9 @@ class Grav extends Container
 | 
			
		||||
                }
 | 
			
		||||
                Utils::download($page->path() . DIRECTORY_SEPARATOR . $uri->basename(), $download);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Nothing found
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $page;
 | 
			
		||||
        // Nothing found
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										130
									
								
								system/src/Grav/Common/HTTP/Client.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								system/src/Grav/Common/HTTP/Client.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,130 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @package    Grav\Common\HTTP
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright  Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
 | 
			
		||||
 * @license    MIT License; see LICENSE file for details.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Grav\Common\HTTP;
 | 
			
		||||
 | 
			
		||||
use Grav\Common\Grav;
 | 
			
		||||
use Symfony\Component\HttpClient\CurlHttpClient;
 | 
			
		||||
use Symfony\Component\HttpClient\HttpClient;
 | 
			
		||||
use Symfony\Component\HttpClient\HttpOptions;
 | 
			
		||||
use Symfony\Component\HttpClient\NativeHttpClient;
 | 
			
		||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
 | 
			
		||||
 | 
			
		||||
class Client
 | 
			
		||||
{
 | 
			
		||||
    /** @var callable    The callback for the progress, either a function or callback in array notation */
 | 
			
		||||
    public static $callback = null;
 | 
			
		||||
    /** @var string[] */
 | 
			
		||||
    private static $headers = [
 | 
			
		||||
        'User-Agent' => 'Grav CMS'
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public static function getClient(array $overrides = [], int $connections = 6, callable $callback = null): HttpClientInterface
 | 
			
		||||
    {
 | 
			
		||||
        $config = Grav::instance()['config'];
 | 
			
		||||
        $options = static::getOptions();
 | 
			
		||||
 | 
			
		||||
        // Use callback if provided
 | 
			
		||||
        if ($callback) {
 | 
			
		||||
            self::$callback = $callback;
 | 
			
		||||
            $options->setOnProgress([Client::class, 'progress']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $settings = array_merge($options->toArray(), $overrides);
 | 
			
		||||
        $preferred_method = $config->get('system.http.method');
 | 
			
		||||
        // Try old GPM setting if value is the same as system default
 | 
			
		||||
        if ($preferred_method === 'auto') {
 | 
			
		||||
            $preferred_method = $config->get('system.gpm.method', 'auto');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        switch ($preferred_method) {
 | 
			
		||||
            case 'curl':
 | 
			
		||||
                $client = new CurlHttpClient($settings, $connections);
 | 
			
		||||
                break;
 | 
			
		||||
            case 'fopen':
 | 
			
		||||
            case 'native':
 | 
			
		||||
                $client = new NativeHttpClient($settings, $connections);
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                $client = HttpClient::create($settings, $connections);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $client;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get HTTP Options
 | 
			
		||||
     *
 | 
			
		||||
     * @return HttpOptions
 | 
			
		||||
     */
 | 
			
		||||
    public static function getOptions(): HttpOptions
 | 
			
		||||
    {
 | 
			
		||||
        $config = Grav::instance()['config'];
 | 
			
		||||
        $referer = defined('GRAV_CLI') ? 'grav_cli' : Grav::instance()['uri']->rootUrl(true);
 | 
			
		||||
 | 
			
		||||
        $options = new HttpOptions();
 | 
			
		||||
 | 
			
		||||
        // Set default Headers
 | 
			
		||||
        $options->setHeaders(array_merge([ 'Referer' => $referer ], self::$headers));
 | 
			
		||||
 | 
			
		||||
        // Disable verify Peer if required
 | 
			
		||||
        $verify_peer = $config->get('system.http.verify_peer');
 | 
			
		||||
        // Try old GPM setting if value is default
 | 
			
		||||
        if ($verify_peer === true) {
 | 
			
		||||
            $verify_peer = $config->get('system.gpm.verify_peer', null) ?? $verify_peer;
 | 
			
		||||
        }
 | 
			
		||||
        $options->verifyPeer($verify_peer);
 | 
			
		||||
 | 
			
		||||
        // Set verify Host
 | 
			
		||||
        $verify_host = $config->get('system.http.verify_host', true);
 | 
			
		||||
        $options->verifyHost($verify_host);
 | 
			
		||||
 | 
			
		||||
        // New setting and must be enabled for Proxy to work
 | 
			
		||||
        if ($config->get('system.http.enable_proxy', true)) {
 | 
			
		||||
            // Set proxy url if provided
 | 
			
		||||
            $proxy_url = $config->get('system.http.proxy_url', $config->get('system.gpm.proxy_url', null));
 | 
			
		||||
            if ($proxy_url !== null) {
 | 
			
		||||
                $options->setProxy($proxy_url);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Certificate
 | 
			
		||||
            $proxy_cert = $config->get('system.http.proxy_cert_path', null);
 | 
			
		||||
            if ($proxy_cert !== null) {
 | 
			
		||||
                $options->setCaPath($proxy_cert);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $options;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Progress normalized for cURL and Fopen
 | 
			
		||||
     * Accepts a variable length of arguments passed in by stream method
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public static function progress(int $bytes_transferred, int $filesize, array $info)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        if ($bytes_transferred > 0) {
 | 
			
		||||
            $percent = $filesize <= 0 ? 0 : (int)(($bytes_transferred * 100) / $filesize);
 | 
			
		||||
 | 
			
		||||
            $progress = [
 | 
			
		||||
                'code'        => $info['http_code'],
 | 
			
		||||
                'filesize'    => $filesize,
 | 
			
		||||
                'transferred' => $bytes_transferred,
 | 
			
		||||
                'percent'     => $percent < 100 ? $percent : 100
 | 
			
		||||
            ];
 | 
			
		||||
 | 
			
		||||
            if (self::$callback !== null) {
 | 
			
		||||
                call_user_func(self::$callback, $progress);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								system/src/Grav/Common/HTTP/Response.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								system/src/Grav/Common/HTTP/Response.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @package    Grav\Common\HTTP
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright  Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
 | 
			
		||||
 * @license    MIT License; see LICENSE file for details.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Grav\Common\HTTP;
 | 
			
		||||
 | 
			
		||||
use Exception;
 | 
			
		||||
use Grav\Common\Utils;
 | 
			
		||||
use Grav\Common\Grav;
 | 
			
		||||
use Symfony\Component\HttpClient\CurlHttpClient;
 | 
			
		||||
use Symfony\Component\HttpClient\Exception\TransportException;
 | 
			
		||||
use Symfony\Component\HttpClient\HttpClient;
 | 
			
		||||
use Symfony\Component\HttpClient\HttpOptions;
 | 
			
		||||
use Symfony\Component\HttpClient\NativeHttpClient;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
 | 
			
		||||
use Symfony\Contracts\HttpClient\ResponseInterface;
 | 
			
		||||
use function call_user_func;
 | 
			
		||||
use function defined;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class Response
 | 
			
		||||
 * @package Grav\Common\GPM
 | 
			
		||||
 */
 | 
			
		||||
class Response
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * Backwards compatible helper method
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $uri
 | 
			
		||||
     * @param array $overrides
 | 
			
		||||
     * @param callable|null $callback
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @throws TransportExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface|TransportExceptionInterface|ClientExceptionInterface
 | 
			
		||||
     */
 | 
			
		||||
    public static function get(string $uri = '', array $overrides = [], callable $callback = null): string
 | 
			
		||||
    {
 | 
			
		||||
        $response = static::request('GET', $uri, $overrides, $callback);
 | 
			
		||||
        return $response->getContent();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes a request to the URL by using the preferred method
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $method method to call such as GET, PUT, etc
 | 
			
		||||
     * @param string $uri URL to call
 | 
			
		||||
     * @param array $overrides An array of parameters for both `curl` and `fopen`
 | 
			
		||||
     * @param callable|null $callback Either a function or callback in array notation
 | 
			
		||||
     * @return ResponseInterface
 | 
			
		||||
     * @throws TransportExceptionInterface
 | 
			
		||||
     */
 | 
			
		||||
    public static function request(string $method, string $uri, array $overrides = [], callable $callback = null): ResponseInterface
 | 
			
		||||
    {
 | 
			
		||||
        if (empty($method)) {
 | 
			
		||||
            throw new TransportException('missing method (GET, PUT, etc.)');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (empty($uri)) {
 | 
			
		||||
            throw new TransportException('missing URI');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check if this function is available, if so use it to stop any timeouts
 | 
			
		||||
        try {
 | 
			
		||||
            if (Utils::functionExists('set_time_limit')) {
 | 
			
		||||
                @set_time_limit(0);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception $e) {}
 | 
			
		||||
 | 
			
		||||
        $client = Client::getClient($overrides, 6, $callback);
 | 
			
		||||
 | 
			
		||||
        return $client->request($method, $uri);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Is this a remote file or not
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $file
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public static function isRemote($file): bool
 | 
			
		||||
    {
 | 
			
		||||
        return (bool) filter_var($file, FILTER_VALIDATE_URL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -33,6 +33,9 @@ class Excerpts
 | 
			
		||||
    public static function processImageHtml($html, PageInterface $page = null)
 | 
			
		||||
    {
 | 
			
		||||
        $excerpt = static::getExcerptFromHtml($html, 'img');
 | 
			
		||||
        if (null === $excerpt) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $original_src = $excerpt['element']['attributes']['src'];
 | 
			
		||||
        $excerpt['element']['attributes']['href'] = $original_src;
 | 
			
		||||
@@ -61,6 +64,9 @@ class Excerpts
 | 
			
		||||
    public static function processLinkHtml($html, PageInterface $page = null)
 | 
			
		||||
    {
 | 
			
		||||
        $excerpt = static::getExcerptFromHtml($html, 'a');
 | 
			
		||||
        if (null === $excerpt) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $original_href = $excerpt['element']['attributes']['href'];
 | 
			
		||||
        $excerpt = static::processLinkExcerpt($excerpt, $page, 'link');
 | 
			
		||||
@@ -89,7 +95,6 @@ class Excerpts
 | 
			
		||||
        $excerpt = null;
 | 
			
		||||
        $inner = [];
 | 
			
		||||
 | 
			
		||||
        /** @var DOMElement $element */
 | 
			
		||||
        foreach ($elements as $element) {
 | 
			
		||||
            $attributes = [];
 | 
			
		||||
            foreach ($element->attributes as $name => $value) {
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,6 @@ class LogViewer
 | 
			
		||||
     */
 | 
			
		||||
    public function tail($filepath, $lines = 1)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        $f = $filepath ? @fopen($filepath, 'rb') : false;
 | 
			
		||||
        if ($f === false) {
 | 
			
		||||
            return false;
 | 
			
		||||
@@ -62,13 +61,12 @@ class LogViewer
 | 
			
		||||
        $buffer = ($lines < 2 ? 64 : ($lines < 10 ? 512 : 4096));
 | 
			
		||||
 | 
			
		||||
        fseek($f, -1, SEEK_END);
 | 
			
		||||
        if (fread($f, 1) != "\n") {
 | 
			
		||||
            $lines -= 1;
 | 
			
		||||
        if (fread($f, 1) !== "\n") {
 | 
			
		||||
            --$lines;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Start reading
 | 
			
		||||
        $output = '';
 | 
			
		||||
        $chunk = '';
 | 
			
		||||
        // While we would like more
 | 
			
		||||
        while (ftell($f) > 0 && $lines >= 0) {
 | 
			
		||||
            // Figure out how far back we should jump
 | 
			
		||||
@@ -76,7 +74,11 @@ class LogViewer
 | 
			
		||||
            // Do the jump (backwards, relative to where we are)
 | 
			
		||||
            fseek($f, -$seek, SEEK_CUR);
 | 
			
		||||
            // Read a chunk and prepend it to our output
 | 
			
		||||
            $output = ($chunk = fread($f, $seek)) . $output;
 | 
			
		||||
            $chunk = fread($f, $seek);
 | 
			
		||||
            if ($chunk === false) {
 | 
			
		||||
                throw new \RuntimeException('Cannot read file');
 | 
			
		||||
            }
 | 
			
		||||
            $output = $chunk . $output;
 | 
			
		||||
            // Jump back to where we started reading
 | 
			
		||||
            fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
 | 
			
		||||
            // Decrease our line counter
 | 
			
		||||
@@ -123,13 +125,13 @@ class LogViewer
 | 
			
		||||
     */
 | 
			
		||||
    public function parse($line)
 | 
			
		||||
    {
 | 
			
		||||
        if (!is_string($line) || strlen($line) === 0) {
 | 
			
		||||
            return array();
 | 
			
		||||
        if (!is_string($line) || $line === '') {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        preg_match($this->pattern, $line, $data);
 | 
			
		||||
        if (!isset($data['date'])) {
 | 
			
		||||
            return array();
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        preg_match('/(.*)- Trace:(.*)/', $data['message'], $matches);
 | 
			
		||||
@@ -138,7 +140,7 @@ class LogViewer
 | 
			
		||||
            $data['trace'] = trim($matches[2]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return array(
 | 
			
		||||
        return [
 | 
			
		||||
            'date' => DateTime::createFromFormat('Y-m-d H:i:s', $data['date']),
 | 
			
		||||
            'logger' => $data['logger'],
 | 
			
		||||
            'level' => $data['level'],
 | 
			
		||||
@@ -146,7 +148,7 @@ class LogViewer
 | 
			
		||||
            'trace' => isset($data['trace']) ? $this->parseTrace($data['trace']) : null,
 | 
			
		||||
            'context' => json_decode($data['context'], true),
 | 
			
		||||
            'extra' => json_decode($data['extra'], true)
 | 
			
		||||
        );
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -230,9 +230,7 @@ class Iterator implements \ArrayAccess, \Iterator, \Countable, \Serializable
 | 
			
		||||
    public function filter(callable $callback = null)
 | 
			
		||||
    {
 | 
			
		||||
        foreach ($this->items as $key => $value) {
 | 
			
		||||
            if ((!$callback && !(bool)$value) ||
 | 
			
		||||
                ($callback && !$callback($value, $key))
 | 
			
		||||
            ) {
 | 
			
		||||
            if ((!$callback && !(bool)$value) || ($callback && !$callback($value, $key))) {
 | 
			
		||||
                unset($this->items[$key]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -86,12 +86,14 @@ class LanguageCodes
 | 
			
		||||
        'ja-JP'      => [ 'name' => 'Japanese',                  'nativeName' => '日本語' ], // not iso-639-1
 | 
			
		||||
        'ka'         => [ 'name' => 'Georgian',                  'nativeName' => 'ქართული' ],
 | 
			
		||||
        'kk'         => [ 'name' => 'Kazakh',                    'nativeName' => 'Қазақ' ],
 | 
			
		||||
        'km'         => [ 'name' => 'Khmer',                     'nativeName' => 'Khmer' ],
 | 
			
		||||
        'kn'         => [ 'name' => 'Kannada',                   'nativeName' => 'ಕನ್ನಡ' ],
 | 
			
		||||
        'ko'         => [ 'name' => 'Korean',                    'nativeName' => '한국어' ],
 | 
			
		||||
        'ku'         => [ 'name' => 'Kurdish',                   'nativeName' => 'Kurdî' ],
 | 
			
		||||
        'la'         => [ 'name' => 'Latin',                     'nativeName' => 'Latina' ],
 | 
			
		||||
        'lb'         => [ 'name' => 'Luxembourgish',             'nativeName' => 'Lëtzebuergesch' ],
 | 
			
		||||
        'lg'         => [ 'name' => 'Luganda',                   'nativeName' => 'Luganda' ],
 | 
			
		||||
        'lo'         => [ 'name' => 'Lao',                       'nativeName' => 'Lao' ],        
 | 
			
		||||
        'lt'         => [ 'name' => 'Lithuanian',                'nativeName' => 'Lietuvių' ],
 | 
			
		||||
        'lv'         => [ 'name' => 'Latvian',                   'nativeName' => 'Latviešu' ],
 | 
			
		||||
        'mai'        => [ 'name' => 'Maithili',                  'nativeName' => 'मैथिली মৈথিলী' ],
 | 
			
		||||
@@ -101,6 +103,7 @@ class LanguageCodes
 | 
			
		||||
        'ml'         => [ 'name' => 'Malayalam',                 'nativeName' => 'മലയാളം' ],
 | 
			
		||||
        'mn'         => [ 'name' => 'Mongolian',                 'nativeName' => 'Монгол' ],
 | 
			
		||||
        'mr'         => [ 'name' => 'Marathi',                   'nativeName' => 'मराठी' ],
 | 
			
		||||
        'my'         => [ 'name' => 'Myanmar (Burmese)',         'nativeName' => 'ဗမာी' ],        
 | 
			
		||||
        'no'         => [ 'name' => 'Norwegian',                 'nativeName' => 'Norsk' ],
 | 
			
		||||
        'nb'         => [ 'name' => 'Norwegian',                 'nativeName' => 'Norsk' ],
 | 
			
		||||
        'nb-NO'      => [ 'name' => 'Norwegian (Bokmål)',        'nativeName' => 'Norsk bokmål' ],
 | 
			
		||||
@@ -132,6 +135,7 @@ class LanguageCodes
 | 
			
		||||
        'st'         => [ 'name' => 'Southern Sotho',            'nativeName' => 'Sesotho' ],
 | 
			
		||||
        'sv'         => [ 'name' => 'Swedish',                   'nativeName' => 'Svenska' ],
 | 
			
		||||
        'sv-SE'      => [ 'name' => 'Swedish',                   'nativeName' => 'Svenska' ],
 | 
			
		||||
        'sw'         => [ 'name' => 'Swahili',                   'nativeName' => 'Swahili' ],
 | 
			
		||||
        'ta'         => [ 'name' => 'Tamil',                     'nativeName' => 'தமிழ்' ],
 | 
			
		||||
        'ta-IN'      => [ 'name' => 'Tamil (India)',             'nativeName' => 'தமிழ் (இந்தியா)' ],
 | 
			
		||||
        'ta-LK'      => [ 'name' => 'Tamil (Sri Lanka)',         'nativeName' => 'தமிழ் (இலங்கை)' ],
 | 
			
		||||
@@ -187,12 +191,7 @@ class LanguageCodes
 | 
			
		||||
     */
 | 
			
		||||
    public static function getOrientation($code)
 | 
			
		||||
    {
 | 
			
		||||
        if (isset(static::$codes[$code])) {
 | 
			
		||||
            if (isset(static::$codes[$code]['orientation'])) {
 | 
			
		||||
                return static::get($code, 'orientation');
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return 'ltr';
 | 
			
		||||
        return static::$codes[$code]['orientation'] ?? 'ltr';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -226,11 +225,7 @@ class LanguageCodes
 | 
			
		||||
     */
 | 
			
		||||
    public static function get($code, $type)
 | 
			
		||||
    {
 | 
			
		||||
        if (isset(static::$codes[$code][$type])) {
 | 
			
		||||
            return static::$codes[$code][$type];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
        return static::$codes[$code][$type] ?? false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -50,6 +50,8 @@ trait ImageMediaTrait
 | 
			
		||||
    /** @var integer */
 | 
			
		||||
    protected $retina_scale;
 | 
			
		||||
 | 
			
		||||
    /** @var bool */
 | 
			
		||||
    protected $watermark;
 | 
			
		||||
 | 
			
		||||
    /** @var array */
 | 
			
		||||
    public static $magic_actions = [
 | 
			
		||||
@@ -379,6 +381,8 @@ trait ImageMediaTrait
 | 
			
		||||
        $this->aspect_ratio = $config->get('system.images.cls.aspect_ratio', false);
 | 
			
		||||
        $this->retina_scale = $config->get('system.images.cls.retina_scale', 1);
 | 
			
		||||
 | 
			
		||||
        $this->watermark = $config->get('system.images.watermark.watermark_all', false);
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -415,6 +419,10 @@ trait ImageMediaTrait
 | 
			
		||||
            $this->image->merge(ImageFile::open($overlay));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($this->watermark) {
 | 
			
		||||
            $this->watermark();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->image->cacheFile($this->format, $this->quality, false, [$this->get('width'), $this->get('height'), $this->get('modified')]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -108,7 +108,7 @@ trait MediaUploadTrait
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $metadata
 | 
			
		||||
     * @param array|null $settings
 | 
			
		||||
     * @return string|null
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @throws RuntimeException
 | 
			
		||||
     */
 | 
			
		||||
    public function checkFileMetadata(array $metadata, string $filename = null, array $settings = null): string
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ class Collection extends Iterator implements PageCollectionInterface
 | 
			
		||||
        parent::__construct($items);
 | 
			
		||||
 | 
			
		||||
        $this->params = $params;
 | 
			
		||||
        $this->pages = $pages ? $pages : Grav::instance()->offsetGet('pages');
 | 
			
		||||
        $this->pages = $pages ?: Grav::instance()->offsetGet('pages');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -187,6 +187,7 @@ class Collection extends Iterator implements PageCollectionInterface
 | 
			
		||||
     * @param string $offset
 | 
			
		||||
     * @return PageInterface|null
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetGet($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->pages->get($offset) ?: null;
 | 
			
		||||
 
 | 
			
		||||
@@ -58,7 +58,7 @@ interface PageContentInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * Needed by the onPageContentProcessed event to set the raw page content
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $content
 | 
			
		||||
     * @param string|null $content
 | 
			
		||||
     */
 | 
			
		||||
    public function setRawContent($content);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -63,6 +63,7 @@ class Media extends AbstractMedia
 | 
			
		||||
     * @param string $offset
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetExists($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return parent::offsetExists($offset) ?: isset(static::$global[$offset]);
 | 
			
		||||
@@ -72,6 +73,7 @@ class Media extends AbstractMedia
 | 
			
		||||
     * @param string $offset
 | 
			
		||||
     * @return MediaObjectInterface|null
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetGet($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return parent::offsetGet($offset) ?: static::$global[$offset];
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ class GlobalMedia extends AbstractMedia
 | 
			
		||||
     * @param string $offset
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetExists($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return parent::offsetExists($offset) ?: !empty($this->resolveStream($offset));
 | 
			
		||||
@@ -55,6 +56,7 @@ class GlobalMedia extends AbstractMedia
 | 
			
		||||
     * @param string $offset
 | 
			
		||||
     * @return MediaObjectInterface|null
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetGet($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return parent::offsetGet($offset) ?: $this->addMedium($offset);
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ use Grav\Common\Media\Interfaces\MediaLinkInterface;
 | 
			
		||||
use Grav\Common\Media\Traits\ImageLoadingTrait;
 | 
			
		||||
use Grav\Common\Media\Traits\ImageMediaTrait;
 | 
			
		||||
use Grav\Common\Utils;
 | 
			
		||||
use Gregwar\Image\Image;
 | 
			
		||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
 | 
			
		||||
use function func_get_args;
 | 
			
		||||
use function in_array;
 | 
			
		||||
@@ -325,6 +326,67 @@ class ImageMedium extends Medium implements ImageMediaInterface, ImageManipulate
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function watermark($image = null, $position = null, $scale = null)
 | 
			
		||||
    {
 | 
			
		||||
        $grav = $this->getGrav();
 | 
			
		||||
 | 
			
		||||
        $locator = $grav['locator'];
 | 
			
		||||
        $config = $grav['config'];
 | 
			
		||||
 | 
			
		||||
        $args = func_get_args();
 | 
			
		||||
 | 
			
		||||
        $file = $args[0] ?? '1'; // using '1' because of markdown. doing  returns $args[0]='1';
 | 
			
		||||
        $file = $file === '1' ? $config->get('system.images.watermark.image') : $args[0];
 | 
			
		||||
 | 
			
		||||
        $watermark = $locator->findResource($file);
 | 
			
		||||
        $watermark = ImageFile::open($watermark);
 | 
			
		||||
 | 
			
		||||
        // Scaling operations
 | 
			
		||||
        $scale     = ($scale ?? $config->get('system.images.watermark.scale', 100)) / 100;
 | 
			
		||||
        $wwidth    = $this->get('width')  * $scale;
 | 
			
		||||
        $wheight   = $this->get('height') * $scale;
 | 
			
		||||
        $watermark->resize($wwidth, $wheight);
 | 
			
		||||
 | 
			
		||||
        // Position operations
 | 
			
		||||
        $position = !empty($args[1]) ? explode('-',  $args[1]) : ['center', 'center']; // todo change to config
 | 
			
		||||
        $positionY = $position[0] ?? $config->get('system.images.watermark.position_y', 'center');
 | 
			
		||||
        $positionX = $position[1] ?? $config->get('system.images.watermark.position_x', 'center');
 | 
			
		||||
 | 
			
		||||
        switch ($positionY)
 | 
			
		||||
        {
 | 
			
		||||
            case 'top':
 | 
			
		||||
                $positionY = 0;
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'bottom':
 | 
			
		||||
                $positionY = $this->get('height')-$wheight;
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'center':
 | 
			
		||||
                $positionY = ($this->get('height')/2) - ($wheight/2);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        switch ($positionX)
 | 
			
		||||
        {
 | 
			
		||||
            case 'left':
 | 
			
		||||
                $positionX = 0;
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'right':
 | 
			
		||||
                $positionX = $this->get('width')-$wwidth;
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
            case 'center':
 | 
			
		||||
                $positionX = ($this->get('width')/2) - ($wwidth/2);
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->__call('merge', [$watermark,$positionX, $positionY]);
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handle this commonly used variant
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -218,6 +218,25 @@ class Page implements PageInterface
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __clone()
 | 
			
		||||
    {
 | 
			
		||||
        $this->initialized = false;
 | 
			
		||||
        $this->header = $this->header ? clone $this->header : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function initialize(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (!$this->initialized) {
 | 
			
		||||
            $this->initialized = true;
 | 
			
		||||
            $this->route = null;
 | 
			
		||||
            $this->raw_route = null;
 | 
			
		||||
            $this->_forms = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
@@ -975,7 +994,7 @@ class Page implements PageInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * Needed by the onPageContentProcessed event to set the raw page content
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $content
 | 
			
		||||
     * @param string|null $content
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function setRawContent($content)
 | 
			
		||||
@@ -2274,11 +2293,11 @@ class Page implements PageInterface
 | 
			
		||||
    {
 | 
			
		||||
        if ($var !== null) {
 | 
			
		||||
            // make sure first level are arrays
 | 
			
		||||
            array_walk($var, function (&$value) {
 | 
			
		||||
            array_walk($var, static function (&$value) {
 | 
			
		||||
                $value = (array) $value;
 | 
			
		||||
            });
 | 
			
		||||
            // make sure all values are strings
 | 
			
		||||
            array_walk_recursive($var, function (&$value) {
 | 
			
		||||
            array_walk_recursive($var, static function (&$value) {
 | 
			
		||||
                $value = (string) $value;
 | 
			
		||||
            });
 | 
			
		||||
            $this->taxonomy = $var;
 | 
			
		||||
 
 | 
			
		||||
@@ -196,6 +196,58 @@ class Pages
 | 
			
		||||
        return $this->baseRoute($lang) . $route;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get relative referrer route and language code. Returns null if the route isn't within the current base, language (if set) and route.
 | 
			
		||||
     *
 | 
			
		||||
     * @example `$langCode = null; $referrer = $pages->referrerRoute($langCode, '/admin');` returns relative referrer url within /admin and updates $langCode
 | 
			
		||||
     * @example `$langCode = 'en'; $referrer = $pages->referrerRoute($langCode, '/admin');` returns relative referrer url within the /en/admin
 | 
			
		||||
     *
 | 
			
		||||
     * @param string|null $langCode Variable to store the language code. If already set, check only against that language.
 | 
			
		||||
     * @param string $route Optional route within the site.
 | 
			
		||||
     * @return string|null
 | 
			
		||||
     * @since 1.7.23
 | 
			
		||||
     */
 | 
			
		||||
    public function referrerRoute(?string &$langCode, string $route = '/'): ?string
 | 
			
		||||
    {
 | 
			
		||||
        $referrer = $_SERVER['HTTP_REFERER'] ?? null;
 | 
			
		||||
 | 
			
		||||
        // Start by checking that referrer came from our site.
 | 
			
		||||
        $root = $this->grav['base_url_absolute'];
 | 
			
		||||
        if (!is_string($referrer) || !str_starts_with($referrer, $root)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var Language $language */
 | 
			
		||||
        $language = $this->grav['language'];
 | 
			
		||||
 | 
			
		||||
        // Get all language codes and append no language.
 | 
			
		||||
        if (null === $langCode) {
 | 
			
		||||
            $languages = $language->enabled() ? $language->getLanguages() : [];
 | 
			
		||||
            $languages[] = '';
 | 
			
		||||
        } else {
 | 
			
		||||
            $languages[] = $langCode;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $path_base = rtrim($this->base(), '/');
 | 
			
		||||
        $path_route = rtrim($route, '/');
 | 
			
		||||
 | 
			
		||||
        // Try to figure out the language code.
 | 
			
		||||
        foreach ($languages as $code) {
 | 
			
		||||
            $path_lang = $code ? "/{$code}" : '';
 | 
			
		||||
 | 
			
		||||
            $base = $path_base . $path_lang . $path_route;
 | 
			
		||||
            if ($referrer === $base || str_starts_with($referrer, "{$base}/")) {
 | 
			
		||||
                if (null === $langCode) {
 | 
			
		||||
                    $langCode = $code;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return substr($referrer, \strlen($base));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     * Get base URL for Grav pages.
 | 
			
		||||
@@ -274,7 +326,7 @@ class Pages
 | 
			
		||||
     *
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function reset()
 | 
			
		||||
    public function reset(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->initialized = false;
 | 
			
		||||
 | 
			
		||||
@@ -554,7 +606,7 @@ class Pages
 | 
			
		||||
 | 
			
		||||
            if (is_array($sort_flags)) {
 | 
			
		||||
                $sort_flags = array_map('constant', $sort_flags); //transform strings to constant value
 | 
			
		||||
                $sort_flags = array_reduce($sort_flags, function ($a, $b) {
 | 
			
		||||
                $sort_flags = array_reduce($sort_flags, static function ($a, $b) {
 | 
			
		||||
                    return $a | $b;
 | 
			
		||||
                }, 0); //merge constant values using bit or
 | 
			
		||||
            }
 | 
			
		||||
@@ -597,7 +649,7 @@ class Pages
 | 
			
		||||
            $cmd = $value;
 | 
			
		||||
            $params = [];
 | 
			
		||||
        } elseif (is_array($value) && count($value) === 1 && !is_int(key($value))) {
 | 
			
		||||
            // Format: @command.param: { attr1: value1, attr2: value2 }
 | 
			
		||||
            // Format: @command.param: { attr1: value1, attr2: value2 }
 | 
			
		||||
            $cmd = (string)key($value);
 | 
			
		||||
            $params = (array)current($value);
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -663,29 +715,39 @@ class Pages
 | 
			
		||||
 | 
			
		||||
        switch ($type) {
 | 
			
		||||
            case 'all':
 | 
			
		||||
                return $page->children();
 | 
			
		||||
                $collection = $page->children();
 | 
			
		||||
                break;
 | 
			
		||||
            case 'modules':
 | 
			
		||||
            case 'modular':
 | 
			
		||||
                return $page->children()->modules();
 | 
			
		||||
                $collection = $page->children()->modules();
 | 
			
		||||
                break;
 | 
			
		||||
            case 'pages':
 | 
			
		||||
            case 'children':
 | 
			
		||||
                return $page->children()->pages();
 | 
			
		||||
                $collection = $page->children()->pages();
 | 
			
		||||
                break;
 | 
			
		||||
            case 'page':
 | 
			
		||||
            case 'self':
 | 
			
		||||
                return !$page->root() ? (new Collection())->addPage($page) : new Collection();
 | 
			
		||||
                $collection = !$page->root() ? (new Collection())->addPage($page) : new Collection();
 | 
			
		||||
                break;
 | 
			
		||||
            case 'parent':
 | 
			
		||||
                $parent = $page->parent();
 | 
			
		||||
                $collection = new Collection();
 | 
			
		||||
                return $parent ? $collection->addPage($parent) : $collection;
 | 
			
		||||
                $collection = $parent ? $collection->addPage($parent) : $collection;
 | 
			
		||||
                break;
 | 
			
		||||
            case 'siblings':
 | 
			
		||||
                $parent = $page->parent();
 | 
			
		||||
                return $parent ? $parent->children()->remove($page->path()) : new Collection();
 | 
			
		||||
                $collection = $parent ? $parent->children()->remove($page->path()) : new Collection();
 | 
			
		||||
                break;
 | 
			
		||||
            case 'descendants':
 | 
			
		||||
                return $this->all($page)->remove($page->path())->pages();
 | 
			
		||||
                $collection = $this->all($page)->remove($page->path())->pages();
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                // Unknown type; return empty collection.
 | 
			
		||||
                return new Collection();
 | 
			
		||||
                $collection = new Collection();
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $collection;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1761,7 +1823,7 @@ class Pages
 | 
			
		||||
        // Build regular expression for all the allowed page extensions.
 | 
			
		||||
        $page_extensions = $language->getFallbackPageExtensions();
 | 
			
		||||
        $regex = '/^[^\.]*(' . implode('|', array_map(
 | 
			
		||||
            function ($str) {
 | 
			
		||||
            static function ($str) {
 | 
			
		||||
                return preg_quote($str, '/');
 | 
			
		||||
            },
 | 
			
		||||
            $page_extensions
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@
 | 
			
		||||
namespace Grav\Common;
 | 
			
		||||
 | 
			
		||||
use ArrayAccess;
 | 
			
		||||
use Composer\Autoload\ClassLoader;
 | 
			
		||||
use Grav\Common\Data\Blueprint;
 | 
			
		||||
use Grav\Common\Data\Data;
 | 
			
		||||
use Grav\Common\Page\Interfaces\PageInterface;
 | 
			
		||||
@@ -42,6 +43,8 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
 | 
			
		||||
    protected $active = true;
 | 
			
		||||
    /** @var Blueprint|null */
 | 
			
		||||
    protected $blueprint;
 | 
			
		||||
    /** @var ClassLoader|null */
 | 
			
		||||
    protected $loader;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * By default assign all methods as listeners using the default priority.
 | 
			
		||||
@@ -79,6 +82,24 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return ClassLoader|null
 | 
			
		||||
     * @internal
 | 
			
		||||
     */
 | 
			
		||||
    final public function getAutoloader(): ?ClassLoader
 | 
			
		||||
    {
 | 
			
		||||
        return $this->loader;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param ClassLoader|null $loader
 | 
			
		||||
     * @internal
 | 
			
		||||
     */
 | 
			
		||||
    final public function setAutoloader(?ClassLoader $loader): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->loader = $loader;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param Config $config
 | 
			
		||||
     * @return $this
 | 
			
		||||
@@ -206,6 +227,7 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
 | 
			
		||||
     * @param string $offset  An offset to check for.
 | 
			
		||||
     * @return bool          Returns TRUE on success or FALSE on failure.
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetExists($offset)
 | 
			
		||||
    {
 | 
			
		||||
        if ($offset === 'title') {
 | 
			
		||||
@@ -223,6 +245,7 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
 | 
			
		||||
     * @param string $offset  The offset to retrieve.
 | 
			
		||||
     * @return mixed         Can return all value types.
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetGet($offset)
 | 
			
		||||
    {
 | 
			
		||||
        if ($offset === 'title') {
 | 
			
		||||
@@ -241,6 +264,7 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
 | 
			
		||||
     * @param mixed $value   The value to set.
 | 
			
		||||
     * @throws LogicException
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetSet($offset, $value)
 | 
			
		||||
    {
 | 
			
		||||
        throw new LogicException(__CLASS__ . ' blueprints cannot be modified.');
 | 
			
		||||
@@ -252,6 +276,7 @@ class Plugin implements EventSubscriberInterface, ArrayAccess
 | 
			
		||||
     * @param string $offset  The offset to unset.
 | 
			
		||||
     * @throws LogicException
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetUnset($offset)
 | 
			
		||||
    {
 | 
			
		||||
        throw new LogicException(__CLASS__ . ' blueprints cannot be modified.');
 | 
			
		||||
 
 | 
			
		||||
@@ -143,7 +143,7 @@ class Plugins extends Iterator
 | 
			
		||||
                $instance->setConfig($config);
 | 
			
		||||
                // Register autoloader.
 | 
			
		||||
                if (method_exists($instance, 'autoload')) {
 | 
			
		||||
                    $instance->autoload();
 | 
			
		||||
                    $instance->setAutoloader($instance->autoload());
 | 
			
		||||
                }
 | 
			
		||||
                // Register event listeners.
 | 
			
		||||
                $events->addSubscriber($instance);
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ class InitializeProcessor extends ProcessorBase
 | 
			
		||||
    public $title = 'Initialize';
 | 
			
		||||
 | 
			
		||||
    /** @var bool */
 | 
			
		||||
    private static $cli_initialized = false;
 | 
			
		||||
    protected static $cli_initialized = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param Grav $grav
 | 
			
		||||
@@ -105,12 +105,12 @@ class InitializeProcessor extends ProcessorBase
 | 
			
		||||
        // TODO: remove in 2.0.
 | 
			
		||||
        $this->container['accounts'];
 | 
			
		||||
 | 
			
		||||
        // Initialize session (used by URI, see issue #3269).
 | 
			
		||||
        $this->initializeSession($config);
 | 
			
		||||
 | 
			
		||||
        // Initialize URI (uses session, see issue #3269).
 | 
			
		||||
        $this->initializeUri($config);
 | 
			
		||||
 | 
			
		||||
        // Initialize session.
 | 
			
		||||
        $this->initializeSession($config);
 | 
			
		||||
 | 
			
		||||
        // Grav may return redirect response right away.
 | 
			
		||||
        $redirectCode = (int)$config->get('system.pages.redirect_trailing_slash', 1);
 | 
			
		||||
        if ($redirectCode) {
 | 
			
		||||
 
 | 
			
		||||
@@ -42,15 +42,29 @@ class PagesProcessor extends ProcessorBase
 | 
			
		||||
        $this->container['debugger']->addMessage($this->container['cache']->getCacheStatus());
 | 
			
		||||
 | 
			
		||||
        $this->container['pages']->init();
 | 
			
		||||
        $this->container->fireEvent('onPagesInitialized', new Event(['pages' => $this->container['pages']]));
 | 
			
		||||
        $this->container->fireEvent('onPageInitialized', new Event(['page' => $this->container['page']]));
 | 
			
		||||
 | 
			
		||||
        $route = $this->container['route'];
 | 
			
		||||
 | 
			
		||||
        $this->container->fireEvent('onPagesInitialized', new Event(
 | 
			
		||||
            [
 | 
			
		||||
                'pages' => $this->container['pages'],
 | 
			
		||||
                'route' => $route,
 | 
			
		||||
                'request' => $request
 | 
			
		||||
            ]
 | 
			
		||||
        ));
 | 
			
		||||
        $this->container->fireEvent('onPageInitialized', new Event(
 | 
			
		||||
            [
 | 
			
		||||
                'page' => $this->container['page'],
 | 
			
		||||
                'route' => $route,
 | 
			
		||||
                'request' => $request
 | 
			
		||||
            ]
 | 
			
		||||
        ));
 | 
			
		||||
 | 
			
		||||
        /** @var PageInterface $page */
 | 
			
		||||
        $page = $this->container['page'];
 | 
			
		||||
 | 
			
		||||
        if (!$page->routable()) {
 | 
			
		||||
            $exception = new RequestException($request, 'Page Not Found', 404);
 | 
			
		||||
            $route = $this->container['route'];
 | 
			
		||||
            // If no page found, fire event
 | 
			
		||||
            $event = new Event([
 | 
			
		||||
                'page' => $page,
 | 
			
		||||
 
 | 
			
		||||
@@ -271,7 +271,7 @@ class Job
 | 
			
		||||
        if ($whenOverlapping) {
 | 
			
		||||
            $this->whenOverlapping = $whenOverlapping;
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->whenOverlapping = function () {
 | 
			
		||||
            $this->whenOverlapping = static function () {
 | 
			
		||||
                return false;
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,11 @@
 | 
			
		||||
 | 
			
		||||
namespace Grav\Common;
 | 
			
		||||
 | 
			
		||||
use enshrined\svgSanitize\Sanitizer;
 | 
			
		||||
use Exception;
 | 
			
		||||
use Grav\Common\Config\Config;
 | 
			
		||||
use Grav\Common\Filesystem\Folder;
 | 
			
		||||
use Grav\Common\Page\Pages;
 | 
			
		||||
use Rhukster\DomSanitizer\DOMSanitizer;
 | 
			
		||||
use function chr;
 | 
			
		||||
use function count;
 | 
			
		||||
use function is_array;
 | 
			
		||||
@@ -34,7 +34,7 @@ class Security
 | 
			
		||||
    public static function sanitizeSvgString(string $svg): string
 | 
			
		||||
    {
 | 
			
		||||
        if (Grav::instance()['config']->get('security.sanitize_svg')) {
 | 
			
		||||
            $sanitizer = new Sanitizer();
 | 
			
		||||
            $sanitizer = new DOMSanitizer(DOMSanitizer::SVG);
 | 
			
		||||
            $sanitized = $sanitizer->sanitize($svg);
 | 
			
		||||
            if (is_string($sanitized)) {
 | 
			
		||||
                $svg = $sanitized;
 | 
			
		||||
@@ -53,7 +53,7 @@ class Security
 | 
			
		||||
    public static function sanitizeSVG(string $file): void
 | 
			
		||||
    {
 | 
			
		||||
        if (file_exists($file) && Grav::instance()['config']->get('security.sanitize_svg')) {
 | 
			
		||||
            $sanitizer = new Sanitizer();
 | 
			
		||||
            $sanitizer = new DOMSanitizer(DOMSanitizer::SVG);
 | 
			
		||||
            $original_svg = file_get_contents($file);
 | 
			
		||||
            $clean_svg = $sanitizer->sanitize($original_svg);
 | 
			
		||||
 | 
			
		||||
@@ -107,7 +107,7 @@ class Security
 | 
			
		||||
                $content = $page->value('content');
 | 
			
		||||
 | 
			
		||||
                $data = ['header' => $header, 'content' => $content];
 | 
			
		||||
                $results = Security::detectXssFromArray($data);
 | 
			
		||||
                $results = static::detectXssFromArray($data);
 | 
			
		||||
 | 
			
		||||
                if (!empty($results)) {
 | 
			
		||||
                    if ($route) {
 | 
			
		||||
@@ -138,7 +138,7 @@ class Security
 | 
			
		||||
            $options = static::getXssDefaults();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $list = [];
 | 
			
		||||
        $list = [[]];
 | 
			
		||||
        foreach ($array as $key => $value) {
 | 
			
		||||
            if (is_array($value)) {
 | 
			
		||||
                $list[] = static::detectXssFromArray($value, $prefix . $key . '.', $options);
 | 
			
		||||
@@ -148,11 +148,7 @@ class Security
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!empty($list)) {
 | 
			
		||||
            return array_merge(...$list);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $list;
 | 
			
		||||
        return array_merge(...$list);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -199,7 +195,7 @@ class Security
 | 
			
		||||
        $string = urldecode($string);
 | 
			
		||||
 | 
			
		||||
        // Convert Hexadecimals
 | 
			
		||||
        $string = (string)preg_replace_callback('!(&#|\\\)[xX]([0-9a-fA-F]+);?!u', function ($m) {
 | 
			
		||||
        $string = (string)preg_replace_callback('!(&#|\\\)[xX]([0-9a-fA-F]+);?!u', static function ($m) {
 | 
			
		||||
            return chr(hexdec($m[2]));
 | 
			
		||||
        }, $string);
 | 
			
		||||
 | 
			
		||||
@@ -207,7 +203,7 @@ class Security
 | 
			
		||||
        $string = preg_replace('!(�+[0-9]+)!u', '$1;', $string);
 | 
			
		||||
 | 
			
		||||
        // Decode entities
 | 
			
		||||
        $string = html_entity_decode($string, ENT_NOQUOTES, 'UTF-8');
 | 
			
		||||
        $string = html_entity_decode($string, ENT_NOQUOTES | ENT_HTML5, 'UTF-8');
 | 
			
		||||
 | 
			
		||||
        // Strip whitespace characters
 | 
			
		||||
        $string = preg_replace('!\s!u', '', $string);
 | 
			
		||||
@@ -239,7 +235,7 @@ class Security
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function getXssDefaults(): array
 | 
			
		||||
 
 | 
			
		||||
@@ -179,7 +179,7 @@ class ConfigServiceProvider implements ServiceProviderInterface
 | 
			
		||||
     * @param string $folder_path
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    private static function pluginFolderPaths($plugins, $folder_path)
 | 
			
		||||
    protected static function pluginFolderPaths($plugins, $folder_path)
 | 
			
		||||
    {
 | 
			
		||||
        $paths = [];
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -129,12 +129,12 @@ class Session extends \Grav\Framework\Session\Session
 | 
			
		||||
                /** @var Uri $uri */
 | 
			
		||||
                $uri = $grav['uri'];
 | 
			
		||||
                /** @var Forms|null $form */
 | 
			
		||||
                $form = $grav['forms']->getActiveForm(); // @phpstan-ignore-line
 | 
			
		||||
                $form = $grav['forms']->getActiveForm(); // @phpstan-ignore-line (form plugin)
 | 
			
		||||
 | 
			
		||||
                $sessionField = base64_encode($uri->url);
 | 
			
		||||
 | 
			
		||||
                /** @var FormFlash|null $flash */
 | 
			
		||||
                $flash = $form ? $form->getFlash() : null; // @phpstan-ignore-line
 | 
			
		||||
                $flash = $form ? $form->getFlash() : null; // @phpstan-ignore-line (form plugin)
 | 
			
		||||
                $object = $flash && method_exists($flash, 'getLegacyFiles') ? [$sessionField => $flash->getLegacyFiles()] : null;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,7 @@ class Taxonomy
 | 
			
		||||
            }
 | 
			
		||||
        } elseif (is_string($value)) {
 | 
			
		||||
            if (!empty($key)) {
 | 
			
		||||
                $taxonomy = $taxonomy . $key;
 | 
			
		||||
                $taxonomy .= $key;
 | 
			
		||||
            }
 | 
			
		||||
            $this->taxonomy_map[$taxonomy][(string) $value][$page->path()] = ['slug' => $page->slug()];
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -90,7 +90,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
     *
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function getGlobals()
 | 
			
		||||
    public function getGlobals(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            'grav' => $this->grav,
 | 
			
		||||
@@ -102,7 +102,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
     *
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function getFilters()
 | 
			
		||||
    public function getFilters(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            new TwigFilter('*ize', [$this, 'inflectorFilter']),
 | 
			
		||||
@@ -172,7 +172,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
     *
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function getFunctions()
 | 
			
		||||
    public function getFunctions(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            new TwigFunction('array', [$this, 'arrayFilter']),
 | 
			
		||||
@@ -217,6 +217,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
            new TwigFunction('cron', [$this, 'cronFunc']),
 | 
			
		||||
            new TwigFunction('svg_image', [$this, 'svgImageFunction']),
 | 
			
		||||
            new TwigFunction('xss', [$this, 'xssFunc']),
 | 
			
		||||
            new TwigFunction('unique_id', [$this, 'uniqueId']),
 | 
			
		||||
 | 
			
		||||
            // Translations
 | 
			
		||||
            new TwigFunction('t', [$this, 'translate'], ['needs_environment' => true]),
 | 
			
		||||
@@ -243,7 +244,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function getTokenParsers()
 | 
			
		||||
    public function getTokenParsers(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            new TwigTokenParserRender(),
 | 
			
		||||
@@ -654,6 +655,20 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
        return implode(', ', $results_parts);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates a random string with configurable length, prefix and suffix.
 | 
			
		||||
     * Unlike the built-in `uniqid()`, this string is non-conflicting and safe
 | 
			
		||||
     *
 | 
			
		||||
     * @param int $length
 | 
			
		||||
     * @param array $options
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @throws \Exception
 | 
			
		||||
     */
 | 
			
		||||
    public function uniqueId(int $length = 9, array $options = ['prefix' => '', 'suffix' => '']): string
 | 
			
		||||
    {
 | 
			
		||||
        return Utils::uniqueId($length, $options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $string
 | 
			
		||||
     * @return string
 | 
			
		||||
@@ -823,12 +838,8 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
     * @param Environment $twig
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function translate(Environment $twig)
 | 
			
		||||
    public function translate(Environment $twig, ...$args)
 | 
			
		||||
    {
 | 
			
		||||
        // shift off the environment
 | 
			
		||||
        $args = func_get_args();
 | 
			
		||||
        array_shift($args);
 | 
			
		||||
 | 
			
		||||
        // If admin and tu filter provided, use it
 | 
			
		||||
        if (isset($this->grav['admin'])) {
 | 
			
		||||
            $numargs = count($args);
 | 
			
		||||
@@ -836,6 +847,12 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
 | 
			
		||||
            if (($numargs === 3 && is_array($args[1])) || ($numargs === 2 && !is_array($args[1]))) {
 | 
			
		||||
                $lang = array_pop($args);
 | 
			
		||||
                /** @var Language $language */
 | 
			
		||||
                $language = $this->grav['language'];
 | 
			
		||||
                if (is_string($lang) && !$language->getLanguageCode($lang)) {
 | 
			
		||||
                    $args[] = $lang;
 | 
			
		||||
                    $lang = null;
 | 
			
		||||
                }
 | 
			
		||||
            } elseif ($numargs === 2 && is_array($args[1])) {
 | 
			
		||||
                $subs = array_pop($args);
 | 
			
		||||
                $args = array_merge($args, $subs);
 | 
			
		||||
@@ -1343,7 +1360,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
     */
 | 
			
		||||
    public function vardumpFunc($var)
 | 
			
		||||
    {
 | 
			
		||||
        var_dump($var);
 | 
			
		||||
        dump($var);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1407,7 +1424,7 @@ class GravExtension extends AbstractExtension implements GlobalsInterface
 | 
			
		||||
     * @param array $context      Twig Context
 | 
			
		||||
     * @param string $var variable to be found (using dot notation)
 | 
			
		||||
     * @param null $default the default value to be used as last resort
 | 
			
		||||
     * @param null $page an optional page to use for the current page
 | 
			
		||||
     * @param PageInterface|null $page an optional page to use for the current page
 | 
			
		||||
     * @param bool $exists toggle to simply return the page where the variable is set, else null
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -22,9 +22,9 @@ use Grav\Common\Twig\Extension\GravExtension;
 | 
			
		||||
use Grav\Common\Utils;
 | 
			
		||||
use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
 | 
			
		||||
use RocketTheme\Toolbox\Event\Event;
 | 
			
		||||
use Phive\Twig\Extensions\Deferred\DeferredExtension;
 | 
			
		||||
use RuntimeException;
 | 
			
		||||
use Twig\Cache\FilesystemCache;
 | 
			
		||||
use Twig\DeferredExtension\DeferredExtension;
 | 
			
		||||
use Twig\Environment;
 | 
			
		||||
use Twig\Error\LoaderError;
 | 
			
		||||
use Twig\Error\RuntimeError;
 | 
			
		||||
@@ -33,7 +33,6 @@ use Twig\Extension\DebugExtension;
 | 
			
		||||
use Twig\Extension\StringLoaderExtension;
 | 
			
		||||
use Twig\Loader\ArrayLoader;
 | 
			
		||||
use Twig\Loader\ChainLoader;
 | 
			
		||||
use Twig\Loader\ExistsLoaderInterface;
 | 
			
		||||
use Twig\Loader\FilesystemLoader;
 | 
			
		||||
use Twig\Profiler\Profile;
 | 
			
		||||
use Twig\TwigFilter;
 | 
			
		||||
@@ -515,25 +514,21 @@ class Twig
 | 
			
		||||
        $twig_extension = $extension ? '.'. $extension .TWIG_EXT : TEMPLATE_EXT;
 | 
			
		||||
        $template_file = $this->template($page->template() . $twig_extension);
 | 
			
		||||
 | 
			
		||||
        $page_template = null;
 | 
			
		||||
 | 
			
		||||
        $loader = $this->twig->getLoader();
 | 
			
		||||
        if ($loader instanceof ExistsLoaderInterface) {
 | 
			
		||||
            if ($loader->exists($template_file)) {
 | 
			
		||||
                // template.xxx.twig
 | 
			
		||||
                $page_template = $template_file;
 | 
			
		||||
            } elseif ($twig_extension !== TEMPLATE_EXT && $loader->exists($template . TEMPLATE_EXT)) {
 | 
			
		||||
                // template.html.twig
 | 
			
		||||
                $page_template = $template . TEMPLATE_EXT;
 | 
			
		||||
                $format = 'html';
 | 
			
		||||
            } elseif ($loader->exists($default . $twig_extension)) {
 | 
			
		||||
                // default.xxx.twig
 | 
			
		||||
                $page_template = $default . $twig_extension;
 | 
			
		||||
            } else {
 | 
			
		||||
                // default.html.twig
 | 
			
		||||
                $page_template = $default . TEMPLATE_EXT;
 | 
			
		||||
                $format = 'html';
 | 
			
		||||
            }
 | 
			
		||||
        if ($loader->exists($template_file)) {
 | 
			
		||||
            // template.xxx.twig
 | 
			
		||||
            $page_template = $template_file;
 | 
			
		||||
        } elseif ($twig_extension !== TEMPLATE_EXT && $loader->exists($template . TEMPLATE_EXT)) {
 | 
			
		||||
            // template.html.twig
 | 
			
		||||
            $page_template = $template . TEMPLATE_EXT;
 | 
			
		||||
            $format = 'html';
 | 
			
		||||
        } elseif ($loader->exists($default . $twig_extension)) {
 | 
			
		||||
            // default.xxx.twig
 | 
			
		||||
            $page_template = $default . $twig_extension;
 | 
			
		||||
        } else {
 | 
			
		||||
            // default.html.twig
 | 
			
		||||
            $page_template = $default . TEMPLATE_EXT;
 | 
			
		||||
            $format = 'html';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $page_template;
 | 
			
		||||
 
 | 
			
		||||
@@ -160,8 +160,8 @@ class Uri
 | 
			
		||||
        $language = $grav['language'];
 | 
			
		||||
 | 
			
		||||
        // add the port to the base for non-standard ports
 | 
			
		||||
        if ($this->port !== null && $config->get('system.reverse_proxy_setup') === false) {
 | 
			
		||||
            $this->base .= ':' . (string)$this->port;
 | 
			
		||||
        if ($this->port && $config->get('system.reverse_proxy_setup') === false) {
 | 
			
		||||
            $this->base .= ':' . $this->port;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Handle custom base
 | 
			
		||||
@@ -176,8 +176,8 @@ class Uri
 | 
			
		||||
            if (isset($custom_parts['scheme'])) {
 | 
			
		||||
                $this->base = $custom_parts['scheme'] . '://' . $custom_parts['host'];
 | 
			
		||||
                $this->port = $custom_parts['port'] ?? null;
 | 
			
		||||
                if ($this->port !== null && $config->get('system.reverse_proxy_setup') === false) {
 | 
			
		||||
                    $this->base .= ':' . (string)$this->port;
 | 
			
		||||
                if ($this->port && $config->get('system.reverse_proxy_setup') === false) {
 | 
			
		||||
                    $this->base .= ':' . $this->port;
 | 
			
		||||
                }
 | 
			
		||||
                $this->root = $custom_base;
 | 
			
		||||
            } else {
 | 
			
		||||
@@ -462,8 +462,8 @@ class Uri
 | 
			
		||||
    public function port($raw = false)
 | 
			
		||||
    {
 | 
			
		||||
        $port = $this->port;
 | 
			
		||||
        // If not in raw mode and port is not set, figure it out from scheme.
 | 
			
		||||
        if (!$raw && $port === null) {
 | 
			
		||||
        // If not in raw mode and port is not set or is 0, figure it out from scheme.
 | 
			
		||||
        if (!$raw && !$port) {
 | 
			
		||||
            if ($this->scheme === 'http') {
 | 
			
		||||
                $this->port = 80;
 | 
			
		||||
            } elseif ($this->scheme === 'https') {
 | 
			
		||||
@@ -471,7 +471,7 @@ class Uri
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $this->port;
 | 
			
		||||
        return $this->port ?: null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -586,33 +586,38 @@ class Uri
 | 
			
		||||
    /**
 | 
			
		||||
     * Return relative path to the referrer defaulting to current or given page.
 | 
			
		||||
     *
 | 
			
		||||
     * You should set the third parameter to `true` for redirects as long as you came from the same sub-site and language.
 | 
			
		||||
     *
 | 
			
		||||
     * @param string|null $default
 | 
			
		||||
     * @param string|null $attributes
 | 
			
		||||
     * @param bool $withoutBaseRoute
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
    public function referrer($default = null, $attributes = null)
 | 
			
		||||
    public function referrer($default = null, $attributes = null, bool $withoutBaseRoute = false)
 | 
			
		||||
    {
 | 
			
		||||
        $referrer = $_SERVER['HTTP_REFERER'] ?? null;
 | 
			
		||||
 | 
			
		||||
        // Check that referrer came from our site.
 | 
			
		||||
        $root = $this->rootUrl(true);
 | 
			
		||||
        if ($referrer) {
 | 
			
		||||
            // Referrer should always have host set and it should come from the same base address.
 | 
			
		||||
            if (stripos($referrer, $root) !== 0) {
 | 
			
		||||
                $referrer = null;
 | 
			
		||||
            }
 | 
			
		||||
        if ($withoutBaseRoute) {
 | 
			
		||||
            /** @var Pages $pages */
 | 
			
		||||
            $pages = Grav::instance()['pages'];
 | 
			
		||||
            $base = $pages->baseUrl(null, true);
 | 
			
		||||
        } else {
 | 
			
		||||
            $base = $this->rootUrl(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$referrer) {
 | 
			
		||||
        // Referrer should always have host set and it should come from the same base address.
 | 
			
		||||
        if (!is_string($referrer) || !str_starts_with($referrer, $base)) {
 | 
			
		||||
            $referrer = $default ?: $this->route(true, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Relative path from grav root.
 | 
			
		||||
        $referrer = substr($referrer, strlen($base));
 | 
			
		||||
        if ($attributes) {
 | 
			
		||||
            $referrer .= $attributes;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Return relative path.
 | 
			
		||||
        return substr($referrer, strlen($root));
 | 
			
		||||
        return $referrer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -648,7 +653,7 @@ class Uri
 | 
			
		||||
        return [
 | 
			
		||||
            'scheme'    => $this->scheme,
 | 
			
		||||
            'host'      => $this->host,
 | 
			
		||||
            'port'      => $this->port,
 | 
			
		||||
            'port'      => $this->port ?: null,
 | 
			
		||||
            'user'      => $this->user,
 | 
			
		||||
            'pass'      => $this->password,
 | 
			
		||||
            'path'      => $path,
 | 
			
		||||
@@ -1146,11 +1151,8 @@ class Uri
 | 
			
		||||
    public static function isValidUrl($url)
 | 
			
		||||
    {
 | 
			
		||||
        $regex = '/^(?:(https?|ftp|telnet):)?\/\/((?:[a-z0-9@:.-]|%[0-9A-F]{2}){3,})(?::(\d+))?((?:\/(?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\@]|%[0-9A-F]{2})*)*)(?:\?((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?(?:#((?:[a-z0-9-._~!$&\'\(\)\*\+\,\;\=\:\/?@]|%[0-9A-F]{2})*))?/';
 | 
			
		||||
        if (preg_match($regex, $url)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
        return (bool)preg_match($regex, $url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1301,7 +1303,7 @@ class Uri
 | 
			
		||||
     */
 | 
			
		||||
    protected function hasStandardPort()
 | 
			
		||||
    {
 | 
			
		||||
        return ($this->port === 80 || $this->port === 443);
 | 
			
		||||
        return (!$this->port || $this->port === 80 || $this->port === 443);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,7 @@ class User extends Data implements UserInterface
 | 
			
		||||
     * @param string $offset
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetExists($offset)
 | 
			
		||||
    {
 | 
			
		||||
        $value = parent::offsetExists($offset);
 | 
			
		||||
@@ -73,6 +74,7 @@ class User extends Data implements UserInterface
 | 
			
		||||
     * @param string $offset
 | 
			
		||||
     * @return mixed
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetGet($offset)
 | 
			
		||||
    {
 | 
			
		||||
        $value = parent::offsetGet($offset);
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ class Group extends Data
 | 
			
		||||
     * @return array
 | 
			
		||||
     * @deprecated 1.7, use $grav['user_groups'] Flex UserGroupCollection instead
 | 
			
		||||
     */
 | 
			
		||||
    private static function groups()
 | 
			
		||||
    protected static function groups()
 | 
			
		||||
    {
 | 
			
		||||
        user_error(__METHOD__ . '() is deprecated since Grav 1.7, use $grav[\'user_groups\'] Flex UserGroupCollection instead', E_USER_DEPRECATED);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ use Grav\Common\Page\Markdown\Excerpts;
 | 
			
		||||
use Grav\Common\Page\Pages;
 | 
			
		||||
use Grav\Framework\Flex\Flex;
 | 
			
		||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
 | 
			
		||||
use Grav\Framework\Media\Interfaces\MediaInterface;
 | 
			
		||||
use InvalidArgumentException;
 | 
			
		||||
use Negotiation\Accept;
 | 
			
		||||
use Negotiation\Negotiator;
 | 
			
		||||
@@ -150,7 +151,7 @@ abstract class Utils
 | 
			
		||||
 | 
			
		||||
        $domain = $domain ?: $grav['config']->get('system.absolute_urls', false);
 | 
			
		||||
 | 
			
		||||
        return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
 | 
			
		||||
        return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?: '');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -166,9 +167,9 @@ abstract class Utils
 | 
			
		||||
 | 
			
		||||
        if ($locator->isStream($path)) {
 | 
			
		||||
            $path = $locator->findResource($path, true);
 | 
			
		||||
        } elseif (!Utils::startsWith($path, GRAV_ROOT)) {
 | 
			
		||||
        } elseif (!static::startsWith($path, GRAV_ROOT)) {
 | 
			
		||||
            $base_url = Grav::instance()['base_url'];
 | 
			
		||||
            $path = GRAV_ROOT . '/' . ltrim(Utils::replaceFirstOccurrence($base_url, '', $path), '/');
 | 
			
		||||
            $path = GRAV_ROOT . '/' . ltrim(static::replaceFirstOccurrence($base_url, '', $path), '/');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $path;
 | 
			
		||||
@@ -628,6 +629,23 @@ abstract class Utils
 | 
			
		||||
        return substr(str_shuffle('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'), 0, $length);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates a random string with configurable length, prefix and suffix.
 | 
			
		||||
     * Unlike the built-in `uniqid()`, this string is non-conflicting and safe
 | 
			
		||||
     *
 | 
			
		||||
     * @param int $length
 | 
			
		||||
     * @param array $options
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
    public static function uniqueId(int $length = 13, array $options = []): string
 | 
			
		||||
    {
 | 
			
		||||
        $options = array_merge(['prefix' => '', 'suffix' => ''], $options);
 | 
			
		||||
        $bytes = random_bytes(ceil($length / 2));
 | 
			
		||||
 | 
			
		||||
        return $options['prefix'] . substr(bin2hex($bytes), 0, $length) . $options['suffix'];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Provides the ability to download a file to the browser
 | 
			
		||||
     *
 | 
			
		||||
@@ -750,13 +768,13 @@ abstract class Utils
 | 
			
		||||
        if (is_string($http_accept)) {
 | 
			
		||||
            $negotiator = new Negotiator();
 | 
			
		||||
 | 
			
		||||
            $supported_types = Utils::getSupportPageTypes(['html', 'json']);
 | 
			
		||||
            $priorities = Utils::getMimeTypes($supported_types);
 | 
			
		||||
            $supported_types = static::getSupportPageTypes(['html', 'json']);
 | 
			
		||||
            $priorities = static::getMimeTypes($supported_types);
 | 
			
		||||
 | 
			
		||||
            $media_type = $negotiator->getBest($http_accept, $priorities);
 | 
			
		||||
            $mimetype = $media_type instanceof Accept ? $media_type->getValue() : '';
 | 
			
		||||
 | 
			
		||||
            return Utils::getExtensionByMime($mimetype);
 | 
			
		||||
            return static::getExtensionByMime($mimetype);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return 'html';
 | 
			
		||||
@@ -791,13 +809,7 @@ abstract class Utils
 | 
			
		||||
 | 
			
		||||
        $media_types = Grav::instance()['config']->get('media.types');
 | 
			
		||||
 | 
			
		||||
        if (isset($media_types[$extension])) {
 | 
			
		||||
            if (isset($media_types[$extension]['mime'])) {
 | 
			
		||||
                return $media_types[$extension]['mime'];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $default;
 | 
			
		||||
        return $media_types[$extension]['mime'] ?? $default;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -1555,7 +1567,7 @@ abstract class Utils
 | 
			
		||||
 | 
			
		||||
        switch ($matches[0]) {
 | 
			
		||||
            case 'self':
 | 
			
		||||
                if (null === $object) {
 | 
			
		||||
                if (!$object instanceof MediaInterface) {
 | 
			
		||||
                    throw new RuntimeException(sprintf('Page not available for self@ reference: %s', $path));
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@@ -1629,7 +1641,7 @@ abstract class Utils
 | 
			
		||||
     * @param string $path
 | 
			
		||||
     * @return string[]|null
 | 
			
		||||
     */
 | 
			
		||||
    private static function resolveTokenPath(string $path): ?array
 | 
			
		||||
    protected static function resolveTokenPath(string $path): ?array
 | 
			
		||||
    {
 | 
			
		||||
        if (strpos($path, '@') !== false) {
 | 
			
		||||
            $regex = '/^(@\w+|\w+@|@\w+@)([^:]*)(.*)$/u';
 | 
			
		||||
@@ -1739,7 +1751,7 @@ abstract class Utils
 | 
			
		||||
    {
 | 
			
		||||
        $enc_url = preg_replace_callback(
 | 
			
		||||
            '%[^:/@?&=#]+%usD',
 | 
			
		||||
            function ($matches) {
 | 
			
		||||
            static function ($matches) {
 | 
			
		||||
                return urlencode($matches[0]);
 | 
			
		||||
            },
 | 
			
		||||
            $url
 | 
			
		||||
@@ -1763,7 +1775,7 @@ abstract class Utils
 | 
			
		||||
     *
 | 
			
		||||
     * @param string $string
 | 
			
		||||
     * @param bool $block Block or Line processing
 | 
			
		||||
     * @param null $page
 | 
			
		||||
     * @param PageInterface|null $page
 | 
			
		||||
     * @return string
 | 
			
		||||
     * @throws Exception
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,8 @@ use Grav\Framework\File\Formatter\YamlFormatter;
 | 
			
		||||
 */
 | 
			
		||||
abstract class Yaml
 | 
			
		||||
{
 | 
			
		||||
    /** @var YamlFormatter */
 | 
			
		||||
    private static $yaml;
 | 
			
		||||
    /** @var YamlFormatter|null */
 | 
			
		||||
    protected static $yaml;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $data
 | 
			
		||||
@@ -51,7 +51,7 @@ abstract class Yaml
 | 
			
		||||
    /**
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    private static function init()
 | 
			
		||||
    protected static function init()
 | 
			
		||||
    {
 | 
			
		||||
        $config = [
 | 
			
		||||
            'inline' => 5,
 | 
			
		||||
 
 | 
			
		||||
@@ -102,11 +102,10 @@ class CleanCommand extends Command
 | 
			
		||||
        'vendor/dragonmantank/cron-expression/composer.json',
 | 
			
		||||
        'vendor/dragonmantank/cron-expression/tests',
 | 
			
		||||
        'vendor/dragonmantank/cron-expression/CHANGELOG.md',
 | 
			
		||||
        'vendor/enshrined/svg-sanitize/tests',
 | 
			
		||||
        'vendor/enshrined/svg-sanitize/.gitignore',
 | 
			
		||||
        'vendor/enshrined/svg-sanitize/.travis.yml',
 | 
			
		||||
        'vendor/enshrined/svg-sanitize/composer.json',
 | 
			
		||||
        'vendor/enshrined/svg-sanitize/phpunit.xml',
 | 
			
		||||
        'vendor/rhukster/dom-sanitizer/tests',
 | 
			
		||||
        'vendor/rhukster/dom-sanitizer/.gitignore',
 | 
			
		||||
        'vendor/rhukster/dom-sanitizer/composer.json',
 | 
			
		||||
        'vendor/rhukster/dom-sanitizer/composer.lock',
 | 
			
		||||
        'vendor/erusev/parsedown/composer.json',
 | 
			
		||||
        'vendor/erusev/parsedown/phpunit.xml.dist',
 | 
			
		||||
        'vendor/erusev/parsedown/.travis.yml',
 | 
			
		||||
 
 | 
			
		||||
@@ -200,7 +200,6 @@ class IndexCommand extends GpmCommand
 | 
			
		||||
     */
 | 
			
		||||
    private function installed(Package $package): string
 | 
			
		||||
    {
 | 
			
		||||
        $package   = $list[$package->slug] ?? $package;
 | 
			
		||||
        $type      = ucfirst(preg_replace('/s$/', '', $package->package_type));
 | 
			
		||||
        $method = 'is' . $type . 'Installed';
 | 
			
		||||
        $installed = $this->gpm->{$method}($package->slug);
 | 
			
		||||
@@ -214,7 +213,6 @@ class IndexCommand extends GpmCommand
 | 
			
		||||
     */
 | 
			
		||||
    private function enabled(Package $package): string
 | 
			
		||||
    {
 | 
			
		||||
        $package   = $list[$package->slug] ?? $package;
 | 
			
		||||
        $type      = ucfirst(preg_replace('/s$/', '', $package->package_type));
 | 
			
		||||
        $method = 'is' . $type . 'Installed';
 | 
			
		||||
        $installed = $this->gpm->{$method}($package->slug);
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ use function is_array;
 | 
			
		||||
class PermissionsReader
 | 
			
		||||
{
 | 
			
		||||
    /** @var array */
 | 
			
		||||
    private static $types;
 | 
			
		||||
    protected static $types;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $filename
 | 
			
		||||
@@ -131,7 +131,7 @@ class PermissionsReader
 | 
			
		||||
     */
 | 
			
		||||
    protected static function getDependencies(array $dependencies): array
 | 
			
		||||
    {
 | 
			
		||||
        $list = [];
 | 
			
		||||
        $list = [[]];
 | 
			
		||||
        foreach ($dependencies as $name => $deps) {
 | 
			
		||||
            $current = $deps ? static::getDependencies($deps) : [];
 | 
			
		||||
            $current[] = $name;
 | 
			
		||||
 
 | 
			
		||||
@@ -24,10 +24,10 @@ use function array_slice;
 | 
			
		||||
 * Collection of objects stored into a filesystem.
 | 
			
		||||
 *
 | 
			
		||||
 * @package Grav\Framework\Collection
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @template TKey of array-key
 | 
			
		||||
 * @template T of object
 | 
			
		||||
 * @extends AbstractLazyCollection<TKey,T>
 | 
			
		||||
 * @mplements FileCollectionInterface<TKey,T>
 | 
			
		||||
 * @implements FileCollectionInterface<TKey,T>
 | 
			
		||||
 */
 | 
			
		||||
class AbstractFileCollection extends AbstractLazyCollection implements FileCollectionInterface
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,9 @@ use function count;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract Index Collection.
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template TKey of array-key
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @template C of CollectionInterface
 | 
			
		||||
 * @implements CollectionInterface<TKey,T>
 | 
			
		||||
 */
 | 
			
		||||
abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
@@ -144,6 +145,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     *
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetExists($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->containsKey($offset);
 | 
			
		||||
@@ -154,6 +156,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     *
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetGet($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->get($offset);
 | 
			
		||||
@@ -164,6 +167,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     *
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetSet($offset, $value)
 | 
			
		||||
    {
 | 
			
		||||
        if (null === $offset) {
 | 
			
		||||
@@ -178,9 +182,10 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     *
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetUnset($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->remove($offset);
 | 
			
		||||
        $this->remove($offset);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -361,6 +366,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     * @param int $start
 | 
			
		||||
     * @param int|null $limit
 | 
			
		||||
     * @return static
 | 
			
		||||
     * @phpstan-return static<TKey,T,C>
 | 
			
		||||
     */
 | 
			
		||||
    public function limit($start, $limit = null)
 | 
			
		||||
    {
 | 
			
		||||
@@ -371,6 +377,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     * Reverse the order of the items.
 | 
			
		||||
     *
 | 
			
		||||
     * @return static
 | 
			
		||||
     * @phpstan-return static<TKey,T,C>
 | 
			
		||||
     */
 | 
			
		||||
    public function reverse()
 | 
			
		||||
    {
 | 
			
		||||
@@ -381,6 +388,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     * Shuffle items.
 | 
			
		||||
     *
 | 
			
		||||
     * @return static
 | 
			
		||||
     * @phpstan-return static<TKey,T,C>
 | 
			
		||||
     */
 | 
			
		||||
    public function shuffle()
 | 
			
		||||
    {
 | 
			
		||||
@@ -397,6 +405,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $keys
 | 
			
		||||
     * @return static
 | 
			
		||||
     * @phpstan-return static<TKey,T,C>
 | 
			
		||||
     */
 | 
			
		||||
    public function select(array $keys)
 | 
			
		||||
    {
 | 
			
		||||
@@ -415,6 +424,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $keys
 | 
			
		||||
     * @return static
 | 
			
		||||
     * @phpstan-return static<TKey,T,C>
 | 
			
		||||
     */
 | 
			
		||||
    public function unselect(array $keys)
 | 
			
		||||
    {
 | 
			
		||||
@@ -469,6 +479,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
     *
 | 
			
		||||
     * @param array $entries Elements.
 | 
			
		||||
     * @return static
 | 
			
		||||
     * @phpstan-return static<TKey,T,C>
 | 
			
		||||
     */
 | 
			
		||||
    protected function createFrom(array $entries)
 | 
			
		||||
    {
 | 
			
		||||
@@ -521,6 +532,7 @@ abstract class AbstractIndexCollection implements CollectionInterface
 | 
			
		||||
    /**
 | 
			
		||||
     * @param array|null $entries
 | 
			
		||||
     * @return CollectionInterface
 | 
			
		||||
     * @phpstan-return C
 | 
			
		||||
     */
 | 
			
		||||
    abstract protected function loadCollection(array $entries = null): CollectionInterface;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ use Doctrine\Common\Collections\AbstractLazyCollection as BaseAbstractLazyCollec
 | 
			
		||||
 * General JSON serializable collection.
 | 
			
		||||
 *
 | 
			
		||||
 * @package Grav\Framework\Collection
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template TKey of array-key
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @extends BaseAbstractLazyCollection<TKey,T>
 | 
			
		||||
 * @implements CollectionInterface<TKey,T>
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ use Doctrine\Common\Collections\ArrayCollection as BaseArrayCollection;
 | 
			
		||||
 * General JSON serializable collection.
 | 
			
		||||
 *
 | 
			
		||||
 * @package Grav\Framework\Collection
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template TKey of array-key
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @extends BaseArrayCollection<TKey,T>
 | 
			
		||||
 * @implements CollectionInterface<TKey,T>
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ use JsonSerializable;
 | 
			
		||||
 * Collection Interface.
 | 
			
		||||
 *
 | 
			
		||||
 * @package Grav\Framework\Collection
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template TKey of array-key
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @extends Collection<TKey,T>
 | 
			
		||||
 */
 | 
			
		||||
@@ -26,7 +26,7 @@ interface CollectionInterface extends Collection, JsonSerializable
 | 
			
		||||
     * Reverse the order of the items.
 | 
			
		||||
     *
 | 
			
		||||
     * @return CollectionInterface
 | 
			
		||||
     * @phpstan-return CollectionInterface<TKey,T>
 | 
			
		||||
     * @phpstan-return static<TKey,T>
 | 
			
		||||
     */
 | 
			
		||||
    public function reverse();
 | 
			
		||||
 | 
			
		||||
@@ -34,7 +34,7 @@ interface CollectionInterface extends Collection, JsonSerializable
 | 
			
		||||
     * Shuffle items.
 | 
			
		||||
     *
 | 
			
		||||
     * @return CollectionInterface
 | 
			
		||||
     * @phpstan-return CollectionInterface<TKey,T>
 | 
			
		||||
     * @phpstan-return static<TKey,T>
 | 
			
		||||
     */
 | 
			
		||||
    public function shuffle();
 | 
			
		||||
 | 
			
		||||
@@ -53,7 +53,7 @@ interface CollectionInterface extends Collection, JsonSerializable
 | 
			
		||||
     *
 | 
			
		||||
     * @param array<int|string> $keys
 | 
			
		||||
     * @return CollectionInterface
 | 
			
		||||
     * @phpstan-return CollectionInterface<TKey,T>
 | 
			
		||||
     * @phpstan-return static<TKey,T>
 | 
			
		||||
     */
 | 
			
		||||
    public function select(array $keys);
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +62,7 @@ interface CollectionInterface extends Collection, JsonSerializable
 | 
			
		||||
     *
 | 
			
		||||
     * @param array<int|string> $keys
 | 
			
		||||
     * @return CollectionInterface
 | 
			
		||||
     * @phpstan-return CollectionInterface<TKey,T>
 | 
			
		||||
     * @phpstan-return static<TKey,T>
 | 
			
		||||
     */
 | 
			
		||||
    public function unselect(array $keys);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,13 +9,13 @@
 | 
			
		||||
 | 
			
		||||
namespace Grav\Framework\Collection;
 | 
			
		||||
 | 
			
		||||
use stdClass;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Collection of objects stored into a filesystem.
 | 
			
		||||
 *
 | 
			
		||||
 * @package Grav\Framework\Collection
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @extends AbstractFileCollection<TKey,T>
 | 
			
		||||
 * @extends AbstractFileCollection<array-key,stdClass>
 | 
			
		||||
 */
 | 
			
		||||
class FileCollection extends AbstractFileCollection
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ use Doctrine\Common\Collections\Selectable;
 | 
			
		||||
 * Collection of objects stored into a filesystem.
 | 
			
		||||
 *
 | 
			
		||||
 * @package Grav\Framework\Collection
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template TKey of array-key
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @extends CollectionInterface<TKey,T>
 | 
			
		||||
 * @extends Selectable<TKey,T>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,12 +12,14 @@ declare(strict_types=1);
 | 
			
		||||
namespace Grav\Framework\Controller\Traits;
 | 
			
		||||
 | 
			
		||||
use Grav\Common\Config\Config;
 | 
			
		||||
use Grav\Common\Data\ValidationException;
 | 
			
		||||
use Grav\Common\Debugger;
 | 
			
		||||
use Grav\Common\Grav;
 | 
			
		||||
use Grav\Common\Utils;
 | 
			
		||||
use Grav\Framework\Psr7\Response;
 | 
			
		||||
use Grav\Framework\RequestHandler\Exception\RequestException;
 | 
			
		||||
use Grav\Framework\Route\Route;
 | 
			
		||||
use JsonSerializable;
 | 
			
		||||
use Psr\Http\Message\ResponseInterface;
 | 
			
		||||
use Psr\Http\Message\ServerRequestInterface;
 | 
			
		||||
use Psr\Http\Message\StreamInterface;
 | 
			
		||||
@@ -203,7 +205,14 @@ trait ControllerResponseTrait
 | 
			
		||||
    protected function getErrorJson(Throwable $e): array
 | 
			
		||||
    {
 | 
			
		||||
        $code = $this->getErrorCode($e instanceof RequestException ? $e->getHttpCode() : $e->getCode());
 | 
			
		||||
        $message = $e->getMessage();
 | 
			
		||||
        if ($e instanceof ValidationException) {
 | 
			
		||||
            $message = $e->getMessage();
 | 
			
		||||
        } else {
 | 
			
		||||
            $message = htmlspecialchars($e->getMessage(), ENT_QUOTES | ENT_HTML5, 'UTF-8');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $extra = $e instanceof JsonSerializable ? $e->jsonSerialize() : [];
 | 
			
		||||
 | 
			
		||||
        $response = [
 | 
			
		||||
            'code' => $code,
 | 
			
		||||
            'status' => 'error',
 | 
			
		||||
@@ -211,7 +220,7 @@ trait ControllerResponseTrait
 | 
			
		||||
            'error' => [
 | 
			
		||||
                'code' => $code,
 | 
			
		||||
                'message' => $message
 | 
			
		||||
            ]
 | 
			
		||||
            ] + $extra
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        /** @var Debugger $debugger */
 | 
			
		||||
 
 | 
			
		||||
@@ -256,7 +256,7 @@ class Flex implements FlexInterface
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Remove missing objects if not asked to keep them.
 | 
			
		||||
        if (empty($option['keep_missing'])) {
 | 
			
		||||
        if (empty($options['keep_missing'])) {
 | 
			
		||||
            $list = array_filter($list);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ use Grav\Common\Data\Blueprint;
 | 
			
		||||
use Grav\Common\Data\Data;
 | 
			
		||||
use Grav\Common\Grav;
 | 
			
		||||
use Grav\Common\Twig\Twig;
 | 
			
		||||
use Grav\Common\Utils;
 | 
			
		||||
use Grav\Framework\Flex\Interfaces\FlexDirectoryFormInterface;
 | 
			
		||||
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
 | 
			
		||||
use Grav\Framework\Form\Interfaces\FormFlashInterface;
 | 
			
		||||
@@ -94,9 +95,14 @@ class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
 | 
			
		||||
            $uniqueId = md5($directory->getFlexType() . '-directory-' . $this->name);
 | 
			
		||||
        }
 | 
			
		||||
        $this->setUniqueId($uniqueId);
 | 
			
		||||
 | 
			
		||||
        $this->setFlashLookupFolder($directory->getDirectoryBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]');
 | 
			
		||||
        $this->form = $options['form'] ?? null;
 | 
			
		||||
 | 
			
		||||
        if (Utils::isPositive($this->form['disabled'] ?? false)) {
 | 
			
		||||
            $this->disable();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->initialize();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -129,6 +135,17 @@ class FlexDirectoryForm implements FlexDirectoryFormInterface, JsonSerializable
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $uniqueId
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function setUniqueId(string $uniqueId): void
 | 
			
		||||
    {
 | 
			
		||||
        if ($uniqueId !== '') {
 | 
			
		||||
            $this->uniqueid = $uniqueId;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $name
 | 
			
		||||
     * @param mixed $default
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ use Grav\Common\Data\Blueprint;
 | 
			
		||||
use Grav\Common\Data\Data;
 | 
			
		||||
use Grav\Common\Grav;
 | 
			
		||||
use Grav\Common\Twig\Twig;
 | 
			
		||||
use Grav\Common\Utils;
 | 
			
		||||
use Grav\Framework\Flex\Interfaces\FlexFormInterface;
 | 
			
		||||
use Grav\Framework\Flex\Interfaces\FlexObjectFormInterface;
 | 
			
		||||
use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
 | 
			
		||||
@@ -125,10 +126,15 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
 | 
			
		||||
            $uniqueId = md5($uniqueId);
 | 
			
		||||
        }
 | 
			
		||||
        $this->setUniqueId($uniqueId);
 | 
			
		||||
 | 
			
		||||
        $directory = $object->getFlexDirectory();
 | 
			
		||||
        $this->setFlashLookupFolder($options['flash_folder'] ?? $directory->getBlueprint()->get('form/flash_folder') ?? 'tmp://forms/[SESSIONID]');
 | 
			
		||||
        $this->form = $options['form'] ?? null;
 | 
			
		||||
 | 
			
		||||
        if (Utils::isPositive($this->items['disabled'] ?? $this->form['disabled'] ?? false)) {
 | 
			
		||||
            $this->disable();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!empty($options['reset'])) {
 | 
			
		||||
            $this->getFlash()->delete();
 | 
			
		||||
        }
 | 
			
		||||
@@ -172,6 +178,17 @@ class FlexForm implements FlexObjectFormInterface, JsonSerializable
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $uniqueId
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function setUniqueId(string $uniqueId): void
 | 
			
		||||
    {
 | 
			
		||||
        if ($uniqueId !== '') {
 | 
			
		||||
            $this->uniqueid = $uniqueId;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param string $name
 | 
			
		||||
     * @param mixed $default
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ use function in_array;
 | 
			
		||||
 * @package Grav\Framework\Flex
 | 
			
		||||
 * @template T of FlexObjectInterface
 | 
			
		||||
 * @template C of FlexCollectionInterface
 | 
			
		||||
 * @extends ObjectIndex<string,T>
 | 
			
		||||
 * @extends ObjectIndex<string,T,C>
 | 
			
		||||
 * @implements FlexIndexInterface<T>
 | 
			
		||||
 * @mixin C
 | 
			
		||||
 */
 | 
			
		||||
@@ -540,6 +540,7 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
 | 
			
		||||
     */
 | 
			
		||||
    protected function createFrom(array $entries, string $keyField = null)
 | 
			
		||||
    {
 | 
			
		||||
        /** @phpstan-var static<T,C> $index */
 | 
			
		||||
        $index = new static($entries, $this->getFlexDirectory());
 | 
			
		||||
        $index->setKeyField($keyField ?? $this->_keyField);
 | 
			
		||||
 | 
			
		||||
@@ -630,7 +631,10 @@ class FlexIndex extends ObjectIndex implements FlexCollectionInterface, FlexInde
 | 
			
		||||
     */
 | 
			
		||||
    protected function loadCollection(array $entries = null): CollectionInterface
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getFlexDirectory()->loadCollection($entries ?? $this->getEntries(), $this->_keyField);
 | 
			
		||||
        /** @var C $collection */
 | 
			
		||||
        $collection = $this->getFlexDirectory()->loadCollection($entries ?? $this->getEntries(), $this->_keyField);
 | 
			
		||||
 | 
			
		||||
        return $collection;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ namespace Grav\Framework\Flex;
 | 
			
		||||
use ArrayAccess;
 | 
			
		||||
use Exception;
 | 
			
		||||
use Grav\Common\Data\Blueprint;
 | 
			
		||||
use Grav\Common\Data\Data;
 | 
			
		||||
use Grav\Common\Debugger;
 | 
			
		||||
use Grav\Common\Grav;
 | 
			
		||||
use Grav\Common\Inflector;
 | 
			
		||||
@@ -72,8 +73,6 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
 | 
			
		||||
    private $_meta;
 | 
			
		||||
    /** @var array */
 | 
			
		||||
    protected $_original;
 | 
			
		||||
    /** @var array */
 | 
			
		||||
    protected $_changes;
 | 
			
		||||
    /** @var string */
 | 
			
		||||
    protected $storage_key;
 | 
			
		||||
    /** @var int */
 | 
			
		||||
@@ -454,13 +453,50 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get any changes based on data sent to update
 | 
			
		||||
     * Get diff array from the object.
 | 
			
		||||
     *
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function getDiff(): array
 | 
			
		||||
    {
 | 
			
		||||
        $blueprint = $this->getBlueprint();
 | 
			
		||||
 | 
			
		||||
        $flattenOriginal = $blueprint->flattenData($this->getOriginalData());
 | 
			
		||||
        $flattenElements = $blueprint->flattenData($this->getElements());
 | 
			
		||||
        $removedElements = array_diff_key($flattenOriginal, $flattenElements);
 | 
			
		||||
        $diff = [];
 | 
			
		||||
 | 
			
		||||
        // Include all added or changed keys.
 | 
			
		||||
        foreach ($flattenElements as $key => $value) {
 | 
			
		||||
            $orig = $flattenOriginal[$key] ?? null;
 | 
			
		||||
            if ($orig !== $value) {
 | 
			
		||||
                $diff[$key] = ['old' => $orig, 'new' => $value];
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Include all removed keys.
 | 
			
		||||
        foreach ($removedElements as $key => $value) {
 | 
			
		||||
            $diff[$key] = ['old' => $value, 'new' => null];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $diff;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get any changes from the object.
 | 
			
		||||
     *
 | 
			
		||||
     * @return array
 | 
			
		||||
     */
 | 
			
		||||
    public function getChanges(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->_changes ?? [];
 | 
			
		||||
        $diff = $this->getDiff();
 | 
			
		||||
 | 
			
		||||
        $data = new Data();
 | 
			
		||||
        foreach ($diff as $key => $change) {
 | 
			
		||||
            $data->set($key, $change['new']);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $data->toArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -641,14 +677,19 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
 | 
			
		||||
    public function update(array $data, array $files = [])
 | 
			
		||||
    {
 | 
			
		||||
        if ($data) {
 | 
			
		||||
            // Get currently stored data.
 | 
			
		||||
            $elements = $this->getElements();
 | 
			
		||||
 | 
			
		||||
            // Store original version of the object.
 | 
			
		||||
            if ($this->_original === null) {
 | 
			
		||||
                $this->_original = $elements;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            $blueprint = $this->getBlueprint();
 | 
			
		||||
 | 
			
		||||
            // Process updated data through the object filters.
 | 
			
		||||
            $this->filterElements($data);
 | 
			
		||||
 | 
			
		||||
            // Get currently stored data.
 | 
			
		||||
            $elements = $this->getElements();
 | 
			
		||||
 | 
			
		||||
            // Merge existing object to the test data to be validated.
 | 
			
		||||
            $test = $blueprint->mergeData($elements, $data);
 | 
			
		||||
 | 
			
		||||
@@ -657,17 +698,14 @@ class FlexObject implements FlexObjectInterface, FlexAuthorizeInterface
 | 
			
		||||
            $data = $blueprint->filter($data, true, true);
 | 
			
		||||
 | 
			
		||||
            // Finally update the object.
 | 
			
		||||
            foreach ($blueprint->flattenData($data) as $key => $value) {
 | 
			
		||||
            $flattenData = $blueprint->flattenData($data);
 | 
			
		||||
            foreach ($flattenData as $key => $value) {
 | 
			
		||||
                if ($value === null) {
 | 
			
		||||
                    $this->unsetNestedProperty($key);
 | 
			
		||||
                } else {
 | 
			
		||||
                    $this->setNestedProperty($key, $value);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Store the changes
 | 
			
		||||
            $this->_original = $this->getElements();
 | 
			
		||||
            $this->_changes = Utils::arrayDiffMultidimensional($this->_original, $elements);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($files && method_exists($this, 'setUpdatedMedia')) {
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,7 @@ interface FlexIndexInterface extends FlexCollectionInterface
 | 
			
		||||
     *
 | 
			
		||||
     * @param string|null $keyField Switch key field of the collection.
 | 
			
		||||
     * @return static  Returns a new Flex Collection with new key field.
 | 
			
		||||
     * @phpstan-return static<T>
 | 
			
		||||
     * @api
 | 
			
		||||
     */
 | 
			
		||||
    public function withKeyField(string $keyField = null);
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ class FlexPageObject extends FlexObject implements PageInterface, FlexTranslateI
 | 
			
		||||
    /** @var array|null */
 | 
			
		||||
    protected $_reorder;
 | 
			
		||||
    /** @var FlexPageObject|null */
 | 
			
		||||
    protected $_original;
 | 
			
		||||
    protected $_originalObject;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Clone page.
 | 
			
		||||
@@ -264,7 +264,7 @@ class FlexPageObject extends FlexObject implements PageInterface, FlexTranslateI
 | 
			
		||||
     */
 | 
			
		||||
    public function getOriginal()
 | 
			
		||||
    {
 | 
			
		||||
        return $this->_original;
 | 
			
		||||
        return $this->_originalObject;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -276,8 +276,8 @@ class FlexPageObject extends FlexObject implements PageInterface, FlexTranslateI
 | 
			
		||||
     */
 | 
			
		||||
    public function storeOriginal(): void
 | 
			
		||||
    {
 | 
			
		||||
        if (null === $this->_original) {
 | 
			
		||||
            $this->_original = clone $this;
 | 
			
		||||
        if (null === $this->_originalObject) {
 | 
			
		||||
            $this->_originalObject = clone $this;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -224,6 +224,7 @@ class FolderStorage extends AbstractFilesystemStorage
 | 
			
		||||
     * @param string $src
 | 
			
		||||
     * @param string $dst
 | 
			
		||||
     * @return bool
 | 
			
		||||
     * @throws RuntimeException
 | 
			
		||||
     */
 | 
			
		||||
    public function copyRow(string $src, string $dst): bool
 | 
			
		||||
    {
 | 
			
		||||
@@ -247,6 +248,7 @@ class FolderStorage extends AbstractFilesystemStorage
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritdoc}
 | 
			
		||||
     * @see FlexStorageInterface::renameRow()
 | 
			
		||||
     * @throws RuntimeException
 | 
			
		||||
     */
 | 
			
		||||
    public function renameRow(string $src, string $dst): bool
 | 
			
		||||
    {
 | 
			
		||||
@@ -634,7 +636,7 @@ class FolderStorage extends AbstractFilesystemStorage
 | 
			
		||||
        $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS | FilesystemIterator::UNIX_PATHS;
 | 
			
		||||
 | 
			
		||||
        $iterator = new FilesystemIterator($path, $flags);
 | 
			
		||||
        $list = [];
 | 
			
		||||
        $list = [[]];
 | 
			
		||||
        /** @var SplFileInfo $info */
 | 
			
		||||
        foreach ($iterator as $filename => $info) {
 | 
			
		||||
            if (!$info->isDir() || strpos($info->getFilename(), '.') === 0) {
 | 
			
		||||
@@ -644,11 +646,7 @@ class FolderStorage extends AbstractFilesystemStorage
 | 
			
		||||
            $list[] = $this->buildIndexFromFilesystem($filename);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!$list) {
 | 
			
		||||
            return [];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return count($list) > 1 ? array_merge(...$list) : $list[0];
 | 
			
		||||
        return array_merge(...$list);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,8 @@ trait FormTrait
 | 
			
		||||
    private $name;
 | 
			
		||||
    /** @var string */
 | 
			
		||||
    private $id;
 | 
			
		||||
    /** @var bool */
 | 
			
		||||
    private $enabled = true;
 | 
			
		||||
    /** @var string */
 | 
			
		||||
    private $uniqueid;
 | 
			
		||||
    /** @var string */
 | 
			
		||||
@@ -90,6 +92,30 @@ trait FormTrait
 | 
			
		||||
        $this->id = $id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function disable(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->enabled = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    public function enable(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->enabled = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return bool
 | 
			
		||||
     */
 | 
			
		||||
    public function isEnabled(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return string
 | 
			
		||||
     */
 | 
			
		||||
@@ -685,7 +711,7 @@ trait FormTrait
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            $data,
 | 
			
		||||
            $files ?? []
 | 
			
		||||
            $files
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @package    Grav\Framework\Logger
 | 
			
		||||
 *
 | 
			
		||||
 * @copyright  Copyright (c) 2015 - 2021 Trilby Media, LLC. All rights reserved.
 | 
			
		||||
 * @license    MIT License; see LICENSE file for details.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
namespace Grav\Framework\Logger\Processors;
 | 
			
		||||
 | 
			
		||||
use Grav\Common\Grav;
 | 
			
		||||
use Grav\Common\User\Interfaces\UserInterface;
 | 
			
		||||
use Monolog\Processor\ProcessorInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adds username and email to log messages.
 | 
			
		||||
 */
 | 
			
		||||
class UserProcessor implements ProcessorInterface
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * {@inheritDoc}
 | 
			
		||||
     */
 | 
			
		||||
    public function __invoke(array $record): array
 | 
			
		||||
    {
 | 
			
		||||
        /** @var UserInterface|null $user */
 | 
			
		||||
        $user = Grav::instance()['user'] ?? null;
 | 
			
		||||
        if ($user && $user->exists()) {
 | 
			
		||||
            $record['extra']['user'] = ['username' => $user->username, 'email' => $user->email];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $record;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -21,6 +21,7 @@ trait ArrayAccessTrait
 | 
			
		||||
     * @param mixed $offset  An offset to check for.
 | 
			
		||||
     * @return bool          Returns TRUE on success or FALSE on failure.
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetExists($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->hasProperty($offset);
 | 
			
		||||
@@ -32,6 +33,7 @@ trait ArrayAccessTrait
 | 
			
		||||
     * @param mixed $offset  The offset to retrieve.
 | 
			
		||||
     * @return mixed         Can return all value types.
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetGet($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getProperty($offset);
 | 
			
		||||
@@ -44,6 +46,7 @@ trait ArrayAccessTrait
 | 
			
		||||
     * @param mixed $value   The value to set.
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetSet($offset, $value)
 | 
			
		||||
    {
 | 
			
		||||
        $this->setProperty($offset, $value);
 | 
			
		||||
@@ -55,6 +58,7 @@ trait ArrayAccessTrait
 | 
			
		||||
     * @param mixed $offset  The offset to unset.
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetUnset($offset)
 | 
			
		||||
    {
 | 
			
		||||
        $this->unsetProperty($offset);
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ trait NestedArrayAccessTrait
 | 
			
		||||
     * @param mixed $offset  An offset to check for.
 | 
			
		||||
     * @return bool          Returns TRUE on success or FALSE on failure.
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetExists($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->hasNestedProperty($offset);
 | 
			
		||||
@@ -32,6 +33,7 @@ trait NestedArrayAccessTrait
 | 
			
		||||
     * @param mixed $offset  The offset to retrieve.
 | 
			
		||||
     * @return mixed         Can return all value types.
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetGet($offset)
 | 
			
		||||
    {
 | 
			
		||||
        return $this->getNestedProperty($offset);
 | 
			
		||||
@@ -44,6 +46,7 @@ trait NestedArrayAccessTrait
 | 
			
		||||
     * @param mixed $value   The value to set.
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetSet($offset, $value)
 | 
			
		||||
    {
 | 
			
		||||
        $this->setNestedProperty($offset, $value);
 | 
			
		||||
@@ -55,6 +58,7 @@ trait NestedArrayAccessTrait
 | 
			
		||||
     * @param mixed $offset  The offset to unset.
 | 
			
		||||
     * @return void
 | 
			
		||||
     */
 | 
			
		||||
    #[\ReturnTypeWillChange]
 | 
			
		||||
    public function offsetUnset($offset)
 | 
			
		||||
    {
 | 
			
		||||
        $this->unsetNestedProperty($offset);
 | 
			
		||||
 
 | 
			
		||||
@@ -207,8 +207,6 @@ trait ObjectCollectionTrait
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a copy from this collection by cloning all objects in the collection.
 | 
			
		||||
     *
 | 
			
		||||
     * @return static
 | 
			
		||||
     */
 | 
			
		||||
    public function copy()
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,7 @@ use RuntimeException;
 | 
			
		||||
 * Common Interface for both Objects and Collections
 | 
			
		||||
 * @package Grav\Framework\Object
 | 
			
		||||
 *
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template TKey of array-key
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @extends ObjectCollectionInterface<TKey,T>
 | 
			
		||||
 */
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ use Serializable;
 | 
			
		||||
/**
 | 
			
		||||
 * ObjectCollection Interface
 | 
			
		||||
 * @package Grav\Framework\Collection
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template TKey of array-key
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @extends CollectionInterface<TKey,T>
 | 
			
		||||
 * @extends Selectable<TKey,T>
 | 
			
		||||
@@ -76,6 +76,7 @@ interface ObjectCollectionInterface extends CollectionInterface, Selectable, Ser
 | 
			
		||||
     * Create a copy from this collection by cloning all objects in the collection.
 | 
			
		||||
     *
 | 
			
		||||
     * @return static
 | 
			
		||||
     * @phpstan-return static<TKey,T>
 | 
			
		||||
     */
 | 
			
		||||
    public function copy();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ use function array_slice;
 | 
			
		||||
/**
 | 
			
		||||
 * Class contains a collection of objects.
 | 
			
		||||
 *
 | 
			
		||||
 * @template TKey
 | 
			
		||||
 * @template TKey of array-key
 | 
			
		||||
 * @template T
 | 
			
		||||
 * @extends ArrayCollection<TKey,T>
 | 
			
		||||
 * @implements NestedObjectCollectionInterface<TKey,T>
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user