ouidade 1 year ago
parent
commit
faaab9c4d3
100 changed files with 712 additions and 355 deletions
  1. 91 0
      CHANGELOG.md
  2. 1 1
      assets/.gitkeep
  3. BIN
      bin/composer.phar
  4. 1 1
      bin/gpm
  5. 1 1
      bin/grav
  6. 1 1
      bin/plugin
  7. 2 1
      composer.json
  8. 208 170
      composer.lock
  9. 1 1
      index.php
  10. 7 0
      system/blueprints/config/system.yaml
  11. 11 0
      system/blueprints/flex/user-accounts.yaml
  12. 2 0
      system/config/system.yaml
  13. 2 2
      system/defines.php
  14. 1 1
      system/install.php
  15. 1 1
      system/router.php
  16. 1 1
      system/src/Grav/Common/Assets.php
  17. 1 1
      system/src/Grav/Common/Assets/BaseAsset.php
  18. 1 1
      system/src/Grav/Common/Assets/BlockAssets.php
  19. 1 1
      system/src/Grav/Common/Assets/Css.php
  20. 1 1
      system/src/Grav/Common/Assets/InlineCss.php
  21. 1 1
      system/src/Grav/Common/Assets/InlineJs.php
  22. 1 1
      system/src/Grav/Common/Assets/InlineJsModule.php
  23. 1 1
      system/src/Grav/Common/Assets/Js.php
  24. 1 1
      system/src/Grav/Common/Assets/JsModule.php
  25. 1 1
      system/src/Grav/Common/Assets/Link.php
  26. 1 1
      system/src/Grav/Common/Assets/Pipeline.php
  27. 1 1
      system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php
  28. 1 1
      system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php
  29. 1 1
      system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php
  30. 1 1
      system/src/Grav/Common/Backup/Backups.php
  31. 1 1
      system/src/Grav/Common/Browser.php
  32. 1 1
      system/src/Grav/Common/Cache.php
  33. 1 1
      system/src/Grav/Common/Composer.php
  34. 1 1
      system/src/Grav/Common/Config/CompiledBase.php
  35. 1 1
      system/src/Grav/Common/Config/CompiledBlueprints.php
  36. 1 1
      system/src/Grav/Common/Config/CompiledConfig.php
  37. 1 1
      system/src/Grav/Common/Config/CompiledLanguages.php
  38. 1 1
      system/src/Grav/Common/Config/Config.php
  39. 1 1
      system/src/Grav/Common/Config/ConfigFileFinder.php
  40. 1 1
      system/src/Grav/Common/Config/Languages.php
  41. 5 4
      system/src/Grav/Common/Config/Setup.php
  42. 5 5
      system/src/Grav/Common/Data/Blueprint.php
  43. 1 1
      system/src/Grav/Common/Data/BlueprintSchema.php
  44. 1 1
      system/src/Grav/Common/Data/Blueprints.php
  45. 1 1
      system/src/Grav/Common/Data/Data.php
  46. 1 1
      system/src/Grav/Common/Data/DataInterface.php
  47. 21 3
      system/src/Grav/Common/Data/Validation.php
  48. 1 1
      system/src/Grav/Common/Data/ValidationException.php
  49. 1 1
      system/src/Grav/Common/Debugger.php
  50. 1 1
      system/src/Grav/Common/Errors/BareHandler.php
  51. 1 1
      system/src/Grav/Common/Errors/Errors.php
  52. 1 1
      system/src/Grav/Common/Errors/SimplePageHandler.php
  53. 22 1
      system/src/Grav/Common/Errors/SystemFacade.php
  54. 82 10
      system/src/Grav/Common/File/CompiledFile.php
  55. 1 1
      system/src/Grav/Common/File/CompiledJsonFile.php
  56. 1 1
      system/src/Grav/Common/File/CompiledMarkdownFile.php
  57. 1 1
      system/src/Grav/Common/File/CompiledYamlFile.php
  58. 1 1
      system/src/Grav/Common/Filesystem/Archiver.php
  59. 59 53
      system/src/Grav/Common/Filesystem/Folder.php
  60. 1 1
      system/src/Grav/Common/Filesystem/RecursiveDirectoryFilterIterator.php
  61. 1 1
      system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php
  62. 1 1
      system/src/Grav/Common/Filesystem/ZipArchiver.php
  63. 1 1
      system/src/Grav/Common/Flex/FlexCollection.php
  64. 1 1
      system/src/Grav/Common/Flex/FlexIndex.php
  65. 1 1
      system/src/Grav/Common/Flex/FlexObject.php
  66. 1 1
      system/src/Grav/Common/Flex/Traits/FlexCollectionTrait.php
  67. 1 1
      system/src/Grav/Common/Flex/Traits/FlexCommonTrait.php
  68. 1 1
      system/src/Grav/Common/Flex/Traits/FlexGravTrait.php
  69. 1 1
      system/src/Grav/Common/Flex/Traits/FlexIndexTrait.php
  70. 1 1
      system/src/Grav/Common/Flex/Traits/FlexObjectTrait.php
  71. 1 1
      system/src/Grav/Common/Flex/Types/Generic/GenericCollection.php
  72. 1 1
      system/src/Grav/Common/Flex/Types/Generic/GenericIndex.php
  73. 1 1
      system/src/Grav/Common/Flex/Types/Generic/GenericObject.php
  74. 1 1
      system/src/Grav/Common/Flex/Types/Pages/PageCollection.php
  75. 4 7
      system/src/Grav/Common/Flex/Types/Pages/PageIndex.php
  76. 20 11
      system/src/Grav/Common/Flex/Types/Pages/PageObject.php
  77. 1 1
      system/src/Grav/Common/Flex/Types/Pages/Storage/PageStorage.php
  78. 1 1
      system/src/Grav/Common/Flex/Types/Pages/Traits/PageContentTrait.php
  79. 1 1
      system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php
  80. 1 1
      system/src/Grav/Common/Flex/Types/Pages/Traits/PageRoutableTrait.php
  81. 1 1
      system/src/Grav/Common/Flex/Types/Pages/Traits/PageTranslateTrait.php
  82. 1 1
      system/src/Grav/Common/Flex/Types/UserGroups/UserGroupCollection.php
  83. 1 1
      system/src/Grav/Common/Flex/Types/UserGroups/UserGroupIndex.php
  84. 1 1
      system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php
  85. 1 1
      system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php
  86. 1 1
      system/src/Grav/Common/Flex/Types/Users/Storage/UserFolderStorage.php
  87. 1 1
      system/src/Grav/Common/Flex/Types/Users/Traits/UserObjectLegacyTrait.php
  88. 1 1
      system/src/Grav/Common/Flex/Types/Users/UserCollection.php
  89. 7 5
      system/src/Grav/Common/Flex/Types/Users/UserIndex.php
  90. 81 1
      system/src/Grav/Common/Flex/Types/Users/UserObject.php
  91. 1 1
      system/src/Grav/Common/Form/FormFlash.php
  92. 1 1
      system/src/Grav/Common/GPM/AbstractCollection.php
  93. 1 1
      system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php
  94. 1 1
      system/src/Grav/Common/GPM/Common/CachedCollection.php
  95. 1 1
      system/src/Grav/Common/GPM/Common/Package.php
  96. 2 1
      system/src/Grav/Common/GPM/GPM.php
  97. 1 1
      system/src/Grav/Common/GPM/Installer.php
  98. 1 1
      system/src/Grav/Common/GPM/Licenses.php
  99. 1 1
      system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php
  100. 1 1
      system/src/Grav/Common/GPM/Local/Package.php

+ 91 - 0
CHANGELOG.md

@@ -1,3 +1,94 @@
+# v1.7.38
+## 01/02/2023
+
+1. [](#new)
+    * New `onBeforeSessionStart()` event to be used to store data lost during session regeneration (e.g. login)
+1. [](#improved)
+   * Vendor library updates to latest versions
+   * Updated `bin/composer.phar` to latest `2.4.4` version [#3627](https://github.com/getgrav/grav/issues/3627)
+1. [](#bugfix)
+
+   * Don't fail hard if pages recurse with same path
+   * Github workflows security hardening [#3624](https://github.com/getgrav/grav/pull/3624)
+
+# v1.7.37.1
+## 10/05/2022
+
+1. [](#bugfix)
+    * Fixed a bad return type [#3630](https://github.com/getgrav/grav/issues/3630)
+
+# v1.7.37
+## 10/05/2022
+
+1. [](#new)
+    * Added new `onPageHeaders()` event to allow for header modification as needed
+    * Added a `system.pages.dirs` configuration option to allow for configurable paths, and multiple page paths
+    * Added new `Pages::getSimplePagesHash` which is useful for caching pages specific data
+    * Updated to latest vendor libraries
+1. [](#bugfix)
+    * An attempt to workaround windows reading locked file issue [getgrav/grav-plugin-admin#2299](https://github.com/getgrav/grav-plugin-admin/issues/2299)
+    * Force user index file to be updated to fix email addresses [getgrav/grav-plugin-login#229](https://github.com/getgrav/grav-plugin-login/issues/229)
+
+# v1.7.36
+## 09/08/2022
+
+1. [](#new)
+    * Added `authorize-*@:` support for Flex blueprints, e.g. `authorize-disabled@: not delete` disables the field if user does not have access to delete object
+    * Added support for `flex-ignore@` to hide all the nested fields in the blueprint
+1. [](#bugfix)
+    * Fixed login with a capitalised email address when using old users [getgrav/grav-plugin-login#229](https://github.com/getgrav/grav-plugin-login/issues/229)
+
+# v1.7.35
+## 08/04/2022
+
+1. [](#new)
+   * Added support for `multipart/form-data` content type in PUT and PATCH requests
+   * Added support for object relationships
+   * Added variables `$environment` (string), `$request` (PSR-7 ServerRequestInterface|null) and `$uri` (PSR-7 Uri|null) to be used in `setup.php`
+1. [](#improved)
+   * Minor vendor updates
+
+# v1.7.34
+## 06/14/2022
+
+1. [](#new)
+    * Added back Yiddish to Language Codes [#3336](https://github.com/getgrav/grav/pull/3336)
+    * Ignore upcoming `media.json` file in media
+1. [](#bugfix)
+    * Regression: Fixed saving page with a new language causing cache corruption [getgrav/grav-plugin-admin#2282](https://github.com/getgrav/grav-plugin-admin/issues/2282)
+    * Fixed a potential fatal error when using watermark in images
+    * Fixed `bin/grav install` command with arbitrary destination folder name
+    * Fixed Twig `|filter()` allowing code execution
+    * Fixed login and user search by email not being case-insensitive when using Flex Users
+
+# v1.7.33
+## 04/25/2022
+
+1. [](#improved)
+    * When saving yaml and markdown, create also a cached version of the file and recompile it in opcache
+2. [](#bugfix)
+    * Fixed missing changes in **yaml** & **markdown** files if saved multiple times during the same second because of a caching issue
+    * Fixed XSS check not detecting onX events without quotes
+    * Fixed default collection ordering in pages admin
+
+# v1.7.32
+## 03/28/2022
+
+1. [](#new)
+    * Added `|replace_last(search, replace)` filter
+    * Added `parseurl` Twig function to expose PHP's `parse_url` function
+2. [](#improved)
+    * Added multi-language support for page routes in `Utils::url()`
+    * Set default maximum length for text fields
+      - `password`: 256
+      - `email`: 320
+      - `text`, `url`, `hidden`, `commalist`: 2048
+      - `text` (multiline), `textarea`: 65536
+3. [](#bugfix)
+   * Fixed issue with `system.cache.gzip: true` resulted in "Fetch Failed" for PHP 8.0.17 and PHP 8.1.4 [PHP issue #8218](https://github.com/php/php-src/issues/8218)
+   * Fix for multi-lang issues with Security Report
+   * Fixed page search not working with selected language [#3316](https://github.com/getgrav/grav/issues/3316)
+
 # v1.7.31
 ## 03/14/2022
 

+ 1 - 1
assets/.gitkeep

@@ -1 +1 @@
-/* @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved. */
+/* @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved. */

BIN
bin/composer.phar


+ 1 - 1
bin/gpm

@@ -2,7 +2,7 @@
 <?php
 
 /**
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
bin/grav

@@ -2,7 +2,7 @@
 <?php
 
 /**
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
bin/plugin

@@ -2,7 +2,7 @@
 <?php
 
 /**
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 2 - 1
composer.json

@@ -19,6 +19,7 @@
         "ext-zip": "*",
         "ext-dom": "*",
         "ext-libxml": "*",
+        "ext-gd": "*",
         "symfony/polyfill-mbstring": "~1.23",
         "symfony/polyfill-iconv": "^1.23",
         "symfony/polyfill-php74": "^1.23",
@@ -64,7 +65,7 @@
     },
     "require-dev": {
         "codeception/codeception": "^4.1",
-        "phpstan/phpstan": "^1.2",
+        "phpstan/phpstan": "^1.8",
         "phpstan/phpstan-deprecation-rules": "^1.0",
         "phpunit/php-code-coverage": "~9.2",
         "getgrav/markdowndocs": "^2.0",

File diff suppressed because it is too large
+ 208 - 170
composer.lock


+ 1 - 1
index.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav.Core
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 7 - 0
system/blueprints/config/system.yaml

@@ -1156,6 +1156,13 @@ form:
                 local6: local6
                 local7: local7
 
+            log.syslog.tag:
+              type: text
+              size: small
+              label: PLUGIN_ADMIN.SYSLOG_TAG
+              help: PLUGIN_ADMIN.SYSLOG_TAG_HELP
+              placeholder: "grav"
+
         debugger:
           type: tab
           title: PLUGIN_ADMIN.DEBUGGER

+ 11 - 0
system/blueprints/flex/user-accounts.yaml

@@ -125,6 +125,17 @@ config:
         - username
         - fullname
 
+  relationships:
+    media:
+      type: media
+      cardinality: to-many
+    avatar:
+      type: media
+      cardinality: to-one
+#    roles:
+#      type: user-groups
+#      cardinality: to-many
+
 blueprints:
   configure:
     fields:

+ 2 - 0
system/config/system.yaml

@@ -35,6 +35,7 @@ home:
 
 pages:
   type: regular                                  # EXPERIMENTAL: Page type: regular or flex
+  dirs: ['page://']                              # Advanced functionality, allows for multiple page paths
   theme: quark                                   # Default theme (defaults to "quark" theme)
   order:
     by: default                                  # Order pages by "default", "alpha" or "date"
@@ -144,6 +145,7 @@ log:
   handler: file                                 # Log handler. Currently supported: file | syslog
   syslog:
     facility: local6                            # Syslog facilities output
+    tag: grav                                   # Syslog tag. Default: "grav".
 
 debugger:
   enabled: false                                 # Enable Grav debugger and following settings

+ 2 - 2
system/defines.php

@@ -3,13 +3,13 @@
 /**
  * @package    Grav\Core
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
 // Some standard defines
 define('GRAV', true);
-define('GRAV_VERSION', '1.7.31');
+define('GRAV_VERSION', '1.7.38');
 define('GRAV_SCHEMA', '1.7.0_2020-11-20_1');
 define('GRAV_TESTING', false);
 

+ 1 - 1
system/install.php

@@ -2,7 +2,7 @@
 /**
  * @package    Grav\Core
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/router.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Core
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/BaseAsset.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/BlockAssets.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/Css.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/InlineCss.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/InlineJs.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/InlineJsModule.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/Js.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/JsModule.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/Link.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/Pipeline.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/Traits/AssetUtilsTrait.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets\Traits
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/Traits/LegacyAssetsTrait.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets\Traits
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Assets/Traits/TestingAssetsTrait.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Assets\Traits
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Backup/Backups.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Backup
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Browser.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Cache.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Composer.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Config/CompiledBase.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Config
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Config/CompiledBlueprints.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Config
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Config/CompiledConfig.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Config
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Config/CompiledLanguages.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Config
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Config/Config.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Config
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Config/ConfigFileFinder.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Config
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Config/Languages.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Config
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 5 - 4
system/src/Grav/Common/Config/Setup.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Config
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -182,13 +182,14 @@ class Setup extends Data
         // If no environment is set, make sure we get one (CLI or hostname).
         if (null === $environment) {
             if (defined('GRAV_CLI')) {
+                $request = null;
+                $uri = null;
                 $environment = 'cli';
             } else {
                 /** @var ServerRequestInterface $request */
                 $request = $container['request'];
-                $host = $request->getUri()->getHost();
-
-                $environment = Utils::substrToString($host, ':');
+                $uri = $request->getUri();
+                $environment = $uri->getHost();
             }
         }
 

+ 5 - 5
system/src/Grav/Common/Data/Blueprint.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Data
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -515,7 +515,7 @@ class Blueprint extends BlueprintForm
             $success = $this->resolveActions($user, $actions);
         }
         if (!$success) {
-            $this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
+            static::addPropertyRecursive($field, 'validate', ['ignore' => true]);
         }
     }
 
@@ -566,7 +566,7 @@ class Blueprint extends BlueprintForm
         }
 
         if ($matches) {
-            $this->addPropertyRecursive($field, 'validate', ['ignore' => true]);
+            static::addPropertyRecursive($field, 'validate', ['ignore' => true]);
             return;
         }
     }
@@ -577,7 +577,7 @@ class Blueprint extends BlueprintForm
      * @param mixed $value
      * @return void
      */
-    protected function addPropertyRecursive(array &$field, $property, $value)
+    public static function addPropertyRecursive(array &$field, $property, $value)
     {
         if (is_array($value) && isset($field[$property]) && is_array($field[$property])) {
             $field[$property] = array_merge_recursive($field[$property], $value);
@@ -587,7 +587,7 @@ class Blueprint extends BlueprintForm
 
         if (!empty($field['fields'])) {
             foreach ($field['fields'] as $key => &$child) {
-                $this->addPropertyRecursive($child, $property, $value);
+                static::addPropertyRecursive($child, $property, $value);
             }
         }
     }

+ 1 - 1
system/src/Grav/Common/Data/BlueprintSchema.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Data
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Data/Blueprints.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Data
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Data/Data.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Data
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Data/DataInterface.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Data
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 21 - 3
system/src/Grav/Common/Data/Validation.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Data
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -246,7 +246,9 @@ class Validation
             return false;
         }
 
-        $max = (int)($params['max'] ?? 0);
+        $multiline = isset($params['multiline']) && $params['multiline'];
+
+        $max = (int)($params['max'] ?? ($multiline ? 65536 : 2048));
         if ($max && $len > $max) {
             return false;
         }
@@ -256,7 +258,7 @@ class Validation
             return false;
         }
 
-        if ((!isset($params['multiline']) || !$params['multiline']) && preg_match('/\R/um', $value)) {
+        if (!$multiline && preg_match('/\R/um', $value)) {
             return false;
         }
 
@@ -317,6 +319,10 @@ class Validation
      */
     public static function typeCommaList($value, array $params, array $field)
     {
+        if (!isset($params['max'])) {
+            $params['max'] = 2048;
+        }
+
         return is_array($value) ? true : self::typeText($value, $params, $field);
     }
 
@@ -379,6 +385,10 @@ class Validation
      */
     public static function typePassword($value, array $params, array $field)
     {
+        if (!isset($params['max'])) {
+            $params['max'] = 256;
+        }
+
         return self::typeText($value, $params, $field);
     }
 
@@ -621,6 +631,10 @@ class Validation
      */
     public static function typeEmail($value, array $params, array $field)
     {
+        if (!isset($params['max'])) {
+            $params['max'] = 320;
+        }
+
         $values = !is_array($value) ? explode(',', preg_replace('/\s+/', '', $value)) : $value;
 
         foreach ($values as $val) {
@@ -642,6 +656,10 @@ class Validation
      */
     public static function typeUrl($value, array $params, array $field)
     {
+        if (!isset($params['max'])) {
+            $params['max'] = 2048;
+        }
+
         return self::typeText($value, $params, $field) && filter_var($value, FILTER_VALIDATE_URL);
     }
 

+ 1 - 1
system/src/Grav/Common/Data/ValidationException.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Data
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Debugger.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Errors/BareHandler.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Errors
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Errors/Errors.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Errors
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Errors/SimplePageHandler.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Errors
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 22 - 1
system/src/Grav/Common/Errors/SystemFacade.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Errors
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -43,4 +43,25 @@ class SystemFacade extends \Whoops\Util\SystemFacade
             $handler();
         }
     }
+
+
+    /**
+     * @param int $httpCode
+     *
+     * @return int
+     */
+    public function setHttpResponseCode($httpCode)
+    {
+        if (!headers_sent()) {
+            // Ensure that no 'location' header is present as otherwise this
+            // will override the HTTP code being set here, and mask the
+            // expected error page.
+            header_remove('location');
+
+            // Work around PHP bug #8218 (8.0.17 & 8.1.4).
+            header_remove('Content-Encoding');
+        }
+
+        return http_response_code($httpCode);
+    }
 }

+ 82 - 10
system/src/Grav/Common/File/CompiledFile.php

@@ -3,13 +3,15 @@
 /**
  * @package    Grav\Common\File
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
 namespace Grav\Common\File;
 
 use Exception;
+use Grav\Common\Debugger;
+use Grav\Common\Grav;
 use Grav\Common\Utils;
 use RocketTheme\Toolbox\File\PhpFile;
 use RuntimeException;
@@ -32,9 +34,10 @@ trait CompiledFile
     public function content($var = null)
     {
         try {
+            $filename = $this->filename;
             // If nothing has been loaded, attempt to get pre-compiled version of the file first.
             if ($var === null && $this->raw === null && $this->content === null) {
-                $key = md5($this->filename);
+                $key = md5($filename);
                 $file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
 
                 $modified = $this->modified();
@@ -48,39 +51,49 @@ trait CompiledFile
 
                 $class = get_class($this);
 
+                $size = filesize($filename);
                 $cache = $file->exists() ? $file->content() : null;
 
                 // Load real file if cache isn't up to date (or is invalid).
                 if (!isset($cache['@class'])
                     || $cache['@class'] !== $class
                     || $cache['modified'] !== $modified
-                    || $cache['filename'] !== $this->filename
+                    || ($cache['size'] ?? null) !== $size
+                    || $cache['filename'] !== $filename
                 ) {
                     // Attempt to lock the file for writing.
                     try {
-                        $file->lock(false);
+                        $locked = $file->lock(false);
                     } catch (Exception $e) {
-                        // Another process has locked the file; we will check this in a bit.
+                        $locked = false;
+
+                        /** @var Debugger $debugger */
+                        $debugger = Grav::instance()['debugger'];
+                        $debugger->addMessage(sprintf('%s(): Cannot obtain a lock for compiling cache file for %s: %s', __METHOD__, $this->filename, $e->getMessage()), 'warning');
                     }
 
                     // Decode RAW file into compiled array.
                     $data = (array)$this->decode($this->raw());
                     $cache = [
                         '@class' => $class,
-                        'filename' => $this->filename,
+                        'filename' => $filename,
                         'modified' => $modified,
+                        'size' => $size,
                         'data' => $data
                     ];
 
                     // If compiled file wasn't already locked by another process, save it.
-                    if ($file->locked() !== false) {
+                    if ($locked) {
                         $file->save($cache);
                         $file->unlock();
 
                         // Compile cached file into bytecode cache
-                        if (function_exists('opcache_invalidate')) {
+                        if (function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
+                            $lockName = $file->filename();
+
                             // Silence error if function exists, but is restricted.
-                            @opcache_invalidate($file->filename(), true);
+                            @opcache_invalidate($lockName, true);
+                            @opcache_compile_file($lockName);
                         }
                     }
                 }
@@ -89,12 +102,71 @@ trait CompiledFile
                 $this->content = $cache['data'];
             }
         } catch (Exception $e) {
-            throw new RuntimeException(sprintf('Failed to read %s: %s', Utils::basename($this->filename), $e->getMessage()), 500, $e);
+            throw new RuntimeException(sprintf('Failed to read %s: %s', Utils::basename($filename), $e->getMessage()), 500, $e);
         }
 
         return parent::content($var);
     }
 
+    /**
+     * Save file.
+     *
+     * @param  mixed  $data  Optional data to be saved, usually array.
+     * @return void
+     * @throws RuntimeException
+     */
+    public function save($data = null)
+    {
+        // Make sure that the cache file is always up to date!
+        $key = md5($this->filename);
+        $file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
+        try {
+            $locked = $file->lock();
+        } catch (Exception $e) {
+            $locked = false;
+
+            /** @var Debugger $debugger */
+            $debugger = Grav::instance()['debugger'];
+            $debugger->addMessage(sprintf('%s(): Cannot obtain a lock for compiling cache file for %s: %s', __METHOD__, $this->filename, $e->getMessage()), 'warning');
+        }
+
+        parent::save($data);
+
+        if ($locked) {
+            $modified = $this->modified();
+            $filename = $this->filename;
+            $class = get_class($this);
+            $size = filesize($filename);
+
+            // windows doesn't play nicely with this as it can't read when locked
+            if (!Utils::isWindows()) {
+                // Reload data from the filesystem. This ensures that we always cache the correct data (see issue #2282).
+                $this->raw = $this->content = null;
+                $data = (array)$this->decode($this->raw());
+            }
+
+            // Decode data into compiled array.
+            $cache = [
+                '@class' => $class,
+                'filename' => $filename,
+                'modified' => $modified,
+                'size' => $size,
+                'data' => $data
+            ];
+
+            $file->save($cache);
+            $file->unlock();
+
+            // Compile cached file into bytecode cache
+            if (function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
+                $lockName = $file->filename();
+                // Silence error if function exists, but is restricted.
+                @opcache_invalidate($lockName, true);
+                @opcache_compile_file($lockName);
+            }
+        }
+    }
+
     /**
      * Serialize file.
      *

+ 1 - 1
system/src/Grav/Common/File/CompiledJsonFile.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\File
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/File/CompiledMarkdownFile.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\File
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/File/CompiledYamlFile.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\File
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Filesystem/Archiver.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Filesystem
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 59 - 53
system/src/Grav/Common/Filesystem/Folder.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Filesystem
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -31,32 +31,34 @@ abstract class Folder
     /**
      * Recursively find the last modified time under given path.
      *
-     * @param  string $path
+     * @param array $paths
      * @return int
      */
-    public static function lastModifiedFolder($path)
+    public static function lastModifiedFolder(array $paths): int
     {
-        if (!file_exists($path)) {
-            return 0;
-        }
-
         $last_modified = 0;
 
         /** @var UniformResourceLocator $locator */
         $locator = Grav::instance()['locator'];
         $flags = RecursiveDirectoryIterator::SKIP_DOTS;
-        if ($locator->isStream($path)) {
-            $directory = $locator->getRecursiveIterator($path, $flags);
-        } else {
-            $directory = new RecursiveDirectoryIterator($path, $flags);
-        }
-        $filter  = new RecursiveFolderFilterIterator($directory);
-        $iterator = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::SELF_FIRST);
 
-        foreach ($iterator as $dir) {
-            $dir_modified = $dir->getMTime();
-            if ($dir_modified > $last_modified) {
-                $last_modified = $dir_modified;
+        foreach ($paths as $path) {
+            if (!file_exists($path)) {
+                return 0;
+            }
+            if ($locator->isStream($path)) {
+                $directory = $locator->getRecursiveIterator($path, $flags);
+            } else {
+                $directory = new RecursiveDirectoryIterator($path, $flags);
+            }
+            $filter  = new RecursiveFolderFilterIterator($directory);
+            $iterator = new RecursiveIteratorIterator($filter, RecursiveIteratorIterator::SELF_FIRST);
+
+            foreach ($iterator as $dir) {
+                $dir_modified = $dir->getMTime();
+                if ($dir_modified > $last_modified) {
+                    $last_modified = $dir_modified;
+                }
             }
         }
 
@@ -66,38 +68,40 @@ abstract class Folder
     /**
      * Recursively find the last modified time under given path by file.
      *
-     * @param string  $path
+     * @param array  $paths
      * @param string  $extensions   which files to search for specifically
      * @return int
      */
-    public static function lastModifiedFile($path, $extensions = 'md|yaml')
+    public static function lastModifiedFile(array $paths, $extensions = 'md|yaml'): int
     {
-        if (!file_exists($path)) {
-            return 0;
-        }
-
         $last_modified = 0;
 
         /** @var UniformResourceLocator $locator */
         $locator = Grav::instance()['locator'];
         $flags = RecursiveDirectoryIterator::SKIP_DOTS;
-        if ($locator->isStream($path)) {
-            $directory = $locator->getRecursiveIterator($path, $flags);
-        } else {
-            $directory = new RecursiveDirectoryIterator($path, $flags);
-        }
-        $recursive = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
-        $iterator = new RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
 
-        /** @var RecursiveDirectoryIterator $file */
-        foreach ($iterator as $filepath => $file) {
-            try {
-                $file_modified = $file->getMTime();
-                if ($file_modified > $last_modified) {
-                    $last_modified = $file_modified;
+        foreach($paths as $path) {
+            if (!file_exists($path)) {
+                return 0;
+            }
+            if ($locator->isStream($path)) {
+                $directory = $locator->getRecursiveIterator($path, $flags);
+            } else {
+                $directory = new RecursiveDirectoryIterator($path, $flags);
+            }
+            $recursive = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
+            $iterator = new RegexIterator($recursive, '/^.+\.'.$extensions.'$/i');
+
+            /** @var RecursiveDirectoryIterator $file */
+            foreach ($iterator as $file) {
+                try {
+                    $file_modified = $file->getMTime();
+                    if ($file_modified > $last_modified) {
+                        $last_modified = $file_modified;
+                    }
+                } catch (Exception $e) {
+                    Grav::instance()['log']->error('Could not process file: ' . $e->getMessage());
                 }
-            } catch (Exception $e) {
-                Grav::instance()['log']->error('Could not process file: ' . $e->getMessage());
             }
         }
 
@@ -107,28 +111,30 @@ abstract class Folder
     /**
      * Recursively md5 hash all files in a path
      *
-     * @param string $path
+     * @param array $paths
      * @return string
      */
-    public static function hashAllFiles($path)
+    public static function hashAllFiles(array $paths): string
     {
         $files = [];
 
-        if (file_exists($path)) {
-            $flags = RecursiveDirectoryIterator::SKIP_DOTS;
+        foreach ($paths as $path) {
+            if (file_exists($path)) {
+                $flags = RecursiveDirectoryIterator::SKIP_DOTS;
 
-            /** @var UniformResourceLocator $locator */
-            $locator = Grav::instance()['locator'];
-            if ($locator->isStream($path)) {
-                $directory = $locator->getRecursiveIterator($path, $flags);
-            } else {
-                $directory = new RecursiveDirectoryIterator($path, $flags);
-            }
+                /** @var UniformResourceLocator $locator */
+                $locator = Grav::instance()['locator'];
+                if ($locator->isStream($path)) {
+                    $directory = $locator->getRecursiveIterator($path, $flags);
+                } else {
+                    $directory = new RecursiveDirectoryIterator($path, $flags);
+                }
 
-            $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
+                $iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
 
-            foreach ($iterator as $file) {
-                $files[] = $file->getPathname() . '?'. $file->getMTime();
+                foreach ($iterator as $file) {
+                    $files[] = $file->getPathname() . '?'. $file->getMTime();
+                }
             }
         }
 

+ 1 - 1
system/src/Grav/Common/Filesystem/RecursiveDirectoryFilterIterator.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Filesystem
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Filesystem/RecursiveFolderFilterIterator.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Filesystem
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Filesystem/ZipArchiver.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Filesystem
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/FlexCollection.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/FlexIndex.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/FlexObject.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Traits/FlexCollectionTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Traits/FlexCommonTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Traits/FlexGravTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Traits/FlexIndexTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Traits/FlexObjectTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Generic/GenericCollection.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Generic/GenericIndex.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Generic/GenericObject.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Pages/PageCollection.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 4 - 7
system/src/Grav/Common/Flex/Types/Pages/PageIndex.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -454,7 +454,7 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
                 continue;
             }
 
-            // Get the main key without template and langauge.
+            // Get the main key without template and language.
             [$main_key,] = explode('|', $entry['storage_key'] . '|', 2);
 
             // Update storage key and language.
@@ -527,10 +527,7 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
         $language = $options['lang'];
 
         $status = 'error';
-        $msg = null;
         $response = [];
-        $children = null;
-        $sub_route = null;
         $extra = null;
 
         // Handle leaf_route
@@ -610,12 +607,12 @@ class PageIndex extends FlexPageIndex implements PageCollectionInterface
             $children = $page->children();
             /** @var PageIndex $children */
             $children = $children->getIndex();
-            $selectedChildren = $children->filterBy($filters, true);
+            $selectedChildren = $children->filterBy($filters + ['language' => $language], true);
 
             /** @var Header $header */
             $header = $page->header();
 
-            if (!$field && $header->get('admin.children_display_order') === 'collection' && ($orderby = $header->get('content.order.by'))) {
+            if (!$field && $header->get('admin.children_display_order', 'collection') === 'collection' && ($orderby = $header->get('content.order.by'))) {
                 // Use custom sorting by page header.
                 $sortby = $orderby;
                 $order = $header->get('content.order.dir', $order);

+ 20 - 11
system/src/Grav/Common/Flex/Types/Pages/PageObject.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -242,6 +242,7 @@ class PageObject extends FlexPageObject
     {
         /** @var PageCollection $siblings */
         $siblings = $variables['siblings'];
+        /** @var PageObject $sibling */
         foreach ($siblings as $sibling) {
             $sibling->save(false);
         }
@@ -585,38 +586,46 @@ class PageObject extends FlexPageObject
      */
     public function filterBy(array $filters, bool $recursive = false): bool
     {
+        $language = $filters['language'] ?? null;
+        if (null !== $language) {
+            /** @var PageObject $test */
+            $test = $this->getTranslation($language) ?? $this;
+        } else {
+            $test = $this;
+        }
+
         foreach ($filters as $key => $value) {
             switch ($key) {
                 case 'search':
-                    $matches = $this->search((string)$value) > 0.0;
+                    $matches = $test->search((string)$value) > 0.0;
                     break;
                 case 'page_type':
                     $types = $value ? explode(',', $value) : [];
-                    $matches = in_array($this->template(), $types, true);
+                    $matches = in_array($test->template(), $types, true);
                     break;
                 case 'extension':
-                    $matches = Utils::contains((string)$value, $this->extension());
+                    $matches = Utils::contains((string)$value, $test->extension());
                     break;
                 case 'routable':
-                    $matches = $this->isRoutable() === (bool)$value;
+                    $matches = $test->isRoutable() === (bool)$value;
                     break;
                 case 'published':
-                    $matches = $this->isPublished() === (bool)$value;
+                    $matches = $test->isPublished() === (bool)$value;
                     break;
                 case 'visible':
-                    $matches = $this->isVisible() === (bool)$value;
+                    $matches = $test->isVisible() === (bool)$value;
                     break;
                 case 'module':
-                    $matches = $this->isModule() === (bool)$value;
+                    $matches = $test->isModule() === (bool)$value;
                     break;
                 case 'page':
-                    $matches = $this->isPage() === (bool)$value;
+                    $matches = $test->isPage() === (bool)$value;
                     break;
                 case 'folder':
-                    $matches = $this->isPage() === !$value;
+                    $matches = $test->isPage() === !$value;
                     break;
                 case 'translated':
-                    $matches = $this->hasTranslation() === (bool)$value;
+                    $matches = $test->hasTranslation() === (bool)$value;
                     break;
                 default:
                     $matches = true;

+ 1 - 1
system/src/Grav/Common/Flex/Types/Pages/Storage/PageStorage.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Pages/Traits/PageContentTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Pages/Traits/PageLegacyTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Pages/Traits/PageRoutableTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Pages/Traits/PageTranslateTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/UserGroups/UserGroupCollection.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/UserGroups/UserGroupIndex.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/UserGroups/UserGroupObject.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Users/Storage/UserFileStorage.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Users/Storage/UserFolderStorage.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Users/Traits/UserObjectLegacyTrait.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/Flex/Types/Users/UserCollection.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 7 - 5
system/src/Grav/Common/Flex/Types/Users/UserIndex.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -30,7 +30,7 @@ use function is_string;
  */
 class UserIndex extends FlexIndex implements UserCollectionInterface
 {
-    public const VERSION = parent::VERSION . '.1';
+    public const VERSION = parent::VERSION . '.2';
 
     /**
      * @param FlexStorageInterface $storage
@@ -50,7 +50,7 @@ class UserIndex extends FlexIndex implements UserCollectionInterface
         //    return $index['index'];
         //}
 
-        // Load up to date index.
+        // Load up-to-date index.
         $entries = parent::loadEntriesFromStorage($storage);
 
         return static::updateIndexFile($storage, $index['index'], $entries, ['force_update' => $force]);
@@ -142,9 +142,11 @@ class UserIndex extends FlexIndex implements UserCollectionInterface
                 } elseif ($field === 'flex_key') {
                     $user = $this->withKeyField('flex_key')->get($query);
                 } elseif ($field === 'email') {
-                    $user = $this->withKeyField('email')->get($query);
+                    $email = mb_strtolower($query);
+                    $user = $this->withKeyField('email')->get($email);
                 } elseif ($field === 'username') {
-                    $user = $this->get(static::filterUsername($query, $this->getFlexDirectory()->getStorage()));
+                    $username = static::filterUsername($query, $this->getFlexDirectory()->getStorage());
+                    $user = $this->get($username);
                 } else {
                     $user = $this->__call('find', [$query, $field]);
                 }

+ 81 - 1
system/src/Grav/Common/Flex/Types/Users/UserObject.php

@@ -5,7 +5,7 @@ declare(strict_types=1);
 /**
  * @package    Grav\Common\Flex
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -31,6 +31,7 @@ use Grav\Common\Flex\Types\UserGroups\UserGroupIndex;
 use Grav\Common\User\Interfaces\UserInterface;
 use Grav\Common\User\Traits\UserTrait;
 use Grav\Common\Utils;
+use Grav\Framework\Contracts\Relationships\ToOneRelationshipInterface;
 use Grav\Framework\File\Formatter\JsonFormatter;
 use Grav\Framework\File\Formatter\YamlFormatter;
 use Grav\Framework\Filesystem\Filesystem;
@@ -38,7 +39,10 @@ use Grav\Framework\Flex\Flex;
 use Grav\Framework\Flex\FlexDirectory;
 use Grav\Framework\Flex\Storage\FileStorage;
 use Grav\Framework\Flex\Traits\FlexMediaTrait;
+use Grav\Framework\Flex\Traits\FlexRelationshipsTrait;
 use Grav\Framework\Form\FormFlashFile;
+use Grav\Framework\Media\MediaIdentifier;
+use Grav\Framework\Media\UploadedMediaObject;
 use Psr\Http\Message\UploadedFileInterface;
 use RocketTheme\Toolbox\Event\Event;
 use RocketTheme\Toolbox\File\FileInterface;
@@ -77,6 +81,7 @@ class UserObject extends FlexObject implements UserInterface, Countable
     }
     use UserTrait;
     use UserObjectLegacyTrait;
+    use FlexRelationshipsTrait;
 
     /** @var Closure|null */
     static public $authorizeCallable;
@@ -672,6 +677,81 @@ class UserObject extends FlexObject implements UserInterface, Countable
         return $folder;
     }
 
+    /**
+     * @param string $name
+     * @return array|object|null
+     * @internal
+     */
+    public function initRelationship(string $name)
+    {
+        switch ($name) {
+            case 'media':
+                $list = [];
+                foreach ($this->getMedia()->all() as $filename => $object) {
+                    $list[] = $this->buildMediaObject(null, $filename, $object);
+                }
+
+                return $list;
+            case 'avatar':
+                return $this->buildMediaObject('avatar', basename($this->getAvatarUrl()), $this->getAvatarImage());
+        }
+
+        throw new \InvalidArgumentException(sprintf('%s: Relationship %s does not exist', $this->getFlexType(), $name));
+    }
+
+    /**
+     * @return bool Return true if relationships were updated.
+     */
+    protected function updateRelationships(): bool
+    {
+        $modified = $this->getRelationships()->getModified();
+        if ($modified) {
+            foreach ($modified as $relationship) {
+                $name = $relationship->getName();
+                switch ($name) {
+                    case 'avatar':
+                        \assert($relationship instanceof ToOneRelationshipInterface);
+                        $this->updateAvatarRelationship($relationship);
+                        break;
+                    default:
+                        throw new \InvalidArgumentException(sprintf('%s: Relationship %s cannot be modified', $this->getFlexType(), $name), 400);
+                }
+            }
+
+            $this->resetRelationships();
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * @param ToOneRelationshipInterface $relationship
+     */
+    protected function updateAvatarRelationship(ToOneRelationshipInterface $relationship): void
+    {
+        $files = [];
+        $avatar = $this->getAvatarImage();
+        if ($avatar) {
+            $files['avatar'][$avatar->filename] = null;
+        }
+
+        $identifier = $relationship->getIdentifier();
+        if ($identifier) {
+            \assert($identifier instanceof MediaIdentifier);
+            $object = $identifier->getObject();
+            if ($object instanceof UploadedMediaObject) {
+                $uploadedFile = $object->getUploadedFile();
+                if ($uploadedFile) {
+                    $files['avatar'][$uploadedFile->getClientFilename()] = $uploadedFile;
+                }
+            }
+        }
+
+        $this->update([], $files);
+    }
+
     /**
      * @param string $name
      * @return Blueprint

+ 1 - 1
system/src/Grav/Common/Form/FormFlash.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\Form
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/GPM/AbstractCollection.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\GPM
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/GPM/Common/AbstractPackageCollection.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\GPM
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/GPM/Common/CachedCollection.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\GPM
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/GPM/Common/Package.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\GPM
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 2 - 1
system/src/Grav/Common/GPM/GPM.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\GPM
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 
@@ -12,6 +12,7 @@ namespace Grav\Common\GPM;
 use Exception;
 use Grav\Common\Grav;
 use Grav\Common\Filesystem\Folder;
+use Grav\Common\HTTP\Response;
 use Grav\Common\Inflector;
 use Grav\Common\Iterator;
 use Grav\Common\Utils;

+ 1 - 1
system/src/Grav/Common/GPM/Installer.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\GPM
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/GPM/Licenses.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\GPM
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/GPM/Local/AbstractPackageCollection.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\GPM
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

+ 1 - 1
system/src/Grav/Common/GPM/Local/Package.php

@@ -3,7 +3,7 @@
 /**
  * @package    Grav\Common\GPM
  *
- * @copyright  Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
+ * @copyright  Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  * @license    MIT License; see LICENSE file for details.
  */
 

Some files were not shown because too many files changed in this diff